Sqlite3 works, needs more testing

This commit is contained in:
antao 2018-12-27 17:29:51 +08:00
parent 161a964238
commit 6db8df6bc0
13 changed files with 236 additions and 34 deletions

View File

@ -187,6 +187,9 @@ if (MAKETEST STREQUAL YES)
if(MYSQL_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/mysql_impl/test)
endif()
if(SQLITE3_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/sqlite3_impl/test)
endif()
if(USE_ORM)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/tests)
endif()

View File

@ -61,6 +61,7 @@ class DbClient : public trantor::NonCopyable
* - password: Password to be used if the server demands password authentication.
*
* For other key words on PostgreSQL, see the PostgreSQL documentation.
* Only a pair of key values is valid for Sqlite3, and its keyword is 'filename'.
*
* @param connNum: The number of connections to database server;
*/
@ -70,7 +71,9 @@ class DbClient : public trantor::NonCopyable
#if USE_MYSQL
static std::shared_ptr<DbClient> newMysqlClient(const std::string &connInfo, const size_t connNum);
#endif
#if USE_SQLITE3
static std::shared_ptr<DbClient> newSqlite3Client(const std::string &connInfo, const size_t connNum);
#endif
//Async method, nonblocking by default;
template <
typename FUNCTION1,

View File

@ -673,7 +673,7 @@ inline void Mapper<T>::insert(T &obj) noexcept(false)
assert(r.size() == 1);
obj = T(r[0]);
}
else if (_client->type() == ClientType::Mysql)
else // Mysql or Sqlite3
{
auto id = r.insertId();
obj.updateId(id);
@ -714,7 +714,7 @@ inline void Mapper<T>::insert(const T &obj,
assert(r.size() == 1);
rcb(T(r[0]));
}
else if (client->type() == ClientType::Mysql)
else //Mysql or Sqlite3
{
auto id = r.insertId();
auto newObj = obj;
@ -759,7 +759,7 @@ inline std::future<T> Mapper<T>::insertFuture(const T &obj) noexcept
assert(r.size() == 1);
prom->set_value(T(r[0]));
}
else if (client->type() == ClientType::Mysql)
else //Mysql or Sqlite3
{
auto id = r.insertId();
auto newObj = obj;

View File

@ -360,6 +360,10 @@ class SqlBinder
_format.push_back(MYSQL_TYPE_STRING);
#endif
}
else if (_type == ClientType::Sqlite3)
{
_format.push_back(Sqlite3TypeText);
}
return *this;
}
self &operator<<(std::string &str)
@ -383,6 +387,10 @@ class SqlBinder
_format.push_back(MYSQL_TYPE_STRING);
#endif
}
else if (_type == ClientType::Sqlite3)
{
_format.push_back(Sqlite3TypeText);
}
return *this;
}
self &operator<<(trantor::Date &&date)
@ -410,6 +418,10 @@ class SqlBinder
_format.push_back(MYSQL_TYPE_STRING);
#endif
}
else if (_type == ClientType::Sqlite3)
{
_format.push_back(Sqlite3TypeBlob);
}
return *this;
}
self &operator<<(std::vector<char> &&v)
@ -429,14 +441,32 @@ class SqlBinder
_format.push_back(MYSQL_TYPE_STRING);
#endif
}
else if (_type == ClientType::Sqlite3)
{
_format.push_back(Sqlite3TypeBlob);
}
return *this;
}
self &operator<<(float f)
{
if (_type == ClientType::Sqlite3)
{
return operator<<((double)f);
}
return operator<<(std::to_string(f));
}
self &operator<<(double f)
{
if (_type == ClientType::Sqlite3)
{
_paraNum++;
auto obj = std::make_shared<double>(f);
_objs.push_back(obj);
_format.push_back(Sqlite3TypeDouble);
_length.push_back(0);
_parameters.push_back((char *)(obj.get()));
return *this;
}
return operator<<(std::to_string(f));
}
self &operator<<(std::nullptr_t nullp)
@ -454,6 +484,10 @@ class SqlBinder
_format.push_back(MYSQL_TYPE_NULL);
#endif
}
else if (_type == ClientType::Sqlite3)
{
_format.push_back(Sqlite3TypeNull);
}
return *this;
}
self &operator<<(const Mode &mode)

View File

