From eb9ea6e5b891f9fb560d44a8c95a5f57d6507236 Mon Sep 17 00:00:00 2001 From: Wander Nauta Date: Sun, 22 Dec 2024 21:33:57 +0100 Subject: [PATCH] Replace SQLiteWriter d_columns with metadata call The d_columns field is removed. Instead, to find out whether some column or table exists, the sqlite3_table_column_metadata function is called. This resolves an issue where multiple connections are open to the SQLite database and calls to addValue cause columns to be added to a table that is also used by others. Before, these new columns would only be added to the d_columns list of the connection that created them, meaning that all others would be out of sync, and stay that way. If they added a row that had the new column, they would attempt an ALTER TABLE, which would fail. This change also means that we automatically follow SQLite when it comes to case sensitivity, so this should also fix #9. Finally, the code is slightly simpler. The getSchema method on MiniSQLite is now unused by the code itself, but since the method is public it is kept in case it is used by API users. --- sqlwriter.cc | 26 +++++++------------------ sqlwriter.hh | 4 ++-- testrunner.cc | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/sqlwriter.cc b/sqlwriter.cc index aacde15..34c04e6 100644 --- a/sqlwriter.cc +++ b/sqlwriter.cc @@ -241,7 +241,12 @@ void MiniSQLite::cycle() bool MiniSQLite::haveTable(const string& table) { - return !getSchema(table).empty(); + return sqlite3_table_column_metadata(d_sqlite, nullptr, table.c_str(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr) == SQLITE_OK; +} + +bool MiniSQLite::haveColumn(const string& table, const string& column) +{ + return sqlite3_table_column_metadata(d_sqlite, nullptr, table.c_str(), column.c_str(), nullptr, nullptr, nullptr, nullptr, nullptr) == SQLITE_OK; } @@ -279,18 +284,7 @@ void SQLiteWriter::commitThread() bool SQLiteWriter::haveColumn(const std::string& table, std::string_view name) { - if(d_columns[table].empty()) { - d_columns[table] = d_db.getSchema(table); - } - // cout<<"Do we have column "< cmp{name, std::string()}; - return binary_search(d_columns[table].begin(), d_columns[table].end(), cmp, - [](const auto& a, const auto& b) - { - return a.first < b.first; - }); - + return d_db.haveColumn(table, string(name)); } @@ -337,22 +331,16 @@ void SQLiteWriter::addValueGeneric(const std::string& table, const T& values, bo if(!haveColumn(table, p.first)) { if(std::get_if(&p.second)) { d_db.addColumn(table, p.first, "REAL", d_meta[table][p.first]); - d_columns[table].push_back({p.first, "REAL"}); } else if(std::get_if(&p.second)) { d_db.addColumn(table, p.first, "TEXT", d_meta[table][p.first]); - d_columns[table].push_back({p.first, "TEXT"}); } else if(std::get_if>(&p.second)) { d_db.addColumn(table, p.first, "BLOB", d_meta[table][p.first]); - d_columns[table].push_back({p.first, "BLOB"}); } else { d_db.addColumn(table, p.first, "INT", d_meta[table][p.first]); - d_columns[table].push_back({p.first, "INT"}); } - - sort(d_columns[table].begin(), d_columns[table].end()); } if(!first) { q+=", "; diff --git a/sqlwriter.hh b/sqlwriter.hh index a0b062b..8266780 100644 --- a/sqlwriter.hh +++ b/sqlwriter.hh @@ -49,6 +49,8 @@ public: else return iter->second != nullptr; } + bool haveTable(const std::string& table); + bool haveColumn(const std::string& table, const std::string &column); private: sqlite3* d_sqlite; @@ -56,7 +58,6 @@ private: std::vector> d_rows; // for exec() static int helperFunc(void* ptr, int cols, char** colvals, char** colnames); bool d_intransaction{false}; - bool haveTable(const std::string& table); }; class SQLiteWriter @@ -115,7 +116,6 @@ private: std::mutex d_mutex; MiniSQLite d_db; SQLWFlag d_flag{SQLWFlag::NoFlag}; - std::unordered_map>> d_columns; std::unordered_map> d_lastsig; std::unordered_map d_lastreplace; std::map> d_meta; diff --git a/testrunner.cc b/testrunner.cc index f6ef378..2ab5ace 100644 --- a/testrunner.cc +++ b/testrunner.cc @@ -158,6 +158,60 @@ TEST_CASE("test queries typed") { unlink("testrunner-example.sqlite3"); } +TEST_CASE("test multiple") { + unlink("testrunner-luke.sqlite3"); + + { + SQLiteWriter a("testrunner-luke.sqlite3"); + SQLiteWriter b("testrunner-luke.sqlite3"); + + a.addValue({{"dalton", "joe"}}); + b.addValue({{"dalton", "william"}}); + a.addValue({{"dalton", "jack"}}); + b.addValue({{"dalton", "averell"}}); + } + + unlink("testrunner-luke.sqlite3"); +} + +TEST_CASE("test column name case") { + unlink("testrunner-tintin.sqlite3"); + + { + SQLiteWriter sqw("testrunner-tintin.sqlite3"); + + sqw.addValue({{"name", "tintin"}}); + sqw.addValue({{"Name", "Snowy"}}); + sqw.addValue({{"NAME", "CAPTAIN HADDOCK"}}); + } + + { + SQLiteWriter sqw("testrunner-tintin.sqlite3"); + + auto res = sqw.query("select name from data order by rowid"); + CHECK(res.size() == 3); + CHECK(res.at(0).at("name") == "tintin"); + CHECK(res.at(1).at("name") == "Snowy"); + CHECK(res.at(2).at("name") == "CAPTAIN HADDOCK"); + } + + unlink("testrunner-tintin.sqlite3"); +} + +TEST_CASE("test table name case") { + unlink("testrunner-ao.sqlite3"); + + { + SQLiteWriter sqw("testrunner-ao.sqlite3"); + + sqw.addValue({{"name", "Asterix"}}, "a"); + sqw.addValue({{"name", "Obelix"}}, "a"); + sqw.addValue({{"name", "Getafix"}}, "A"); + sqw.addValue({{"name", "Vitalstatistix"}}, "A"); + } + + unlink("testrunner-ao.sqlite3"); +} TEST_CASE("test meta") { unlink("testrunner-example.sqlite3");