-
背景
承接上篇VSCode配置之C++ & SQLite3极简配置方案,参考《深入应用C++11: 代码优化与工程级应用》,基于VSCode+Cmake无痛实现SmartDB。
GitHub路径: smartDB_tutorial -
结果展示
-
主要变化(与SmartDB1.3相比)
1)使用最新的rapidjson库,弃用了GetKeyPtr(),GetKey()等方法,改用迭代器获取json对象,保证了不改变源码的大前提下,代码更简洁易懂bool ExcecuteJson(sqlite3_stmt *stmt, const rapidjson::Value& val) { int i = 0; for(auto iter = val.MemberBegin(); iter != val.MemberEnd(); ++iter) { auto key = iter->name.GetString(); if (SQLITE_OK != BindJsonValue(stmt, val[key], i + 1)){ return false; } i += 1; } m_code = sqlite3_step(stmt); sqlite3_reset(stmt); return SQLITE_DONE == m_code; }
2)将namespace detail目录更改为对应的utils.hpp文件,合并detail的各个namesapce内容
3)对smartDB的整个开发过程保留逐步测试代码,用于中间过程的调试(源码存在一定的编译问题,具体细节可参考文末的github仓库) -
核心代码部分 (smartDB.hpp)
1.1 smartDB.hpp#pragma once #include"SQLite3/sqlite3.h" #include <string> #include <unordered_map> #include <vector> #include <stdexcept> #include <iostream> #include <cctype> #include <ctime> #include <functional> #include <unordered_map> #include <memory> #include <type_traits> using namespace std; #include "utils.hpp" #include "variant.hpp" #include "non_copy.hpp" #include"json_cpp.hpp" typedef Variant<double, int, uint32_t, sqlite3_int64, char*, const char*, blob, string, nullptr_t> SqliteValue; class SmartDB : NonCopyable { public: SmartDB() : m_jsonHelper(m_buf, m_code){} explicit SmartDB(const string& fileName) : m_dbHandle(nullptr), m_statement(nullptr), m_isConned(false), m_code(0), m_jsonHelper(m_buf, m_code) { Open(fileName); } ~SmartDB() { Close(); } void Open(const string& fileName) { m_code = sqlite3_open(fileName.data(), &m_dbHandle); if (SQLITE_OK == m_code) { m_isConned = true; } } bool Close() { if (m_dbHandle == nullptr) return true; sqlite3_finalize(m_statement); m_code = CloseDBHandle(); bool ret = (SQLITE_OK == m_code); m_statement = nullptr; m_dbHandle = nullptr; return ret; } bool IsConned() const { return m_isConned; } bool Excecute(const string& sqlStr) { m_code = sqlite3_exec(m_dbHandle, sqlStr.data(), nullptr, nullptr, nullptr); return SQLITE_OK == m_code; } template <typename... Args> bool Excecute(const string& sqlStr, Args && ... args) { if (!Prepare(sqlStr)) { return false; } return ExcecuteArgs(std::forward<Args>(args)...); } bool Prepare(const string& sqlStr) { m_code = sqlite3_prepare_v2(m_dbHandle, sqlStr.data(), -1, &m_statement, nullptr); if (m_code != SQLITE_OK) { return false; } return true; } template <typename... Args> bool ExcecuteArgs(Args && ... args) { if (SQLITE_OK != detail::BindParams(m_statement, 1, std::forward<Args>(args)...)) { return false; } m_code = sqlite3_step(m_statement); sqlite3_reset(m_statement); return m_code == SQLITE_DONE; } template<typename Tuple> bool ExcecuteTuple(const string& sqlStr, Tuple&& t) { if (!Prepare(sqlStr)) { return false; } m_code = detail::ExcecuteTuple(m_statement, detail::MakeIndexes<std::tuple_size<Tuple>::value>::type(), std::forward<Tuple>(t)); return m_code == SQLITE_DONE; } bool ExcecuteJson(const string& sqlStr, const char* json) { rapidjson::Document doc; doc.Parse<0>(json); if (doc.HasParseError()) { cout << doc.GetParseError() << endl; return false; } if (!Prepare(sqlStr)) { return false; } return JsonTransaction(doc); } template < typename R = sqlite_int64, typename... Args> R ExecuteScalar(const string& sqlStr, Args&&... args) { if (!Prepare(sqlStr)) return GetErrorVal<R>(); if (SQLITE_OK != detail::BindParams(m_statement, 1, std::forward<Args>(args)...)) { return false; } m_code = sqlite3_step(m_statement); if (m_code != SQLITE_ROW) return GetErrorVal<R>(); SqliteValue val = GetValue(m_statement, 0); R result = val.Get<R>();// get<R>(val); sqlite3_reset(m_statement); return result; } template <typename... Args> std::shared_ptr<rapidjson::Document> Query(const string& query, Args&&... args) { if (!PrepareStatement(query, std::forward<Args>(args)...)) nullptr; auto doc = std::make_shared<rapidjson::Document>(); m_buf.Clear(); m_jsonHelper.BuildJsonObject(m_statement); doc->Parse<0>(m_buf.GetString()); return doc; } bool Begin() { return Excecute(BEGIN); } bool RollBack() { return Excecute(ROLLBACK); } bool Commit() { return Excecute(COMMIT); } int GetLastErrorCode() { return m_code; } private: int CloseDBHandle() { int code = sqlite3_close(m_dbHandle); while (code == SQLITE_BUSY) { // set rc to something that will exit the while loop code = SQLITE_OK; sqlite3_stmt * stmt = sqlite3_next_stmt(m_dbHandle, NULL); if (stmt == nullptr) break; code = sqlite3_finalize(stmt); if (code == SQLITE_OK) { code = sqlite3_close(m_dbHandle); } } return code; } template <typename... Args> bool PrepareStatement(const string& sqlStr, Args&&... args) { if (!Prepare(sqlStr)) { return false; } if (SQLITE_OK != detail::BindParams(m_statement, 1, std::forward<Args>(args)...)) { return false; } return true; } bool JsonTransaction(const rapidjson::Document& doc) { for (int i = 0; i < doc.Size(); i++) { if (!m_jsonHelper.ExcecuteJson(m_statement, doc[i])) { break; } } if (m_code != SQLITE_DONE) { return false; } return true; } private: SqliteValue GetValue(sqlite3_stmt *stmt, const int& index) { int type = sqlite3_column_type(stmt, index); auto it = m_valmap.find(type); if (it == m_valmap.end()) throw std::invalid_argument("can not find this type"); return it->second(stmt, index); } template<typename T> typename std::enable_if <std::is_arithmetic<T>::value, T>::type GetErrorVal() { return T(-9999); } template<typename T> typename std::enable_if <!std::is_arithmetic<T>::value, T>::type GetErrorVal() { return ""; } private: sqlite3* m_dbHandle; sqlite3_stmt* m_statement; bool m_isConned; int m_code; //JsonBuilder m_jsonBuilder; detail::JsonHelper m_jsonHelper; rapidjson::StringBuffer m_buf; static std::unordered_map<int, std::function <SqliteValue(sqlite3_stmt*, int)>> m_valmap; }; std::unordered_map<int, std::function <SqliteValue(sqlite3_stmt*, int)>> SmartDB::m_valmap = { { std::make_pair(SQLITE_INTEGER, [](sqlite3_stmt *stmt, int index){return sqlite3_column_int64(stmt, index); }) }, { std::make_pair(SQLITE_FLOAT, [](sqlite3_stmt *stmt, int index){return sqlite3_column_double(stmt, index); }) }, { std::make_pair(SQLITE_BLOB, [](sqlite3_stmt *stmt, int index){return string((const char*) sqlite3_column_blob(stmt, index));/* SmartDB::GetBlobVal(stmt, index);*/ }) }, { std::make_pair(SQLITE_TEXT, [](sqlite3_stmt *stmt, int index){return string((const char*) sqlite3_column_text(stmt, index)); }) }, { std::make_pair(SQLITE_NULL, [](sqlite3_stmt *stmt, int index){return nullptr; }) } };
1.2 smartDB.hpp (test)
void test_smartDB() {
SmartDB db;
db.Open("test.db");
// test excecute
const string sqlcreat = "CREATE TABLE if not exists PersonTable(ID INTEGER NOT NULL, Name Text, Address Text);";
if (!db.Excecute(sqlcreat))
return;
const string sqlinsert = "INSERT INTO PersonTable(ID, Name, Address) VALUES(?, ?, ?);";
int id = 1;
string name = "smartDB";
string city = "www.database";
if (!db.Excecute(sqlinsert, id, name, city)){
return;
}
// test tuple
db.ExcecuteTuple(sqlinsert, std::forward_as_tuple(id+1, "smartCpp", "www.cpp"));
// test json
auto state = db.ExcecuteJson(sqlinsert, "[{\"ID\":3,\"Name\":\"Xwell\",\"Address\":\"www.csdn\"}]");
// test jsoncpp
JsonCpp jcp;
jcp.StartArray();
for(int i = 0; i < 5; i++) {
jcp.StartObject();
jcp.WriteJson("ID", i+4);
jcp.WriteJson("Name", "smartXwell");
jcp.WriteJson("Address", "www.csdn");
jcp.EndObject();
}
jcp.EndArray();
state = db.ExcecuteJson(sqlinsert, jcp.GetString());
// test json doc
auto p = db.Query("select * from PersonTable");
cout << p -> Size() << endl;
cout << p -> GetString() << endl;
}
void test_time() {
SmartDB db;
db.Open("test_time.db");
const string sqlcreat = "CREATE TABLE if not exists TestTimeTable(ID INTEGER NOT NULL, KPIID INTEGER, CODE INTEGER, V1 INTEGER, V2 INTEGER, V3 REAL, V4 TEXT);";
if (!db.Excecute(sqlcreat))
return;
clock_t start = clock();
const string sqlinsert = "INSERT INTO TestTimeTable(ID, KPIID, CODE, V1, V2, V3, V4) VALUES(?, ?, ?, ?, ?, ?, ?);";
bool ret = db.Prepare(sqlinsert);
db.Begin(); // begin task
for (size_t i = 0; i < 1000000; i++)
{
ret = db.ExcecuteArgs(i, i, i, i, i, i + 1.25, "it is a test");
if (!ret)
break;
}
if (ret)
db.Commit(); //commit task
else
db.RollBack(); //rollback task
clock_t end = clock();
cout << "insert 1000000 time cost: " << (double)(end - start) / CLOCKS_PER_SEC << " s" << endl;
auto p = db.Query("select * from TestTimeTable");
clock_t end2 = clock();
cout << "query all time cost: " << (double)(end2 - end) / CLOCKS_PER_SEC << " s" << endl;
cout << "size: " << p->Size() << endl;
}
2 main.cpp
#include<iostream>
#include<string>
// #include"sqlite.hpp"
// #include"json_cpp.hpp"
#include"smartDB.hpp"
#include"utils.hpp"
using namespace std;
int main() {
// test_sqlite();
// test_json();
test_smartDB();
// test_time();
return 0;
}
- 其余部分,参见github项目地址
- 附VSCode Windows下的调试配置launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/bin/Debug/main.exe", #debug exe path
"args": [],
"cwd": "${workspaceFolder}"
}
]
}
- 小结
1)结合代码,理解smartDB的工作原理,改进数据库增删改查的效率,“让接口容易被正确使用,不易被误用”
2)step by step测试、调试中间结果,涉及到新的数据类型如Tuple, Variant, nonCopyable对象,type_traits, template等内容,还需进一加深理解和应用
3)使用新的rapidjson库,查看源码,理解document部分的设计,从而替换原始方法(实际上,更改第三方库的头文件是不安全的,个人推荐非必要不更改)
4) VSCode的Debug配置,方便实际中遇到的问题调试,本次在ExcecuteJson部分花费了较长时间,定位并修复了Json数据可以绑定,但数据库无法更新的问题,具体方法参看个人github仓库。
5) 代码最终上传路径:smartDB_tutorial