@ -35,3 +35,10 @@ std::shared_ptr<DbClient> DbClient::newMysqlClient(const std::string &connInfo,
return std::make_shared<DbClientImpl>(connInfo, connNum, ClientType::Mysql);
}
#endif
#if USE_SQLITE3
std::shared_ptr<DbClient> DbClient::newSqlite3Client(const std::string &connInfo, const size_t connNum)
{
return std::make_shared<DbClientImpl>(connInfo, connNum, ClientType::Sqlite3);
}
#endif

View File

@ -20,6 +20,9 @@
#if USE_MYSQL
#include "mysql_impl/MysqlConnection.h"
#endif
#if USE_SQLITE3
#include "sqlite3_impl/Sqlite3Connection.h"
#endif
#include "TransactionImpl.h"
#include <trantor/net/EventLoop.h>
#include <trantor/net/inner/Channel.h>
@ -268,6 +271,14 @@ DbConnectionPtr DbClientImpl::newConnection()
connPtr = std::make_shared<MysqlConnection>(_loopPtr.get(), _connInfo);
#else
return nullptr;
#endif
}
else if (_type == ClientType::Sqlite3)
{
#if USE_SQLITE3
connPtr = std::make_shared<Sqlite3Connection>(_loopPtr.get(), _connInfo);
#else
return nullptr;
#endif
}
else

View File

@ -51,7 +51,7 @@ Result::row_size_type PostgreSQLResultImpl::columnNumber(const char colName[]) c
{
auto N = PQfnumber(ptr, colName);
if (N == -1)
throw "there is no column named ..."; // TODO throw detail exception here;
throw std::string("there is no column named ") + colName; // TODO throw detail exception here;
return N;
}
throw "nullptr result"; //The program will not execute here

View File

