mirror of
https://github.com/drogonframework/drogon.git
synced 2025-09-18 00:01:01 -04:00
Sqlite3 works, needs more testing
This commit is contained in:
parent
161a964238
commit
6db8df6bc0
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
3
orm_lib/src/sqlite3_impl/test/CMakeLists.txt
Normal file
3
orm_lib/src/sqlite3_impl/test/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
link_libraries(drogon trantor pthread dl)
|
||||
|
||||
add_executable(sqlite3_test1 test1.cc)
|
55
orm_lib/src/sqlite3_impl/test/test1.cc
Normal file
55
orm_lib/src/sqlite3_impl/test/test1.cc
Normal 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();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user