From 2fabcc8b3f63aaa12dc5b8e3fc99bd665938e29d Mon Sep 17 00:00:00 2001 From: fnc12 Date: Thu, 6 Aug 2020 21:12:41 +0300 Subject: [PATCH 1/2] added indexed_column function --- dev/column_result.h | 2 +- dev/error_code.h | 3 + dev/index.h | 24 +- dev/indexed_column.h | 67 +++++ dev/statement_serializator.h | 62 ++++- dev/storage.h | 77 ++---- dev/storage_base.h | 4 +- dev/storage_impl.h | 15 +- dev/table.h | 2 +- examples/index.cpp | 15 +- include/sqlite_orm/sqlite_orm.h | 258 +++++++++++++----- tests/CMakeLists.txt | 2 +- tests/index_tests.cpp | 47 ++++ tests/statement_serializator_tests/index.cpp | 19 ++ .../indexed_column.cpp | 46 ++++ 15 files changed, 498 insertions(+), 145 deletions(-) create mode 100644 dev/indexed_column.h create mode 100644 tests/index_tests.cpp create mode 100644 tests/statement_serializator_tests/index.cpp create mode 100644 tests/statement_serializator_tests/indexed_column.cpp diff --git a/dev/column_result.h b/dev/column_result.h index a6af11a1f..a32771be2 100644 --- a/dev/column_result.h +++ b/dev/column_result.h @@ -1,6 +1,6 @@ #pragma once -#include // std::enable_if, std::is_same, std::decay +#include // std::enable_if, std::is_same, std::decay, std::is_arithmetic #include // std::tuple #include // std::reference_wrapper diff --git a/dev/error_code.h b/dev/error_code.h index 0dd1f1b25..706bc4d26 100644 --- a/dev/error_code.h +++ b/dev/error_code.h @@ -22,6 +22,7 @@ namespace sqlite_orm { invalid_collate_argument_enum, failed_to_init_a_backup, unknown_member_value, + incorrect_order, }; } @@ -60,6 +61,8 @@ namespace sqlite_orm { return "Failed to init a backup"; case orm_error_code::unknown_member_value: return "Unknown member value"; + case orm_error_code::incorrect_order: + return "Incorrect order"; default: return "unknown error"; } diff --git a/dev/index.h b/dev/index.h index 2b68750f1..35de1669e 100644 --- a/dev/index.h +++ b/dev/index.h @@ -4,28 +4,38 @@ #include // std::string #include // std::forward +#include "indexed_column.h" + namespace sqlite_orm { namespace internal { + struct index_base { + std::string name; + bool unique = false; + }; + template - struct index_t { + struct index_t : index_base { using columns_type = std::tuple; using object_type = void; - std::string name; - bool unique; + index_t(std::string name, bool unique, columns_type columns_) : + index_base{move(name), unique}, columns(move(columns_)) {} + columns_type columns; }; } template - internal::index_t make_index(const std::string &name, Cols... cols) { - return {name, false, std::make_tuple(std::forward(cols)...)}; + internal::index_t::type...> make_index(const std::string &name, + Cols... cols) { + return {name, false, std::make_tuple(internal::make_indexed_column(cols)...)}; } template - internal::index_t make_unique_index(const std::string &name, Cols... cols) { - return {name, true, std::make_tuple(std::forward(cols)...)}; + internal::index_t::type...> make_unique_index(const std::string &name, + Cols... cols) { + return {name, true, std::make_tuple(internal::make_indexed_column(cols)...)}; } } diff --git a/dev/indexed_column.h b/dev/indexed_column.h new file mode 100644 index 000000000..9b2e710a2 --- /dev/null +++ b/dev/indexed_column.h @@ -0,0 +1,67 @@ +#pragma once + +#include // std::string + +namespace sqlite_orm { + + namespace internal { + + template + struct indexed_column_t { + using column_type = C; + + column_type column_or_expression; + std::string _collation_name; + int _order = 0; // -1 = desc, 1 = asc, 0 = not specified + + indexed_column_t collate(std::string name) { + auto res = std::move(*this); + res._collation_name = move(name); + return res; + } + + indexed_column_t asc() { + auto res = std::move(*this); + res._order = 1; + return res; + } + + indexed_column_t desc() { + auto res = std::move(*this); + res._order = -1; + return res; + } + }; + + template + struct indexed_column_maker { + using type = indexed_column_t; + + indexed_column_t operator()(C col) const { + return {std::move(col)}; + } + }; + + template + struct indexed_column_maker> { + using type = indexed_column_t; + + indexed_column_t operator()(indexed_column_t col) const { + return std::move(col); + } + }; + + template + auto make_indexed_column(C col) { + indexed_column_maker maker; + return maker(std::move(col)); + } + + } + + template + internal::indexed_column_t indexed_column(C column_or_expression) { + return {std::move(column_or_expression)}; + } + +} diff --git a/dev/statement_serializator.h b/dev/statement_serializator.h index 8de75baab..aaf193944 100644 --- a/dev/statement_serializator.h +++ b/dev/statement_serializator.h @@ -2,7 +2,7 @@ #include // std::stringstream #include // std::string -#include // std::is_arithmetic, std::enable_if +#include // std::enable_if #include // std::vector #include // std::iter_swap @@ -16,6 +16,8 @@ #include "column_names_getter.h" #include "order_by_serializator.h" #include "values.h" +#include "table_type.h" +#include "indexed_column.h" namespace sqlite_orm { @@ -1324,6 +1326,64 @@ namespace sqlite_orm { } }; + template + struct statement_serializator, void> { + using statement_type = indexed_column_t; + + template + std::string operator()(const statement_type &statement, const C &context) const { + std::stringstream ss; + ss << serialize(statement.column_or_expression, context); + if(!statement._collation_name.empty()) { + ss << " COLLATE " << statement._collation_name; + } + if(statement._order) { + switch(statement._order) { + case -1: + ss << " DESC"; + break; + case 1: + ss << " ASC"; + break; + default: + throw std::system_error(std::make_error_code(orm_error_code::column_not_found)); + } + } + return ss.str(); + } + }; + + template + struct statement_serializator, void> { + using statement_type = index_t; + + template + std::string operator()(const statement_type &statement, const C &context) const { + std::stringstream ss; + ss << "CREATE "; + if(statement.unique) { + ss << "UNIQUE "; + } + using columns_type = typename std::decay::type::columns_type; + using head_t = typename std::tuple_element<0, columns_type>::type::column_type; + using indexed_type = typename table_type::type; + ss << "INDEX IF NOT EXISTS '" << statement.name << "' ON '" + << context.impl.find_table_name(typeid(indexed_type)) << "' ("; + std::vector columnNames; + iterate_tuple(statement.columns, [&columnNames, &context](auto &v) { + columnNames.push_back(context.column_name(v.column_or_expression)); + }); + for(size_t i = 0; i < columnNames.size(); ++i) { + ss << "'" << columnNames[i] << "'"; + if(i < columnNames.size() - 1) { + ss << ", "; + } + } + ss << ")"; + return ss.str(); + } + }; + template struct statement_serializator, void> { using statement_type = where_t; diff --git a/dev/storage.h b/dev/storage.h index 5012c998a..73f8f5ef2 100644 --- a/dev/storage.h +++ b/dev/storage.h @@ -24,7 +24,6 @@ #include "type_printer.h" #include "tuple_helper.h" #include "constraints.h" -#include "table_type.h" #include "type_is_nullable.h" #include "field_printer.h" #include "rowid.h" @@ -87,14 +86,14 @@ namespace sqlite_orm { friend struct serializator_context_builder; template - void create_table(sqlite3 *db, const std::string &tableName, I *tableImpl) { + void create_table(sqlite3 *db, const std::string &tableName, const I &tableImpl) { std::stringstream ss; ss << "CREATE TABLE '" << tableName << "' ( "; - auto columnsCount = tableImpl->table.columns_count; + auto columnsCount = tableImpl.table.columns_count; auto index = 0; using context_t = serializator_context; context_t context{this->impl}; - iterate_tuple(tableImpl->table.columns, [columnsCount, &index, &ss, &context](auto &c) { + iterate_tuple(tableImpl.table.columns, [columnsCount, &index, &ss, &context](auto &c) { ss << serialize(c, context); if(index < columnsCount - 1) { ss << ", "; @@ -102,7 +101,7 @@ namespace sqlite_orm { index++; }); ss << ") "; - if(tableImpl->table._without_rowid) { + if(tableImpl.table._without_rowid) { ss << "WITHOUT ROWID "; } auto query = ss.str(); @@ -122,18 +121,18 @@ namespace sqlite_orm { } template - void backup_table(sqlite3 *db, I *tableImpl, const std::vector &columnsToIgnore) { + void backup_table(sqlite3 *db, const I &tableImpl, const std::vector &columnsToIgnore) { // here we copy source table to another with a name with '_backup' suffix, but in case table with such // a name already exists we append suffix 1, then 2, etc until we find a free name.. - auto backupTableName = tableImpl->table.name + "_backup"; - if(tableImpl->table_exists(backupTableName, db)) { + auto backupTableName = tableImpl.table.name + "_backup"; + if(tableImpl.table_exists(backupTableName, db)) { int suffix = 1; do { std::stringstream stream; stream << suffix; auto anotherBackupTableName = backupTableName + stream.str(); - if(!tableImpl->table_exists(anotherBackupTableName, db)) { + if(!tableImpl.table_exists(anotherBackupTableName, db)) { backupTableName = anotherBackupTableName; break; } @@ -143,11 +142,11 @@ namespace sqlite_orm { this->create_table(db, backupTableName, tableImpl); - tableImpl->copy_table(db, backupTableName, columnsToIgnore); + tableImpl.copy_table(db, backupTableName, columnsToIgnore); - this->drop_table_internal(tableImpl->table.name, db); + this->drop_table_internal(tableImpl.table.name, db); - tableImpl->rename_table(db, backupTableName, tableImpl->table.name); + tableImpl.rename_table(db, backupTableName, tableImpl.table.name); } template @@ -656,32 +655,11 @@ namespace sqlite_orm { protected: template - sync_schema_result - sync_table(storage_impl, Tss...> *tableImpl, sqlite3 *db, bool) { + sync_schema_result sync_table(const storage_impl, Tss...> &tableImpl, sqlite3 *db, bool) { auto res = sync_schema_result::already_in_sync; - std::stringstream ss; - ss << "CREATE "; - if(tableImpl->table.unique) { - ss << "UNIQUE "; - } - using columns_type = typename decltype(tableImpl->table)::columns_type; - using head_t = typename std::tuple_element<0, columns_type>::type; - using indexed_type = typename internal::table_type::type; - ss << "INDEX IF NOT EXISTS '" << tableImpl->table.name << "' ON '" - << this->impl.find_table_name(typeid(indexed_type)) << "' ( "; - std::vector columnNames; - iterate_tuple(tableImpl->table.columns, [&columnNames, this](auto &v) { - columnNames.push_back(this->impl.column_name(v)); - }); - for(size_t i = 0; i < columnNames.size(); ++i) { - ss << "'" << columnNames[i] << "'"; - if(i < columnNames.size() - 1) { - ss << ","; - } - ss << " "; - } - ss << ") "; - auto query = ss.str(); + using context_t = serializator_context; + context_t context{this->impl}; + auto query = serialize(tableImpl.table, context); auto rc = sqlite3_exec(db, query.c_str(), nullptr, nullptr, nullptr); if(rc != SQLITE_OK) { throw std::system_error(std::error_code(sqlite3_errcode(db), get_sqlite_error_category()), @@ -691,13 +669,14 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(storage_impl, Tss...> *tImpl, sqlite3 *db, bool preserve) { + sync_schema_result + sync_table(const storage_impl, Tss...> &tImpl, sqlite3 *db, bool preserve) { auto res = sync_schema_result::already_in_sync; - auto schema_stat = tImpl->schema_status(db, preserve); + auto schema_stat = tImpl.schema_status(db, preserve); if(schema_stat != decltype(schema_stat)::already_in_sync) { if(schema_stat == decltype(schema_stat)::new_table_created) { - this->create_table(db, tImpl->table.name, tImpl); + this->create_table(db, tImpl.table.name, tImpl); res = decltype(res)::new_table_created; } else { if(schema_stat == sync_schema_result::old_columns_removed || @@ -705,15 +684,15 @@ namespace sqlite_orm { schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) { // get table info provided in `make_table` call.. - auto storageTableInfo = tImpl->table.get_table_info(); + auto storageTableInfo = tImpl.table.get_table_info(); // now get current table info from db using `PRAGMA table_info` query.. - auto dbTableInfo = tImpl->get_table_info(tImpl->table.name, db); + auto dbTableInfo = tImpl.get_table_info(tImpl.table.name, db); // this vector will contain pointers to columns that gotta be added.. std::vector columnsToAdd; - tImpl->get_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); + tImpl.get_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); if(schema_stat == sync_schema_result::old_columns_removed) { @@ -724,7 +703,7 @@ namespace sqlite_orm { if(schema_stat == sync_schema_result::new_columns_added) { for(auto columnPointer: columnsToAdd) { - tImpl->add_column(*columnPointer, db); + tImpl.add_column(*columnPointer, db); } res = decltype(res)::new_columns_added; } @@ -736,8 +715,8 @@ namespace sqlite_orm { res = decltype(res)::new_columns_added_and_old_columns_removed; } } else if(schema_stat == sync_schema_result::dropped_and_recreated) { - this->drop_table_internal(tImpl->table.name, db); - this->create_table(db, tImpl->table.name, tImpl); + this->drop_table_internal(tImpl.table.name, db); + this->create_table(db, tImpl.table.name, tImpl); res = decltype(res)::dropped_and_recreated; } } @@ -777,9 +756,9 @@ namespace sqlite_orm { auto con = this->get_connection(); std::map result; auto db = con.get(); - this->impl.for_each([&result, db, preserve, this](auto tableImpl) { + this->impl.for_each([&result, db, preserve, this](auto &tableImpl) { auto res = this->sync_table(tableImpl, db, preserve); - result.insert({tableImpl->table.name, res}); + result.insert({tableImpl.table.name, res}); }); return result; } @@ -794,7 +773,7 @@ namespace sqlite_orm { std::map result; auto db = con.get(); this->impl.for_each([&result, db, preserve](auto tableImpl) { - result.insert({tableImpl->table.name, tableImpl->schema_status(db, preserve)}); + result.insert({tableImpl.table.name, tableImpl.schema_status(db, preserve)}); }); return result; } diff --git a/dev/storage_base.h b/dev/storage_base.h index d0d83e5e1..20f535064 100644 --- a/dev/storage_base.h +++ b/dev/storage_base.h @@ -524,8 +524,8 @@ namespace sqlite_orm { template static int foreign_keys_count(T &storageImpl) { auto res = 0; - storageImpl.for_each([&res](auto impl) { - res += impl->foreign_keys_count(); + storageImpl.for_each([&res](auto &impl) { + res += impl.foreign_keys_count(); }); return res; } diff --git a/dev/storage_impl.h b/dev/storage_impl.h index 8af7a9efd..617f55ef0 100644 --- a/dev/storage_impl.h +++ b/dev/storage_impl.h @@ -28,7 +28,7 @@ namespace sqlite_orm { struct storage_impl_base { - bool table_exists(const std::string &tableName, sqlite3 *db) { + bool table_exists(const std::string &tableName, sqlite3 *db) const { auto res = false; std::stringstream ss; ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = '" @@ -54,7 +54,7 @@ namespace sqlite_orm { return res; } - void rename_table(sqlite3 *db, const std::string &oldName, const std::string &newName) { + void rename_table(sqlite3 *db, const std::string &oldName, const std::string &newName) const { std::stringstream ss; ss << "ALTER TABLE " << oldName << " RENAME TO " << newName; auto query = ss.str(); @@ -112,7 +112,7 @@ namespace sqlite_orm { return notEqual; } - std::vector get_table_info(const std::string &tableName, sqlite3 *db) { + std::vector get_table_info(const std::string &tableName, sqlite3 *db) const { std::vector res; auto query = "PRAGMA table_info('" + tableName + "')"; auto rc = sqlite3_exec( @@ -161,7 +161,7 @@ namespace sqlite_orm { template void for_each(const L &l) { this->super::for_each(l); - l(this); + l(*this); } #if SQLITE_VERSION_NUMBER >= 3006019 @@ -261,7 +261,7 @@ namespace sqlite_orm { } } - void add_column(const table_info &ti, sqlite3 *db) { + void add_column(const table_info &ti, sqlite3 *db) const { std::stringstream ss; ss << "ALTER TABLE " << this->table.name << " ADD COLUMN " << ti.name << " "; ss << ti.type << " "; @@ -295,7 +295,8 @@ namespace sqlite_orm { * Copies current table to another table with a given **name**. * Performs CREATE TABLE %name% AS SELECT %this->table.columns_names()% FROM &this->table.name%; */ - void copy_table(sqlite3 *db, const std::string &name, const std::vector &columnsToIgnore) { + void + copy_table(sqlite3 *db, const std::string &name, const std::vector &columnsToIgnore) const { std::ignore = columnsToIgnore; std::stringstream ss; @@ -346,7 +347,7 @@ namespace sqlite_orm { } } - sync_schema_result schema_status(sqlite3 *db, bool preserve) { + sync_schema_result schema_status(sqlite3 *db, bool preserve) const { auto res = sync_schema_result::already_in_sync; diff --git a/dev/table.h b/dev/table.h index fb90dfaab..7fc86810a 100644 --- a/dev/table.h +++ b/dev/table.h @@ -232,7 +232,7 @@ namespace sqlite_orm { }); } - std::vector get_table_info() { + std::vector get_table_info() const { std::vector res; res.reserve(size_t(this->columns_count)); this->for_each_column([&res](auto &col) { diff --git a/examples/index.cpp b/examples/index.cpp index 71b541768..fd45ec6a1 100644 --- a/examples/index.cpp +++ b/examples/index.cpp @@ -17,13 +17,14 @@ using namespace sqlite_orm; // beware - put `make_index` before `make_table` cause `sync_schema` is called in reverse order // otherwise you'll receive an exception -auto storage = make_storage("index.sqlite", - make_index("idx_contacts_name", &Contract::firstName, &Contract::lastName), - make_unique_index("idx_contacts_email", &Contract::email), - make_table("contacts", - make_column("first_name", &Contract::firstName), - make_column("last_name", &Contract::lastName), - make_column("email", &Contract::email))); +auto storage = + make_storage("index.sqlite", + make_index("idx_contacts_name", &Contract::firstName, &Contract::lastName), + make_unique_index("idx_contacts_email", indexed_column(&Contract::email).collate("BINARY").desc()), + make_table("contacts", + make_column("first_name", &Contract::firstName), + make_column("last_name", &Contract::lastName), + make_column("email", &Contract::email))); int main(int, char **) { diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index 06eefe369..e602779c4 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -43,6 +43,7 @@ __pragma(push_macro("min")) invalid_collate_argument_enum, failed_to_init_a_backup, unknown_member_value, + incorrect_order, }; } @@ -80,6 +81,8 @@ namespace sqlite_orm { return "Failed to init a backup"; case orm_error_code::unknown_member_value: return "Unknown member value"; + case orm_error_code::incorrect_order: + return "Incorrect order"; default: return "unknown error"; } @@ -5311,29 +5314,105 @@ namespace sqlite_orm { #include // std::string #include // std::forward +// #include "indexed_column.h" + +#include // std::string + +namespace sqlite_orm { + + namespace internal { + + template + struct indexed_column_t { + using column_type = C; + + column_type column_or_expression; + std::string _collation_name; + int _order = 0; // -1 = desc, 1 = asc, 0 = not specified + + indexed_column_t collate(std::string name) { + auto res = std::move(*this); + res._collation_name = move(name); + return res; + } + + indexed_column_t asc() { + auto res = std::move(*this); + res._order = 1; + return res; + } + + indexed_column_t desc() { + auto res = std::move(*this); + res._order = -1; + return res; + } + }; + + template + struct indexed_column_maker { + using type = indexed_column_t; + + indexed_column_t operator()(C col) const { + return {std::move(col)}; + } + }; + + template + struct indexed_column_maker> { + using type = indexed_column_t; + + indexed_column_t operator()(indexed_column_t col) const { + return std::move(col); + } + }; + + template + auto make_indexed_column(C col) { + indexed_column_maker maker; + return maker(std::move(col)); + } + + } + + template + internal::indexed_column_t indexed_column(C column_or_expression) { + return {std::move(column_or_expression)}; + } + +} + namespace sqlite_orm { namespace internal { + struct index_base { + std::string name; + bool unique = false; + }; + template - struct index_t { + struct index_t : index_base { using columns_type = std::tuple; using object_type = void; - std::string name; - bool unique; + index_t(std::string name, bool unique, columns_type columns_) : + index_base{move(name), unique}, columns(move(columns_)) {} + columns_type columns; }; } template - internal::index_t make_index(const std::string &name, Cols... cols) { - return {name, false, std::make_tuple(std::forward(cols)...)}; + internal::index_t::type...> make_index(const std::string &name, + Cols... cols) { + return {name, false, std::make_tuple(internal::make_indexed_column(cols)...)}; } template - internal::index_t make_unique_index(const std::string &name, Cols... cols) { - return {name, true, std::make_tuple(std::forward(cols)...)}; + internal::index_t::type...> make_unique_index(const std::string &name, + Cols... cols) { + return {name, true, std::make_tuple(internal::make_indexed_column(cols)...)}; } } #pragma once @@ -5430,7 +5509,7 @@ namespace sqlite_orm { } #pragma once -#include // std::enable_if, std::is_same, std::decay +#include // std::enable_if, std::is_same, std::decay, std::is_arithmetic #include // std::tuple #include // std::reference_wrapper @@ -6075,7 +6154,7 @@ namespace sqlite_orm { }); } - std::vector get_table_info() { + std::vector get_table_info() const { std::vector res; res.reserve(size_t(this->columns_count)); this->for_each_column([&res](auto &col) { @@ -6187,7 +6266,7 @@ namespace sqlite_orm { struct storage_impl_base { - bool table_exists(const std::string &tableName, sqlite3 *db) { + bool table_exists(const std::string &tableName, sqlite3 *db) const { auto res = false; std::stringstream ss; ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = '" @@ -6213,7 +6292,7 @@ namespace sqlite_orm { return res; } - void rename_table(sqlite3 *db, const std::string &oldName, const std::string &newName) { + void rename_table(sqlite3 *db, const std::string &oldName, const std::string &newName) const { std::stringstream ss; ss << "ALTER TABLE " << oldName << " RENAME TO " << newName; auto query = ss.str(); @@ -6271,7 +6350,7 @@ namespace sqlite_orm { return notEqual; } - std::vector get_table_info(const std::string &tableName, sqlite3 *db) { + std::vector get_table_info(const std::string &tableName, sqlite3 *db) const { std::vector res; auto query = "PRAGMA table_info('" + tableName + "')"; auto rc = sqlite3_exec( @@ -6320,7 +6399,7 @@ namespace sqlite_orm { template void for_each(const L &l) { this->super::for_each(l); - l(this); + l(*this); } #if SQLITE_VERSION_NUMBER >= 3006019 @@ -6420,7 +6499,7 @@ namespace sqlite_orm { } } - void add_column(const table_info &ti, sqlite3 *db) { + void add_column(const table_info &ti, sqlite3 *db) const { std::stringstream ss; ss << "ALTER TABLE " << this->table.name << " ADD COLUMN " << ti.name << " "; ss << ti.type << " "; @@ -6454,7 +6533,8 @@ namespace sqlite_orm { * Copies current table to another table with a given **name**. * Performs CREATE TABLE %name% AS SELECT %this->table.columns_names()% FROM &this->table.name%; */ - void copy_table(sqlite3 *db, const std::string &name, const std::vector &columnsToIgnore) { + void + copy_table(sqlite3 *db, const std::string &name, const std::vector &columnsToIgnore) const { std::ignore = columnsToIgnore; std::stringstream ss; @@ -6505,7 +6585,7 @@ namespace sqlite_orm { } } - sync_schema_result schema_status(sqlite3 *db, bool preserve) { + sync_schema_result schema_status(sqlite3 *db, bool preserve) const { auto res = sync_schema_result::already_in_sync; @@ -6751,8 +6831,6 @@ namespace sqlite_orm { // #include "constraints.h" -// #include "table_type.h" - // #include "type_is_nullable.h" // #include "field_printer.h" @@ -8954,8 +9032,8 @@ namespace sqlite_orm { template static int foreign_keys_count(T &storageImpl) { auto res = 0; - storageImpl.for_each([&res](auto impl) { - res += impl->foreign_keys_count(); + storageImpl.for_each([&res](auto &impl) { + res += impl.foreign_keys_count(); }); return res; } @@ -9093,7 +9171,7 @@ namespace sqlite_orm { #include // std::stringstream #include // std::string -#include // std::is_arithmetic, std::enable_if +#include // std::enable_if #include // std::vector #include // std::iter_swap @@ -9409,6 +9487,10 @@ namespace sqlite_orm { } +// #include "table_type.h" + +// #include "indexed_column.h" + namespace sqlite_orm { namespace internal { @@ -10716,6 +10798,64 @@ namespace sqlite_orm { } }; + template + struct statement_serializator, void> { + using statement_type = indexed_column_t; + + template + std::string operator()(const statement_type &statement, const C &context) const { + std::stringstream ss; + ss << serialize(statement.column_or_expression, context); + if(!statement._collation_name.empty()) { + ss << " COLLATE " << statement._collation_name; + } + if(statement._order) { + switch(statement._order) { + case -1: + ss << " DESC"; + break; + case 1: + ss << " ASC"; + break; + default: + throw std::system_error(std::make_error_code(orm_error_code::column_not_found)); + } + } + return ss.str(); + } + }; + + template + struct statement_serializator, void> { + using statement_type = index_t; + + template + std::string operator()(const statement_type &statement, const C &context) const { + std::stringstream ss; + ss << "CREATE "; + if(statement.unique) { + ss << "UNIQUE "; + } + using columns_type = typename std::decay::type::columns_type; + using head_t = typename std::tuple_element<0, columns_type>::type::column_type; + using indexed_type = typename table_type::type; + ss << "INDEX IF NOT EXISTS '" << statement.name << "' ON '" + << context.impl.find_table_name(typeid(indexed_type)) << "' ("; + std::vector columnNames; + iterate_tuple(statement.columns, [&columnNames, &context](auto &v) { + columnNames.push_back(context.column_name(v.column_or_expression)); + }); + for(size_t i = 0; i < columnNames.size(); ++i) { + ss << "'" << columnNames[i] << "'"; + if(i < columnNames.size() - 1) { + ss << ", "; + } + } + ss << ")"; + return ss.str(); + } + }; + template struct statement_serializator, void> { using statement_type = where_t; @@ -11091,14 +11231,14 @@ namespace sqlite_orm { friend struct serializator_context_builder; template - void create_table(sqlite3 *db, const std::string &tableName, I *tableImpl) { + void create_table(sqlite3 *db, const std::string &tableName, const I &tableImpl) { std::stringstream ss; ss << "CREATE TABLE '" << tableName << "' ( "; - auto columnsCount = tableImpl->table.columns_count; + auto columnsCount = tableImpl.table.columns_count; auto index = 0; using context_t = serializator_context; context_t context{this->impl}; - iterate_tuple(tableImpl->table.columns, [columnsCount, &index, &ss, &context](auto &c) { + iterate_tuple(tableImpl.table.columns, [columnsCount, &index, &ss, &context](auto &c) { ss << serialize(c, context); if(index < columnsCount - 1) { ss << ", "; @@ -11106,7 +11246,7 @@ namespace sqlite_orm { index++; }); ss << ") "; - if(tableImpl->table._without_rowid) { + if(tableImpl.table._without_rowid) { ss << "WITHOUT ROWID "; } auto query = ss.str(); @@ -11126,18 +11266,18 @@ namespace sqlite_orm { } template - void backup_table(sqlite3 *db, I *tableImpl, const std::vector &columnsToIgnore) { + void backup_table(sqlite3 *db, const I &tableImpl, const std::vector &columnsToIgnore) { // here we copy source table to another with a name with '_backup' suffix, but in case table with such // a name already exists we append suffix 1, then 2, etc until we find a free name.. - auto backupTableName = tableImpl->table.name + "_backup"; - if(tableImpl->table_exists(backupTableName, db)) { + auto backupTableName = tableImpl.table.name + "_backup"; + if(tableImpl.table_exists(backupTableName, db)) { int suffix = 1; do { std::stringstream stream; stream << suffix; auto anotherBackupTableName = backupTableName + stream.str(); - if(!tableImpl->table_exists(anotherBackupTableName, db)) { + if(!tableImpl.table_exists(anotherBackupTableName, db)) { backupTableName = anotherBackupTableName; break; } @@ -11147,11 +11287,11 @@ namespace sqlite_orm { this->create_table(db, backupTableName, tableImpl); - tableImpl->copy_table(db, backupTableName, columnsToIgnore); + tableImpl.copy_table(db, backupTableName, columnsToIgnore); - this->drop_table_internal(tableImpl->table.name, db); + this->drop_table_internal(tableImpl.table.name, db); - tableImpl->rename_table(db, backupTableName, tableImpl->table.name); + tableImpl.rename_table(db, backupTableName, tableImpl.table.name); } template @@ -11660,32 +11800,11 @@ namespace sqlite_orm { protected: template - sync_schema_result - sync_table(storage_impl, Tss...> *tableImpl, sqlite3 *db, bool) { + sync_schema_result sync_table(const storage_impl, Tss...> &tableImpl, sqlite3 *db, bool) { auto res = sync_schema_result::already_in_sync; - std::stringstream ss; - ss << "CREATE "; - if(tableImpl->table.unique) { - ss << "UNIQUE "; - } - using columns_type = typename decltype(tableImpl->table)::columns_type; - using head_t = typename std::tuple_element<0, columns_type>::type; - using indexed_type = typename internal::table_type::type; - ss << "INDEX IF NOT EXISTS '" << tableImpl->table.name << "' ON '" - << this->impl.find_table_name(typeid(indexed_type)) << "' ( "; - std::vector columnNames; - iterate_tuple(tableImpl->table.columns, [&columnNames, this](auto &v) { - columnNames.push_back(this->impl.column_name(v)); - }); - for(size_t i = 0; i < columnNames.size(); ++i) { - ss << "'" << columnNames[i] << "'"; - if(i < columnNames.size() - 1) { - ss << ","; - } - ss << " "; - } - ss << ") "; - auto query = ss.str(); + using context_t = serializator_context; + context_t context{this->impl}; + auto query = serialize(tableImpl.table, context); auto rc = sqlite3_exec(db, query.c_str(), nullptr, nullptr, nullptr); if(rc != SQLITE_OK) { throw std::system_error(std::error_code(sqlite3_errcode(db), get_sqlite_error_category()), @@ -11695,13 +11814,14 @@ namespace sqlite_orm { } template - sync_schema_result sync_table(storage_impl, Tss...> *tImpl, sqlite3 *db, bool preserve) { + sync_schema_result + sync_table(const storage_impl, Tss...> &tImpl, sqlite3 *db, bool preserve) { auto res = sync_schema_result::already_in_sync; - auto schema_stat = tImpl->schema_status(db, preserve); + auto schema_stat = tImpl.schema_status(db, preserve); if(schema_stat != decltype(schema_stat)::already_in_sync) { if(schema_stat == decltype(schema_stat)::new_table_created) { - this->create_table(db, tImpl->table.name, tImpl); + this->create_table(db, tImpl.table.name, tImpl); res = decltype(res)::new_table_created; } else { if(schema_stat == sync_schema_result::old_columns_removed || @@ -11709,15 +11829,15 @@ namespace sqlite_orm { schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) { // get table info provided in `make_table` call.. - auto storageTableInfo = tImpl->table.get_table_info(); + auto storageTableInfo = tImpl.table.get_table_info(); // now get current table info from db using `PRAGMA table_info` query.. - auto dbTableInfo = tImpl->get_table_info(tImpl->table.name, db); + auto dbTableInfo = tImpl.get_table_info(tImpl.table.name, db); // this vector will contain pointers to columns that gotta be added.. std::vector columnsToAdd; - tImpl->get_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); + tImpl.get_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo); if(schema_stat == sync_schema_result::old_columns_removed) { @@ -11728,7 +11848,7 @@ namespace sqlite_orm { if(schema_stat == sync_schema_result::new_columns_added) { for(auto columnPointer: columnsToAdd) { - tImpl->add_column(*columnPointer, db); + tImpl.add_column(*columnPointer, db); } res = decltype(res)::new_columns_added; } @@ -11740,8 +11860,8 @@ namespace sqlite_orm { res = decltype(res)::new_columns_added_and_old_columns_removed; } } else if(schema_stat == sync_schema_result::dropped_and_recreated) { - this->drop_table_internal(tImpl->table.name, db); - this->create_table(db, tImpl->table.name, tImpl); + this->drop_table_internal(tImpl.table.name, db); + this->create_table(db, tImpl.table.name, tImpl); res = decltype(res)::dropped_and_recreated; } } @@ -11781,9 +11901,9 @@ namespace sqlite_orm { auto con = this->get_connection(); std::map result; auto db = con.get(); - this->impl.for_each([&result, db, preserve, this](auto tableImpl) { + this->impl.for_each([&result, db, preserve, this](auto &tableImpl) { auto res = this->sync_table(tableImpl, db, preserve); - result.insert({tableImpl->table.name, res}); + result.insert({tableImpl.table.name, res}); }); return result; } @@ -11798,7 +11918,7 @@ namespace sqlite_orm { std::map result; auto db = con.get(); this->impl.for_each([&result, db, preserve](auto tableImpl) { - result.insert({tableImpl->table.name, tableImpl->schema_status(db, preserve)}); + result.insert({tableImpl.table.name, tableImpl.schema_status(db, preserve)}); }); return result; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a813d1816..9a3d22120 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ else() add_subdirectory(third_party/sqlite) endif() -add_executable(unit_tests tests.cpp tests2.cpp tests3.cpp tests4.cpp tests5.cpp private_getters_tests.cpp pragma_tests.cpp explicit_columns.cpp core_functions_tests.cpp constraints/composite_key.cpp static_tests.cpp operators/arithmetic_operators.cpp operators/like.cpp operators/glob.cpp operators/in.cpp operators/cast.cpp operators/is_null.cpp operators/not_operator.cpp operators/bitwise.cpp dynamic_order_by.cpp prepared_statement_tests/select.cpp prepared_statement_tests/get_all.cpp prepared_statement_tests/get_all_pointer.cpp prepared_statement_tests/get_all_optional.cpp prepared_statement_tests/update_all.cpp prepared_statement_tests/remove_all.cpp prepared_statement_tests/get.cpp prepared_statement_tests/get_pointer.cpp prepared_statement_tests/get_optional.cpp prepared_statement_tests/update.cpp prepared_statement_tests/remove.cpp prepared_statement_tests/insert.cpp prepared_statement_tests/replace.cpp prepared_statement_tests/insert_range.cpp prepared_statement_tests/replace_range.cpp prepared_statement_tests/insert_explicit.cpp pragma_tests.cpp simple_query.cpp static_tests/is_bindable.cpp static_tests/arithmetic_operators_result_type.cpp static_tests/tuple_conc.cpp static_tests/node_tuple.cpp static_tests/bindable_filter.cpp static_tests/count_tuple.cpp static_tests/member_traits_tests.cpp static_tests/select_return_type.cpp constraints/default.cpp constraints/unique.cpp constraints/foreign_key.cpp constraints/check.cpp table_tests.cpp statement_serializator_tests/primary_key.cpp statement_serializator_tests/column_names.cpp statement_serializator_tests/autoincrement.cpp statement_serializator_tests/arithmetic_operators.cpp statement_serializator_tests/core_functions.cpp statement_serializator_tests/comparison_operators.cpp statement_serializator_tests/unique.cpp statement_serializator_tests/foreign_key.cpp statement_serializator_tests/collate.cpp statement_serializator_tests/check.cpp unique_cases/get_all_with_two_tables.cpp unique_cases/prepare_get_all_with_case.cpp unique_cases/index_named_table_with_fk.cpp unique_cases/issue525.cpp get_all_custom_containers.cpp select_asterisk.cpp backup_tests.cpp) +add_executable(unit_tests tests.cpp tests2.cpp tests3.cpp tests4.cpp tests5.cpp private_getters_tests.cpp pragma_tests.cpp explicit_columns.cpp core_functions_tests.cpp index_tests.cpp constraints/composite_key.cpp static_tests.cpp operators/arithmetic_operators.cpp operators/like.cpp operators/glob.cpp operators/in.cpp operators/cast.cpp operators/is_null.cpp operators/not_operator.cpp operators/bitwise.cpp dynamic_order_by.cpp prepared_statement_tests/select.cpp prepared_statement_tests/get_all.cpp prepared_statement_tests/get_all_pointer.cpp prepared_statement_tests/get_all_optional.cpp prepared_statement_tests/update_all.cpp prepared_statement_tests/remove_all.cpp prepared_statement_tests/get.cpp prepared_statement_tests/get_pointer.cpp prepared_statement_tests/get_optional.cpp prepared_statement_tests/update.cpp prepared_statement_tests/remove.cpp prepared_statement_tests/insert.cpp prepared_statement_tests/replace.cpp prepared_statement_tests/insert_range.cpp prepared_statement_tests/replace_range.cpp prepared_statement_tests/insert_explicit.cpp pragma_tests.cpp simple_query.cpp static_tests/is_bindable.cpp static_tests/arithmetic_operators_result_type.cpp static_tests/tuple_conc.cpp static_tests/node_tuple.cpp static_tests/bindable_filter.cpp static_tests/count_tuple.cpp static_tests/member_traits_tests.cpp static_tests/select_return_type.cpp constraints/default.cpp constraints/unique.cpp constraints/foreign_key.cpp constraints/check.cpp table_tests.cpp statement_serializator_tests/primary_key.cpp statement_serializator_tests/column_names.cpp statement_serializator_tests/autoincrement.cpp statement_serializator_tests/arithmetic_operators.cpp statement_serializator_tests/core_functions.cpp statement_serializator_tests/comparison_operators.cpp statement_serializator_tests/unique.cpp statement_serializator_tests/foreign_key.cpp statement_serializator_tests/collate.cpp statement_serializator_tests/check.cpp statement_serializator_tests/index.cpp statement_serializator_tests/indexed_column.cpp unique_cases/get_all_with_two_tables.cpp unique_cases/prepare_get_all_with_case.cpp unique_cases/index_named_table_with_fk.cpp unique_cases/issue525.cpp get_all_custom_containers.cpp select_asterisk.cpp backup_tests.cpp) if(SQLITE_ORM_OMITS_CODECVT) diff --git a/tests/index_tests.cpp b/tests/index_tests.cpp new file mode 100644 index 000000000..ccb64f6ad --- /dev/null +++ b/tests/index_tests.cpp @@ -0,0 +1,47 @@ +#include +#include + +using namespace sqlite_orm; + +TEST_CASE("index") { + struct User { + int id = 0; + std::string name; + }; + + auto table = make_table("users", make_column("id", &User::id), make_column("name", &User::name)); + { + auto storage = make_storage({}, make_index("id_index", &User::id), table); + storage.sync_schema(); + } + { + auto storage = make_storage({}, make_index("id_index", indexed_column(&User::id).asc()), table); + storage.sync_schema(); + } + { + auto storage = make_storage({}, make_index("id_index", indexed_column(&User::id).desc()), table); + storage.sync_schema(); + } + { + auto storage = make_storage({}, make_index("name_index", &User::name), table); + storage.sync_schema(); + } + { + auto storage = make_storage({}, make_index("name_index", indexed_column(&User::name)), table); + storage.sync_schema(); + } + { + auto storage = make_storage({}, make_index("name_index", indexed_column(&User::name).collate("binary")), table); + storage.sync_schema(); + } + { + auto storage = + make_storage({}, make_index("name_index", indexed_column(&User::name).collate("binary").asc()), table); + storage.sync_schema(); + } + { + auto storage = + make_storage({}, make_index("name_index", indexed_column(&User::name).collate("binary").desc()), table); + storage.sync_schema(); + } +} diff --git a/tests/statement_serializator_tests/index.cpp b/tests/statement_serializator_tests/index.cpp new file mode 100644 index 000000000..64e247a9a --- /dev/null +++ b/tests/statement_serializator_tests/index.cpp @@ -0,0 +1,19 @@ +#include +#include + +using namespace sqlite_orm; + +TEST_CASE("statement_serializator index") { + struct User { + int id = 0; + std::string name; + }; + auto table = make_table("users", make_column("id", &User::id), make_column("name", &User::name)); + using storage_impl_t = internal::storage_impl; + auto storageImpl = storage_impl_t{table}; + using context_t = internal::serializator_context; + context_t context{storageImpl}; + auto index = make_index("id_index", &User::id); + auto value = internal::serialize(index, context); + REQUIRE(value == "CREATE INDEX IF NOT EXISTS 'id_index' ON 'users' ('id')"); +} diff --git a/tests/statement_serializator_tests/indexed_column.cpp b/tests/statement_serializator_tests/indexed_column.cpp new file mode 100644 index 000000000..3736056ac --- /dev/null +++ b/tests/statement_serializator_tests/indexed_column.cpp @@ -0,0 +1,46 @@ +#include +#include + +using namespace sqlite_orm; + +TEST_CASE("statement_serializator indexed_column") { + struct User { + int id = 0; + std::string name; + }; + auto table = make_table("users", make_column("id", &User::id), make_column("name", &User::name)); + using storage_impl_t = internal::storage_impl; + auto storageImpl = storage_impl_t{table}; + using context_t = internal::serializator_context; + context_t context{storageImpl}; + { + auto column = indexed_column(&User::id); + auto value = internal::serialize(column, context); + REQUIRE(value == "\"id\""); + } + { + auto column = indexed_column(&User::id).asc(); + auto value = internal::serialize(column, context); + REQUIRE(value == "\"id\" ASC"); + } + { + auto column = indexed_column(&User::id).desc(); + auto value = internal::serialize(column, context); + REQUIRE(value == "\"id\" DESC"); + } + { + auto column = indexed_column(&User::id).collate("BINARY"); + auto value = internal::serialize(column, context); + REQUIRE(value == "\"id\" COLLATE BINARY"); + } + { + auto column = indexed_column(&User::name).collate("BINARY").asc(); + auto value = internal::serialize(column, context); + REQUIRE(value == "\"name\" COLLATE BINARY ASC"); + } + { + auto column = indexed_column(&User::name).collate("OTHER").desc(); + auto value = internal::serialize(column, context); + REQUIRE(value == "\"name\" COLLATE OTHER DESC"); + } +} From 65bd71dc6f3601766b23f56eb4444ca05570e5fa Mon Sep 17 00:00:00 2001 From: fnc12 Date: Thu, 6 Aug 2020 21:19:09 +0300 Subject: [PATCH 2/2] added a comment --- dev/indexed_column.h | 4 ++++ dev/statement_serializator.h | 2 +- include/sqlite_orm/sqlite_orm.h | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dev/indexed_column.h b/dev/indexed_column.h index 9b2e710a2..7b19e3b43 100644 --- a/dev/indexed_column.h +++ b/dev/indexed_column.h @@ -59,6 +59,10 @@ namespace sqlite_orm { } + /** + * Use this function to specify indexed column inside `make_index` function call. + * Example: make_index("index_name", indexed_column(&User::id).asc()) + */ template internal::indexed_column_t indexed_column(C column_or_expression) { return {std::move(column_or_expression)}; diff --git a/dev/statement_serializator.h b/dev/statement_serializator.h index aaf193944..651ba07e5 100644 --- a/dev/statement_serializator.h +++ b/dev/statement_serializator.h @@ -1346,7 +1346,7 @@ namespace sqlite_orm { ss << " ASC"; break; default: - throw std::system_error(std::make_error_code(orm_error_code::column_not_found)); + throw std::system_error(std::make_error_code(orm_error_code::incorrect_order)); } } return ss.str(); diff --git a/include/sqlite_orm/sqlite_orm.h b/include/sqlite_orm/sqlite_orm.h index e602779c4..2047bc048 100644 --- a/include/sqlite_orm/sqlite_orm.h +++ b/include/sqlite_orm/sqlite_orm.h @@ -10818,7 +10818,7 @@ namespace sqlite_orm { ss << " ASC"; break; default: - throw std::system_error(std::make_error_code(orm_error_code::column_not_found)); + throw std::system_error(std::make_error_code(orm_error_code::incorrect_order)); } } return ss.str();