@ -14,10 +14,26 @@
#include "Sqlite3Connection.h"
#include "Sqlite3ResultImpl.h"
#include <drogon/utils/Utilities.h>
#include <regex>
using namespace drogon::orm;
std::once_flag Sqlite3Connection::_once;
void Sqlite3Connection::onError(const std::string &sql, const std::function<void(const std::exception_ptr &)> &exceptCallback)
{
try
{
throw SqlError(sqlite3_errmsg(_conn.get()), sql);
}
catch (...)
{
auto exceptPtr = std::current_exception();
exceptCallback(exceptPtr);
}
}
Sqlite3Connection::Sqlite3Connection(trantor::EventLoop *loop, const std::string &connInfo)
: DbConnection(loop),
_queue("Sqlite")
@ -29,21 +45,42 @@ Sqlite3Connection::Sqlite3Connection(trantor::EventLoop *loop, const std::string
LOG_FATAL << sqlite3_errstr(ret);
}
});
auto thisPtr = shared_from_this();
_queue.runTaskInQueue([thisPtr, connInfo]() {
//Get the key and value
std::regex r(" *= *");
auto tmpStr = std::regex_replace(connInfo, r, "=");
std::string host, user, passwd, dbname, port;
auto keyValues = splitString(tmpStr, " ");
std::string filename;
for (auto &kvs : keyValues)
{
auto kv = splitString(kvs, "=");
assert(kv.size() == 2);
auto key = kv[0];
auto value = kv[1];
if (value[0] == '\'' && value[value.length() - 1] == '\'')
{
value = value.substr(1, value.length() - 2);
}
std::transform(key.begin(), key.end(), key.begin(), tolower);
if (key == "filename")
{
filename = value;
}
}
_queue.runTaskInQueue([this, filename = std::move(filename)]() {
sqlite3 *tmp = nullptr;
auto ret = sqlite3_open(connInfo.data(), &tmp);
thisPtr->_conn = std::shared_ptr<sqlite3>(tmp, [=](sqlite3 *ptr) { sqlite3_close(ptr); });
auto ret = sqlite3_open(filename.data(), &tmp);
_conn = std::shared_ptr<sqlite3>(tmp, [=](sqlite3 *ptr) { sqlite3_close(ptr); });
auto thisPtr = shared_from_this();
if (ret != SQLITE_OK)
{
LOG_FATAL << sqlite3_errstr(ret);
thisPtr->_closeCb(thisPtr);
_closeCb(thisPtr);
}
else
{
sqlite3_extended_result_codes(tmp, true);
thisPtr->_okCb(thisPtr);
_okCb(thisPtr);
}
});
}
@ -74,17 +111,30 @@ void Sqlite3Connection::execSqlInQueue(const std::string &sql,
{
sqlite3_stmt *stmt = nullptr;
const char *remaining;
auto ret = sqlite3_prepare_v2(_conn.get(), sql.data(), -1, &stmt, &remaining);
std::shared_ptr<sqlite3_stmt> stmtPtr = stmt ? std::shared_ptr<sqlite3_stmt>(stmt,
[](sqlite3_stmt *p) {
sqlite3_finalize(p);
})
: nullptr;
if (ret != SQLITE_OK)
{
//FIXME:throw exception here;
onError(sql, exceptCallback);
return;
}
if (!std::all_of(remaining, sql.data() + sql.size(), [](char ch) { return std::isspace(ch); }))
{
///close stmt
///FIXME
///throw errors::more_statements("Multiple semicolon separated statements are unsupported", sql);
try
{
throw SqlError("Multiple semicolon separated statements are unsupported", sql);
}
catch (...)
{
auto exceptPtr = std::current_exception();
exceptCallback(exceptPtr);
}
return;
}
for (int i = 0; i < parameters.size(); i++)
@ -119,13 +169,20 @@ void Sqlite3Connection::execSqlInQueue(const std::string &sql,
}
if (bindRet != SQLITE_OK)
{
//FIXME throw!
onError(sql, exceptCallback);
return;
}
}
int r;
int columnNum = sqlite3_column_count(stmt);
Sqlite3ResultImpl result(sql);
auto resultPtr = std::make_shared<Sqlite3ResultImpl>(sql);
for (int i = 0; i < columnNum; i++)
{
auto name = std::string(sqlite3_column_name(stmt, i));
std::transform(name.begin(), name.end(), name.begin(), tolower);
resultPtr->_columnNames.push_back(name);
resultPtr->_columnNameMap.insert({name, i});
}
while ((r = sqlite3_step(stmt)) == SQLITE_ROW)
{
std::vector<std::shared_ptr<std::string>> row;
@ -135,21 +192,37 @@ void Sqlite3Connection::execSqlInQueue(const std::string &sql,
switch (sqlite3_column_type(stmt, i))
{
case SQLITE_INTEGER:
row.push_back(std::make_shared<std::string>(formattedString("%ld", sqlite3_column_int64(stmt, i))));
break;
case SQLITE_FLOAT:
row.push_back(std::make_shared<std::string>(formattedString("%f", sqlite3_column_double(stmt, i))));
break;
case SQLITE_TEXT:
row.push_back(std::make_shared<std::string>((const char *)sqlite3_column_text(stmt, i), (size_t)sqlite3_column_bytes(stmt, i)));
break;
case SQLITE_BLOB:
{
const char *buf = (const char *)sqlite3_column_blob(stmt, i);
size_t len = sqlite3_column_bytes(stmt, i);
row.push_back(buf ? std::make_shared<std::string>(buf, len) : std::make_shared<std::string>());
}
break;
case SQLITE_NULL:
row.push_back(nullptr);
break;
}
}
result._result->push_back(std::move(row));
resultPtr->_result.push_back(std::move(row));
}
if (r != SQLITE_DONE)
{
//FIXME throw
onError(sql, exceptCallback);
return;
}
rcb(result);
// If the sql is a select statement? FIXME
resultPtr->_affectedRows = sqlite3_changes(_conn.get());
resultPtr->_insertId = sqlite3_last_insert_rowid(_conn.get());
// sqlite3_set_last_insert_rowid(_conn.get(), 0);
rcb(Result(resultPtr));
idleCb();
}

View File

@ -56,6 +56,7 @@ class Sqlite3Connection : public DbConnection, public std::enable_shared_from_th
const ResultCallback &rcb,
const std::function<void(const std::exception_ptr &)> &exceptCallback,
const std::function<void()> &idleCb);
void onError(const std::string &sql, const std::function<void(const std::exception_ptr &)> &exceptCallback);
trantor::SerialTaskQueue _queue;
std::shared_ptr<sqlite3> _conn;
};

View File

@ -19,16 +19,16 @@ using namespace drogon::orm;
Result::size_type Sqlite3ResultImpl::size() const noexcept
{
return _result->size();
return _result.size();
}
Result::row_size_type Sqlite3ResultImpl::columns() const noexcept
{
return _result->empty() ? 0 : (*_result)[0].size();
return _result.empty() ? 0 : _result[0].size();
}
const char *Sqlite3ResultImpl::columnName(row_size_type number) const
{
//FIXME
return "";
assert(number < _columnNames.size());
return _columnNames[number].c_str();
}
Result::size_type Sqlite3ResultImpl::affectedRows() const noexcept
{
@ -36,20 +36,30 @@ Result::size_type Sqlite3ResultImpl::affectedRows() const noexcept
}
Result::row_size_type Sqlite3ResultImpl::columnNumber(const char colName[]) const
{
//FIXME
return 0;
auto name = std::string(colName);
std::transform(name.begin(), name.end(), name.begin(), tolower);
auto iter = _columnNameMap.find(name);
if (iter != _columnNameMap.end())
{
return iter->second;
}
throw std::string("there is no column named ")+colName;
}
const char *Sqlite3ResultImpl::getValue(size_type row, row_size_type column) const
{
auto col = (*_result)[row][column];
auto col = _result[row][column];
return col ? nullptr : col->c_str();
}
bool Sqlite3ResultImpl::isNull(size_type row, row_size_type column) const
{
return !(*_result)[row][column];
return !_result[row][column];
}
Result::field_size_type Sqlite3ResultImpl::getLength(size_type row, row_size_type column) const
{
auto col = (*_result)[row][column];
auto col = _result[row][column];
return col ? 0 : col->length();
}
unsigned long long Sqlite3ResultImpl::insertId() const noexcept
{
return _insertId;
}

View File

@ -20,6 +20,7 @@
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
namespace drogon
{
@ -29,10 +30,7 @@ class Sqlite3ResultImpl : public ResultImpl
{
public:
Sqlite3ResultImpl(const std::string &query) noexcept
: _query(query),
_result(new std::vector<std::vector<std::shared_ptr<std::string>>>)
{
}
virtual size_type size() const noexcept override;
virtual row_size_type columns() const noexcept override;
@ -42,12 +40,16 @@ class Sqlite3ResultImpl : public ResultImpl
virtual const char *getValue(size_type row, row_size_type column) const override;
virtual bool isNull(size_type row, row_size_type column) const override;
virtual field_size_type getLength(size_type row, row_size_type column) const override;
virtual unsigned long long insertId() const noexcept override;
private:
friend class Sqlite3Connection;
std::shared_ptr<std::vector<std::vector<std::shared_ptr<std::string>>>> _result;
std::vector<std::vector<std::shared_ptr<std::string>>> _result;
std::string _query;
std::vector<std::string> _columnNames;
std::unordered_map<std::string, size_t> _columnNameMap;
size_t _affectedRows = 0;
size_t _insertId = 0;
};
} // namespace orm
} // namespace drogon

View File

@ -0,0 +1,3 @@
link_libraries(drogon trantor pthread dl)
add_executable(sqlite3_test1 test1.cc)

View File

@ -0,0 +1,55 @@
#include <drogon/orm/DbClient.h>
#include <trantor/utils/Logger.h>
#include <iostream>
#include <unistd.h>
using namespace drogon::orm;
int main()
{
trantor::Logger::setLogLevel(trantor::Logger::TRACE);
auto clientPtr = DbClient::newSqlite3Client("filename=test.db", 3);
sleep(1);
LOG_DEBUG << "start!";
// *clientPtr << "Drop table groups;" << Mode::Blocking >>
// [](const Result &r) {
// LOG_DEBUG << "droped";
// } >>
// [](const DrogonDbException &e) {
// std::cout << e.base().what() << std::endl;
// };
// ;
*clientPtr << "CREATE TABLE IF NOT EXISTS GROUPS (GROUP_ID INTEGER PRIMARY KEY autoincrement,\
GROUP_NAME TEXT,\
CREATER_ID INTEGER,\
CREATE_TIME TEXT,\
INVITING INTEGER,\
INVITING_USER_ID INTEGER,\
AVATAR_ID TEXT)"
<< Mode::Blocking >>
[](const Result &r) {
LOG_DEBUG << "created";
} >>
[](const DrogonDbException &e) {
std::cout << e.base().what() << std::endl;
};
*clientPtr << "insert into GROUPS (group_name) values(?)"
<< "test_group" << Mode::Blocking >>
[](const Result &r) {
LOG_DEBUG << "inserted:" << r.affectedRows();
LOG_DEBUG << "id:" << r.insertId();
} >>
[](const DrogonDbException &e) {
std::cout << e.base().what() << std::endl;
};
*clientPtr << "select * from GROUPS " >>
[](const Result &r) {
LOG_DEBUG << "affected rows:" << r.affectedRows();
LOG_DEBUG << "select " << r.size() << " rows";
LOG_DEBUG << "id:" << r.insertId();
} >>
[](const DrogonDbException &e) {
std::cout << e.base().what() << std::endl;
};
getchar();
}