diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..643b678 --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +--- +Language: Cpp +BasedOnStyle: Google +Standard: c++11 +ColumnLimit: 120 +IndentWidth: 4 +ContinuationIndentWidth: 4 +TabWidth: 4 +UseTab: Never +AccessModifierOffset: -4 +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +BreakBeforeBraces: Attach +DerivePointerAlignment: false +PointerAlignment: Left +SortIncludes: CaseSensitive +IncludeBlocks: Regroup +NamespaceIndentation: None +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..9124350 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,31 @@ +--- +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-throwing-static-initialization, + clang-analyzer-*, + performance-*, + -performance-enum-size, + -performance-no-int-to-ptr, + -performance-unnecessary-value-param, + portability-*, + -portability-avoid-pragma-once, + readability-braces-around-statements, + readability-const-return-type, + readability-container-size-empty, + readability-duplicate-include, + readability-misleading-indentation, + readability-redundant-member-init, + readability-redundant-string-cstr, + readability-redundant-string-init, + readability-simplify-boolean-expr, + readability-static-accessed-through-instance +WarningsAsErrors: '*' +HeaderFilterRegex: '(^|.*/)(include|src|tests)/.*' +ExcludeHeaderFilterRegex: '(^|.*/)(src/internal/(date|sqlite3)\.h)' +FormatStyle: file +CheckOptions: + - key: readability-braces-around-statements.ShortStatementLines + value: '1' +... diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..eb44539 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +## Code style + +This project uses LLVM tooling for formatting and lightweight linting: + +- `clang-format` reads `.clang-format`. +- `clang-tidy` reads `.clang-tidy`. + +The formatting style is based on Google C++ style with a 4-space indent and a +120-column limit. New and modified C++ code should follow the local naming +patterns already used by the public API: + +- types and public API classes: `PascalCase` +- public member functions: `PascalCase` +- free functions used as internal helpers: `PascalCase` when matching existing + code, otherwise descriptive lower-case names are preferred for new private + helpers +- variables, data members, and parameters: `snake_case` +- private data members: trailing underscore, for example `db_` +- constants and enum values: existing project style, for example `kText` +- macros: `UPPER_SNAKE_CASE` + +Do not reformat vendored third-party sources: + +- `src/sqlite3.c` +- `src/internal/sqlite3.h` +- `src/internal/date.h` + +## Formatting changed files + +Run `clang-format` only on project-owned C++ files that you changed: + +```console +clang-format -i include/database.h src/database.cc tests/database_test.cc +``` + +To inspect formatting without modifying files: + +```console +clang-format --dry-run --Werror include/database.h src/database.cc tests/database_test.cc +``` + +## Linting + +After configuring CMake with compile commands enabled, run `clang-tidy` on the +files you changed: + +```console +cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +clang-tidy src/database.cc -p build +``` + +Keep lint fixes focused on the code touched by the current change. Avoid broad +style-only rewrites unless the change is explicitly scoped as a formatting PR. diff --git a/README.md b/README.md index 114989d..4a1fb50 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ A C++ wrapper for SQLite that provides compile-time checked CRUD operations for - [Raw SQL](#raw-sql) - [Build instructions](#build-instructions) - [Dependencies](#dependencies) +- [Contributing](#contributing) ## Motivation @@ -391,3 +392,8 @@ You can also open the generated `build/sqlite-cpp-reflection.sln` in Visual Stud - A C++11-compatible compiler - GoogleTest for tests, fetched by CMake - SQLite, vendored in `src/sqlite3.c` and `src/internal/sqlite3.h` + +## Contributing + +Formatting and linting rules are documented in [CONTRIBUTING.md](CONTRIBUTING.md). The repository includes +`.clang-format` and `.clang-tidy` so contributors can format and lint changed C++ files consistently. diff --git a/include/database.h b/include/database.h index d25e543..19d02f7 100644 --- a/include/database.h +++ b/include/database.h @@ -27,230 +27,229 @@ #include #include -#include "reflection.h" #include "fetch_query_results.h" -#include "query_predicates.h" #include "queries.h" +#include "query_predicates.h" +#include "reflection.h" struct sqlite3; namespace sqlite_reflection { - /// A wrapper of an SQLite database, enabling type-safe and compile-time CRUD operations, - /// encapsulating the C-based API of the underlying SQLite engine - class REFLECTION_EXPORT Database - { - public: - /// Initializes the SQLite database singleton at a given file path. - /// This function should be called before any operation is performed on the database. - /// During initialization all reflectable structs/records are registered and their corresponding tables are created in the database. - /// If the path is empty, an in-memory database is created. - static void Initialize(const std::string& path = ""); - - /// This should, ideally, be called before the program finishes execution, so that - /// the database connection is closed. - static void Finalize(); - - /// Retrieves the database singleton wrapper for further operations - static const Database& Instance(); - - Database(Database const&) = delete; - Database(Database&&) = delete; - Database& operator=(const Database&) = delete; - Database& operator=(Database&&) = delete; - - /// Retrieves all entries of a given record from the database. - /// This corresponds to a SELECT query in the SQL syntax - template - std::vector FetchAll() const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - EmptyPredicate empty; - const auto& query_result = Fetch(record, &empty); - return Hydrate(query_result, record); - } - - /// Retrieves all entries of a given record from the database, which match a given predicate. - /// This corresponds to a SELECT query in the SQL syntax - template - std::vector Fetch(const QueryPredicateBase* predicate) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - const auto& query_result = Fetch(record, predicate); - return Hydrate(query_result, record); - } - - /// Retrieves a single entry of a given record from the database, which matches a given id. - /// This corresponds to a SELECT query in the SQL syntax - template - T Fetch(int64_t id) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - Equal equal_id_condition(&T::id, id); - const auto& query_result = Fetch(record, &equal_id_condition); - if (query_result.row_values.size() != 1) { - throw std::runtime_error("No record with this id found"); - } - return Hydrate(query_result, record)[0]; - } - - /// Retrieves the max id of a given record from the database - /// This corresponds to SELECT MAX(id) FROM TABLE in the SQL syntax - template - int64_t GetMaxId() const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - FetchMaxIdQuery query(db_, record); - const auto max_id = query.GetMaxId(); - return max_id; - } - - /// Saves a given record in the database. - /// This corresponds to an INSERT query in the SQL syntax - template - void Save(const T& model) const { - Save(model, false); - } - - /// Saves a given record in the database and auto-increments its id. - /// This corresponds to an INSERT query in the SQL syntax - template - void SaveAutoIncrement(const T& model) const { - Save(model, true); +/// A wrapper of an SQLite database, enabling type-safe and compile-time CRUD operations, +/// encapsulating the C-based API of the underlying SQLite engine +class REFLECTION_EXPORT Database { +public: + /// Initializes the SQLite database singleton at a given file path. + /// This function should be called before any operation is performed on the database. + /// During initialization all reflectable structs/records are registered and their corresponding tables are created + /// in the database. If the path is empty, an in-memory database is created. + static void Initialize(const std::string& path = ""); + + /// This should, ideally, be called before the program finishes execution, so that + /// the database connection is closed. + static void Finalize(); + + /// Retrieves the database singleton wrapper for further operations + static const Database& Instance(); + + Database(Database const&) = delete; + Database(Database&&) = delete; + Database& operator=(const Database&) = delete; + Database& operator=(Database&&) = delete; + + /// Retrieves all entries of a given record from the database. + /// This corresponds to a SELECT query in the SQL syntax + template + std::vector FetchAll() const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + EmptyPredicate empty; + const auto& query_result = Fetch(record, &empty); + return Hydrate(query_result, record); + } + + /// Retrieves all entries of a given record from the database, which match a given predicate. + /// This corresponds to a SELECT query in the SQL syntax + template + std::vector Fetch(const QueryPredicateBase* predicate) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + const auto& query_result = Fetch(record, predicate); + return Hydrate(query_result, record); + } + + /// Retrieves a single entry of a given record from the database, which matches a given id. + /// This corresponds to a SELECT query in the SQL syntax + template + T Fetch(int64_t id) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + Equal equal_id_condition(&T::id, id); + const auto& query_result = Fetch(record, &equal_id_condition); + if (query_result.row_values.size() != 1) { + throw std::runtime_error("No record with this id found"); } - - /// Saves multiple records iteratively in the database. - /// This corresponds to an INSERT query in the SQL syntax - template - void Save(const std::vector& models) const { - Save(models, false); - } - - /// Saves multiple records iteratively in the database and auto-increments their ids. - /// This corresponds to an INSERT query in the SQL syntax - template - void SaveAutoIncrement(const std::vector& models) const { - Save(models, true); + return Hydrate(query_result, record)[0]; + } + + /// Retrieves the max id of a given record from the database + /// This corresponds to SELECT MAX(id) FROM TABLE in the SQL syntax + template + int64_t GetMaxId() const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + FetchMaxIdQuery query(db_, record); + const auto max_id = query.GetMaxId(); + return max_id; + } + + /// Saves a given record in the database. + /// This corresponds to an INSERT query in the SQL syntax + template + void Save(const T& model) const { + Save(model, false); + } + + /// Saves a given record in the database and auto-increments its id. + /// This corresponds to an INSERT query in the SQL syntax + template + void SaveAutoIncrement(const T& model) const { + Save(model, true); + } + + /// Saves multiple records iteratively in the database. + /// This corresponds to an INSERT query in the SQL syntax + template + void Save(const std::vector& models) const { + Save(models, false); + } + + /// Saves multiple records iteratively in the database and auto-increments their ids. + /// This corresponds to an INSERT query in the SQL syntax + template + void SaveAutoIncrement(const std::vector& models) const { + Save(models, true); + } + + /// Updates a given record in the database. + /// This corresponds to an UPDATE query in the SQL syntax + template + void Update(const T& model) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + Update((void*)&model, record); + } + + /// Updates multiple records iteratively in the database. + /// This corresponds to an UPDATE query in the SQL syntax + template + void Update(const std::vector& models) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + for (const auto& model : models) { + Update((void*)&model, record); } - - /// Updates a given record in the database. - /// This corresponds to an UPDATE query in the SQL syntax - template - void Update(const T& model) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - Update((void*)&model, record); - } - - /// Updates multiple records iteratively in the database. - /// This corresponds to an UPDATE query in the SQL syntax - template - void Update(const std::vector& models) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - for (const auto& model : models) { - Update((void*)&model, record); - } - } - - /// Deletes a given record from the database. - /// This corresponds to an DELETE query in the SQL syntax - template - void Delete(const T& model) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - const auto equal_id_predicate = Equal(&T::id, model.id); - Delete(record, &equal_id_predicate); - } - - /// Deletes a given record from the database, which matches a given id. - /// This corresponds to an DELETE query in the SQL syntax - template - void Delete(int64_t id) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - const auto equal_id_predicate = Equal(&T::id, id); - Delete(record, &equal_id_predicate); - } - - /// Deletes multiple records of a given type from the database, which match a given predicate. - /// This corresponds to an DELETE query in the SQL syntax, with an additional WHERE clause - template - void Delete(const QueryPredicateBase* predicate) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - Delete(record, predicate); + } + + /// Deletes a given record from the database. + /// This corresponds to an DELETE query in the SQL syntax + template + void Delete(const T& model) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + const auto equal_id_predicate = Equal(&T::id, model.id); + Delete(record, &equal_id_predicate); + } + + /// Deletes a given record from the database, which matches a given id. + /// This corresponds to an DELETE query in the SQL syntax + template + void Delete(int64_t id) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + const auto equal_id_predicate = Equal(&T::id, id); + Delete(record, &equal_id_predicate); + } + + /// Deletes multiple records of a given type from the database, which match a given predicate. + /// This corresponds to an DELETE query in the SQL syntax, with an additional WHERE clause + template + void Delete(const QueryPredicateBase* predicate) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + Delete(record, predicate); + } + + /// Executes a raw SQL query. A trailing semicolon is added if needed. + /// Prefer the type-safe CRUD APIs for user-provided values. + void UnsafeSql(const std::string& raw_sql_query) const; + +private: + explicit Database(const char* path); + + /// Executes a fetch query (SELECT) for a given record with a given predicate, + /// and returns the results in a textual representation + FetchQueryResults Fetch(const Reflection& record, const QueryPredicateBase* predicate) const; + + /// Returns a record type from its type information, retrieved from typeid(...).name() + static const Reflection& GetRecord(const std::string& type_id); + + /// Creates concrete record types with initialized members, + /// based on the textual representation of results from a fetch query + template + std::vector Hydrate(const FetchQueryResults& query_results, const Reflection& record) const { + std::vector models; + for (auto i = 0; i < query_results.row_values.size(); i++) { + T model; + FetchRecordsQuery::Hydrate((void*)&model, query_results, record, i); + models.emplace_back(model); } - - /// Executes a raw SQL query. A trailing semicolon is added if needed. - /// Prefer the type-safe CRUD APIs for user-provided values. - void UnsafeSql(const std::string& raw_sql_query) const; - - private: - explicit Database(const char* path); - - /// Executes a fetch query (SELECT) for a given record with a given predicate, - /// and returns the results in a textual representation - FetchQueryResults Fetch(const Reflection& record, const QueryPredicateBase* predicate) const; - - /// Returns a record type from its type information, retrieved from typeid(...).name() - static const Reflection& GetRecord(const std::string& type_id); - - /// Creates concrete record types with initialized members, - /// based on the textual representation of results from a fetch query - template - std::vector Hydrate(const FetchQueryResults& query_results, const Reflection& record) const { - std::vector models; - for (auto i = 0; i < query_results.row_values.size(); i++) { - T model; - FetchRecordsQuery::Hydrate((void*)&model, query_results, record, i); - models.emplace_back(model); - } - return models; - } - - /// Saves a given record in the database. - /// This corresponds to an INSERT query in the SQL syntax - template - void Save(const T& model, bool auto_increment_id) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - T saved_model(model); - if (auto_increment_id) { - const auto current_max_id = GetMaxId(); - saved_model.id = current_max_id + 1; - } - Save((void*)&saved_model, record); + return models; + } + + /// Saves a given record in the database. + /// This corresponds to an INSERT query in the SQL syntax + template + void Save(const T& model, bool auto_increment_id) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + T saved_model(model); + if (auto_increment_id) { + const auto current_max_id = GetMaxId(); + saved_model.id = current_max_id + 1; } - - /// Saves multiple records iteratively in the database. - /// This corresponds to an INSERT query in the SQL syntax - template - void Save(const std::vector& models, bool auto_increment_id) const { - const auto type_id = typeid(T).name(); - const auto& record = GetRecord(type_id); - const auto current_max_id = auto_increment_id ? GetMaxId() : 0; - for (auto i = 0; i < models.size(); ++i) { - const auto& model = models[i]; - if (auto_increment_id) { - T saved_model(model); - saved_model.id = current_max_id + i + 1; - Save((void*)&saved_model, record); - } else { - Save((void*)&model, record); - } + Save((void*)&saved_model, record); + } + + /// Saves multiple records iteratively in the database. + /// This corresponds to an INSERT query in the SQL syntax + template + void Save(const std::vector& models, bool auto_increment_id) const { + const auto type_id = typeid(T).name(); + const auto& record = GetRecord(type_id); + const auto current_max_id = auto_increment_id ? GetMaxId() : 0; + for (auto i = 0; i < models.size(); ++i) { + const auto& model = models[i]; + if (auto_increment_id) { + T saved_model(model); + saved_model.id = current_max_id + i + 1; + Save((void*)&saved_model, record); + } else { + Save((void*)&model, record); } } + } - /// Saves a single record in the database - void Save(void* p, const Reflection& record) const; + /// Saves a single record in the database + void Save(void* p, const Reflection& record) const; - /// Updates a single record in the database - void Update(void* p, const Reflection& record) const; + /// Updates a single record in the database + void Update(void* p, const Reflection& record) const; - /// Deletes a single record from the database - void Delete(const Reflection& record, const QueryPredicateBase* predicate) const; + /// Deletes a single record from the database + void Delete(const Reflection& record, const QueryPredicateBase* predicate) const; - static Database* instance_; - sqlite3* db_; - }; -} + static Database* instance_; + sqlite3* db_; +}; +} // namespace sqlite_reflection diff --git a/include/fetch_query_results.h b/include/fetch_query_results.h index 803a17c..c932e52 100644 --- a/include/fetch_query_results.h +++ b/include/fetch_query_results.h @@ -25,10 +25,9 @@ #include namespace sqlite_reflection { - /// A wrapper of the results of an SQLite SELECT query - struct FetchQueryResults - { - std::vector column_names; - std::vector> row_values; - }; -} +/// A wrapper of the results of an SQLite SELECT query +struct FetchQueryResults { + std::vector column_names; + std::vector> row_values; +}; +} // namespace sqlite_reflection diff --git a/include/queries.h b/include/queries.h index 1198765..c53feed 100644 --- a/include/queries.h +++ b/include/queries.h @@ -25,163 +25,154 @@ #include #include -#include "reflection.h" #include "query_predicates.h" +#include "reflection.h" struct sqlite3; struct sqlite3_stmt; namespace sqlite_reflection { - /// A wrapper of all SQLite queries, encapsulating the preparation and - /// execution of queries against the SQLite database - class REFLECTION_EXPORT Query - { - public: - virtual ~Query() = default; - - protected: - Query(sqlite3* db, const Reflection& record); - - /// A textual representation of the given query, in SQL language - virtual std::string PrepareSql() const = 0; - - /// Returns a comma-separated string of all struct member names - std::string JoinedRecordColumnNames() const; - - /// Returns all struct member names in textual format - std::vector GetRecordColumnNames() const; - - /// Hook for customization of a given table column name, used primarily - /// for marking the column corresponding to id as PRIMARY KEY - virtual std::string CustomizedColumnName(size_t index) const; - - sqlite3* db_; - const Reflection& record_; - }; - - /// A query for which no results are expected, such as - /// CREATE TABLE - /// UPDATE - /// INSERT - /// DELETE - class REFLECTION_EXPORT ExecutionQuery : public Query - { - public: - ~ExecutionQuery() override = default; - explicit ExecutionQuery(sqlite3* db, const Reflection& record); - void Execute() const; - - protected: - virtual std::vector Bindings() const; - std::vector GetValues(void* p) const; - }; - - /// A query for which direct SQL prompts are used - class REFLECTION_EXPORT SqlQuery : public ExecutionQuery - { - public: - ~SqlQuery() override = default; - explicit SqlQuery(sqlite3* db, const std::string& sql); - - protected: - std::string PrepareSql() const override; - std::string sql_; - }; - - /// A query for creating a table for a given reflectable struct. When the database - /// is initialized, a table for every registered reflectable struct is created - /// This maps to CREATE TABLE IF NOT EXISTS in SQL - class REFLECTION_EXPORT CreateTableQuery final : public ExecutionQuery - { - public: - ~CreateTableQuery() override = default; - explicit CreateTableQuery(sqlite3* db, const Reflection& record); - - protected: - std::string PrepareSql() const override; - std::string CustomizedColumnName(size_t index) const override; - }; - - /// A query to delete a given record from the database, by means of its id - /// This maps to DELETE in SQL - class REFLECTION_EXPORT DeleteQuery final : public ExecutionQuery - { - public: - ~DeleteQuery() override = default; - explicit DeleteQuery(sqlite3* db, const Reflection& record, const QueryPredicateBase* predicate); - - protected: - std::string PrepareSql() const override; - std::vector Bindings() const override; - const QueryPredicateBase* predicate_; - }; - - /// A query to insert a given record to the database, by supplying a given type-erased struct instance - /// This maps to INSERT INTO in SQL - class REFLECTION_EXPORT InsertQuery final : public ExecutionQuery - { - public: - ~InsertQuery() override = default; - explicit InsertQuery(sqlite3* db, const Reflection& record, void* p); - - protected: - std::string PrepareSql() const override; - std::vector Bindings() const override; - void* p_; - }; - - /// A query to update a given record to the database, by supplying a given type-erased struct instance - /// This maps to UPDATE in SQL - class REFLECTION_EXPORT UpdateQuery final : public ExecutionQuery - { - public: - ~UpdateQuery() override = default; - explicit UpdateQuery(sqlite3* db, const Reflection& record, void* p); - - protected: - std::string PrepareSql() const override; - std::vector Bindings() const override; - void* p_; - }; - - /// A query for retrieving the max id of a given record from the database - class REFLECTION_EXPORT FetchMaxIdQuery final : public Query - { - public: - explicit FetchMaxIdQuery(sqlite3* db, const Reflection& record); - ~FetchMaxIdQuery() override; - - /// Retrieve the max id currently used for the given record type - int64_t GetMaxId(); - - protected: - std::string PrepareSql() const override; - sqlite3_stmt* stmt_; - }; - - struct FetchQueryResults; - - /// A query for retrieving all records from the database, which match a given predicate condition - /// This maps to SELECT * in SQL - class REFLECTION_EXPORT FetchRecordsQuery final : public Query - { - public: - explicit FetchRecordsQuery(sqlite3* db, const Reflection& record, const QueryPredicateBase* predicate); - ~FetchRecordsQuery() override; - - /// Returns a textual representation of the results of the query - FetchQueryResults GetResults(); - - /// Reconstructs all record member values based on their concrete type, - /// based on the textual representation of the corresponding row result - /// of the fetch query - static void Hydrate(void* p, const FetchQueryResults& query_results, const Reflection& record, size_t i); - - protected: - std::string PrepareSql() const override; - std::wstring GetColumnValue(int col) const; - - sqlite3_stmt* stmt_; - const QueryPredicateBase* predicate_; - }; -} +/// A wrapper of all SQLite queries, encapsulating the preparation and +/// execution of queries against the SQLite database +class REFLECTION_EXPORT Query { +public: + virtual ~Query() = default; + +protected: + Query(sqlite3* db, const Reflection& record); + + /// A textual representation of the given query, in SQL language + virtual std::string PrepareSql() const = 0; + + /// Returns a comma-separated string of all struct member names + std::string JoinedRecordColumnNames() const; + + /// Returns all struct member names in textual format + std::vector GetRecordColumnNames() const; + + /// Hook for customization of a given table column name, used primarily + /// for marking the column corresponding to id as PRIMARY KEY + virtual std::string CustomizedColumnName(size_t index) const; + + sqlite3* db_; + const Reflection& record_; +}; + +/// A query for which no results are expected, such as +/// CREATE TABLE +/// UPDATE +/// INSERT +/// DELETE +class REFLECTION_EXPORT ExecutionQuery : public Query { +public: + ~ExecutionQuery() override = default; + explicit ExecutionQuery(sqlite3* db, const Reflection& record); + void Execute() const; + +protected: + virtual std::vector Bindings() const; + std::vector GetValues(void* p) const; +}; + +/// A query for which direct SQL prompts are used +class REFLECTION_EXPORT SqlQuery : public ExecutionQuery { +public: + ~SqlQuery() override = default; + explicit SqlQuery(sqlite3* db, const std::string& sql); + +protected: + std::string PrepareSql() const override; + std::string sql_; +}; + +/// A query for creating a table for a given reflectable struct. When the database +/// is initialized, a table for every registered reflectable struct is created +/// This maps to CREATE TABLE IF NOT EXISTS in SQL +class REFLECTION_EXPORT CreateTableQuery final : public ExecutionQuery { +public: + ~CreateTableQuery() override = default; + explicit CreateTableQuery(sqlite3* db, const Reflection& record); + +protected: + std::string PrepareSql() const override; + std::string CustomizedColumnName(size_t index) const override; +}; + +/// A query to delete a given record from the database, by means of its id +/// This maps to DELETE in SQL +class REFLECTION_EXPORT DeleteQuery final : public ExecutionQuery { +public: + ~DeleteQuery() override = default; + explicit DeleteQuery(sqlite3* db, const Reflection& record, const QueryPredicateBase* predicate); + +protected: + std::string PrepareSql() const override; + std::vector Bindings() const override; + const QueryPredicateBase* predicate_; +}; + +/// A query to insert a given record to the database, by supplying a given type-erased struct instance +/// This maps to INSERT INTO in SQL +class REFLECTION_EXPORT InsertQuery final : public ExecutionQuery { +public: + ~InsertQuery() override = default; + explicit InsertQuery(sqlite3* db, const Reflection& record, void* p); + +protected: + std::string PrepareSql() const override; + std::vector Bindings() const override; + void* p_; +}; + +/// A query to update a given record to the database, by supplying a given type-erased struct instance +/// This maps to UPDATE in SQL +class REFLECTION_EXPORT UpdateQuery final : public ExecutionQuery { +public: + ~UpdateQuery() override = default; + explicit UpdateQuery(sqlite3* db, const Reflection& record, void* p); + +protected: + std::string PrepareSql() const override; + std::vector Bindings() const override; + void* p_; +}; + +/// A query for retrieving the max id of a given record from the database +class REFLECTION_EXPORT FetchMaxIdQuery final : public Query { +public: + explicit FetchMaxIdQuery(sqlite3* db, const Reflection& record); + ~FetchMaxIdQuery() override; + + /// Retrieve the max id currently used for the given record type + int64_t GetMaxId(); + +protected: + std::string PrepareSql() const override; + sqlite3_stmt* stmt_; +}; + +struct FetchQueryResults; + +/// A query for retrieving all records from the database, which match a given predicate condition +/// This maps to SELECT * in SQL +class REFLECTION_EXPORT FetchRecordsQuery final : public Query { +public: + explicit FetchRecordsQuery(sqlite3* db, const Reflection& record, const QueryPredicateBase* predicate); + ~FetchRecordsQuery() override; + + /// Returns a textual representation of the results of the query + FetchQueryResults GetResults(); + + /// Reconstructs all record member values based on their concrete type, + /// based on the textual representation of the corresponding row result + /// of the fetch query + static void Hydrate(void* p, const FetchQueryResults& query_results, const Reflection& record, size_t i); + +protected: + std::string PrepareSql() const override; + std::wstring GetColumnValue(int col) const; + + sqlite3_stmt* stmt_; + const QueryPredicateBase* predicate_; +}; +} // namespace sqlite_reflection diff --git a/include/query_predicates.h b/include/query_predicates.h index 30dc1c2..026616a 100644 --- a/include/query_predicates.h +++ b/include/query_predicates.h @@ -23,271 +23,235 @@ #pragma once #include -#include #include +#include #include #include "reflection.h" namespace sqlite_reflection { - class AndPredicate; - class OrPredicate; - - struct REFLECTION_EXPORT SqlValue - { - SqlValue(); - - SqliteStorageClass storage_class; - int64_t int_value; - bool bool_value; - double real_value; - std::string text_value; - }; - - /// The base class of all WHERE predicates used in SQLite queries - class REFLECTION_EXPORT QueryPredicateBase - { - public: - virtual ~QueryPredicateBase() = default; - - /// Returns a textual representation of the predicate with placeholders for bound values - virtual std::string Evaluate() const = 0; - - /// Returns values that need to be bound to the predicate placeholders - virtual std::vector Bindings() const = 0; - - /// Creates a clone for compounding predicates - virtual QueryPredicateBase* Clone() const = 0; - - /// Returns a compound predicate, which requires that both the current - /// and the other predicate are valid at the same time - AndPredicate And(const QueryPredicateBase& other) const; - - /// Returns a compound predicate, which requires that either the current - /// or the other predicate are valid - OrPredicate Or(const QueryPredicateBase& other) const; - }; - - /// A wrapper of a query predicate, which can be constructed from - /// a pointer-to-member function of a reflectable struct, thus enabling - /// type safety and compile-time guarantees, that the query is indeed valid - class REFLECTION_EXPORT QueryPredicate : public QueryPredicateBase - { - public: - std::string Evaluate() const override; - std::vector Bindings() const override; - QueryPredicateBase* Clone() const override; - - protected: - template - QueryPredicate(R T::* fn, R value, const std::string& symbol, std::function value_retrieval) - : symbol_(symbol) - { - auto record = GetRecordFromTypeId(typeid(T).name()); - auto offset = OffsetFromStart(fn); - for (auto i = 0; i < record.member_metadata.size(); ++i) { - if (record.member_metadata[i].offset == offset) { - member_name_ = record.member_metadata[i].name; - value_ = value_retrieval((void*)&value, record.member_metadata[i].storage_class); - break; - } - } - } - - template - QueryPredicate(R T::* fn, R value, const std::string& symbol) - : QueryPredicate(fn, value, symbol, [&](void* v, SqliteStorageClass storage_class){ - return GetSqlValue(v, storage_class); - }) {} - - QueryPredicate(const std::string& symbol, const std::string& member_name, const SqlValue& value) - : symbol_(symbol), member_name_(member_name), value_(value) {} - - /// Returns the value used for the current query, against which the struct member - /// (defined from the pointer-to-member function) will be compared. The value needs - /// to be type-erased, so that the header file is not bloated with unnecessary implementation details - virtual SqlValue GetSqlValue(void* v, SqliteStorageClass storage_class) const; - - /// The symbol used for the comparison, for example "=" for equality - std::string symbol_; - - /// The name of the compared member, as defined in source code, used to construct the - /// textual representation of the evaluation string - std::string member_name_; - - /// The comparison value that will be bound to the generated SQL placeholder - SqlValue value_; - }; - - /// A wrapper for an empty predicate, used to fetch all elements of an SQLite table - class REFLECTION_EXPORT EmptyPredicate final : public QueryPredicateBase - { - public: - std::string Evaluate() const override; - std::vector Bindings() const override; - QueryPredicateBase* Clone() const override; - }; - - /// A wrapper for an equality predicate, for which the value of the - /// struct member is required to be equal to a given control value - class REFLECTION_EXPORT Equal final : public QueryPredicate - { - public: - template - explicit Equal(R T::* fn, R value) - : QueryPredicate(fn, value, "=") {} - - template - explicit Equal(int64_t T::* fn, int value) - : Equal(fn, (int64_t)value) {} - - template - explicit Equal(std::wstring T::* fn, const wchar_t* value) - : Equal(fn, std::wstring(value)) {} - }; - - /// A wrapper for an inequality predicate, for which the value of the - /// struct member is required to be unequal to a given control value - class REFLECTION_EXPORT Unequal final : public QueryPredicate - { - public: - template - explicit Unequal(R T::* fn, R value) - : QueryPredicate(fn, value, "!=") {} - - template - explicit Unequal(int64_t T::* fn, int value) - : Unequal(fn, (int64_t)value) {} - - template - explicit Unequal(std::wstring T::* fn, const wchar_t* value) - : Unequal(fn, std::wstring(value)) {} - }; - - /// A wrapper for a similarity predicate, for which the value of the - /// struct member is required to be similar to a given control value - class REFLECTION_EXPORT Like final : public QueryPredicate - { - public: - template - explicit Like(R T::* fn, R value) - : QueryPredicate(fn, value, "LIKE", [&](void* v, SqliteStorageClass storage_class){ - return GetSqlValue(v, storage_class); - }) {} - - template - explicit Like(int64_t T::* fn, int value) - : Like(fn, (int64_t)value) {} - - template - explicit Like(std::wstring T::* fn, const wchar_t* value) - : Like(fn, std::wstring(value)) {} - - protected: - SqlValue GetSqlValue(void* v, SqliteStorageClass storage_class) const override; - }; - - /// A wrapper for a comparison predicate, for which the value of the - /// struct member is required to be greater than a given control value - class REFLECTION_EXPORT GreaterThan final : public QueryPredicate - { - public: - template - explicit GreaterThan(int64_t T::* fn, int64_t value) - : QueryPredicate(fn, value, ">") {} - - template - explicit GreaterThan(int64_t T::* fn, int value) - : QueryPredicate(fn, (int64_t)value, ">") {} - - template - explicit GreaterThan(double T::* fn, double value) - : QueryPredicate(fn, value, ">") {} - }; - - /// A wrapper for a comparison predicate, for which the value of the - /// struct member is required to be greater than or equal to a given control value - class REFLECTION_EXPORT GreaterThanOrEqual final : public QueryPredicate - { - public: - template - explicit GreaterThanOrEqual(int64_t T::* fn, int64_t value) - : QueryPredicate(fn, value, ">=") {} - - template - explicit GreaterThanOrEqual(int64_t T::* fn, int value) - : QueryPredicate(fn, (int64_t)value, ">=") {} - - template - explicit GreaterThanOrEqual(double T::* fn, double value) - : QueryPredicate(fn, value, ">=") {} - }; - - /// A wrapper for a comparison predicate, for which the value of the - /// struct member is required to be smaller than a given control value - class REFLECTION_EXPORT SmallerThan final : public QueryPredicate - { - public: - template - explicit SmallerThan(int64_t T::* fn, int64_t value) - : QueryPredicate(fn, value, "<") {} - - template - explicit SmallerThan(int64_t T::* fn, int value) - : QueryPredicate(fn, (int64_t)value, "<") {} - - template - explicit SmallerThan(double T::* fn, double value) - : QueryPredicate(fn, value, "<") {} - }; - - /// A wrapper for a comparison predicate, for which the value of the - /// struct member is required to be smaller than or equal to a given control value - class REFLECTION_EXPORT SmallerThanOrEqual final : public QueryPredicate - { - public: - template - explicit SmallerThanOrEqual(int64_t T::* fn, int64_t value) - : QueryPredicate(fn, value, "<=") {} - - template - explicit SmallerThanOrEqual(int64_t T::* fn, int value) - : QueryPredicate(fn, (int64_t)value, "<=") {} - - template - explicit SmallerThanOrEqual(double T::* fn, double value) - : QueryPredicate(fn, value, "<=") {} - }; - - /// A wrapper of a compound predicate, which combines two other predicates, - /// allowing the construction of more complex predicates from elementary predicates - class REFLECTION_EXPORT BinaryPredicate : public QueryPredicateBase - { - public: - std::string Evaluate() const override; - std::vector Bindings() const override; - - protected: - BinaryPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right, const std::string& symbol); - - std::unique_ptr left_; - std::unique_ptr right_; - std::string symbol_; - }; - - /// A compound predicate, which requires that both predicates are valid at the same time - class REFLECTION_EXPORT AndPredicate final : public BinaryPredicate - { - public: - AndPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right); - QueryPredicateBase* Clone() const override; - }; - - /// A compound predicate, which requires that either predicate is valid - class REFLECTION_EXPORT OrPredicate final : public BinaryPredicate - { - public: - OrPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right); - QueryPredicateBase* Clone() const override; - }; -} +class AndPredicate; +class OrPredicate; + +struct REFLECTION_EXPORT SqlValue { + SqlValue(); + + SqliteStorageClass storage_class; + int64_t int_value; + bool bool_value; + double real_value; + std::string text_value; +}; + +/// The base class of all WHERE predicates used in SQLite queries +class REFLECTION_EXPORT QueryPredicateBase { +public: + virtual ~QueryPredicateBase() = default; + + /// Returns a textual representation of the predicate with placeholders for bound values + virtual std::string Evaluate() const = 0; + + /// Returns values that need to be bound to the predicate placeholders + virtual std::vector Bindings() const = 0; + + /// Creates a clone for compounding predicates + virtual QueryPredicateBase* Clone() const = 0; + + /// Returns a compound predicate, which requires that both the current + /// and the other predicate are valid at the same time + AndPredicate And(const QueryPredicateBase& other) const; + + /// Returns a compound predicate, which requires that either the current + /// or the other predicate are valid + OrPredicate Or(const QueryPredicateBase& other) const; +}; + +/// A wrapper of a query predicate, which can be constructed from +/// a pointer-to-member function of a reflectable struct, thus enabling +/// type safety and compile-time guarantees, that the query is indeed valid +class REFLECTION_EXPORT QueryPredicate : public QueryPredicateBase { +public: + std::string Evaluate() const override; + std::vector Bindings() const override; + QueryPredicateBase* Clone() const override; + +protected: + template + QueryPredicate(R T::* fn, R value, const std::string& symbol, + std::function value_retrieval) + : symbol_(symbol) { + auto record = GetRecordFromTypeId(typeid(T).name()); + auto offset = OffsetFromStart(fn); + for (auto i = 0; i < record.member_metadata.size(); ++i) { + if (record.member_metadata[i].offset == offset) { + member_name_ = record.member_metadata[i].name; + value_ = value_retrieval((void*)&value, record.member_metadata[i].storage_class); + break; + } + } + } + + template + QueryPredicate(R T::* fn, R value, const std::string& symbol) + : QueryPredicate(fn, value, symbol, + [&](void* v, SqliteStorageClass storage_class) { return GetSqlValue(v, storage_class); }) {} + + QueryPredicate(const std::string& symbol, const std::string& member_name, const SqlValue& value) + : symbol_(symbol), member_name_(member_name), value_(value) {} + + /// Returns the value used for the current query, against which the struct member + /// (defined from the pointer-to-member function) will be compared. The value needs + /// to be type-erased, so that the header file is not bloated with unnecessary implementation details + virtual SqlValue GetSqlValue(void* v, SqliteStorageClass storage_class) const; + + /// The symbol used for the comparison, for example "=" for equality + std::string symbol_; + + /// The name of the compared member, as defined in source code, used to construct the + /// textual representation of the evaluation string + std::string member_name_; + + /// The comparison value that will be bound to the generated SQL placeholder + SqlValue value_; +}; + +/// A wrapper for an empty predicate, used to fetch all elements of an SQLite table +class REFLECTION_EXPORT EmptyPredicate final : public QueryPredicateBase { +public: + std::string Evaluate() const override; + std::vector Bindings() const override; + QueryPredicateBase* Clone() const override; +}; + +/// A wrapper for an equality predicate, for which the value of the +/// struct member is required to be equal to a given control value +class REFLECTION_EXPORT Equal final : public QueryPredicate { +public: + template + explicit Equal(R T::* fn, R value) : QueryPredicate(fn, value, "=") {} + + template + explicit Equal(int64_t T::* fn, int value) : Equal(fn, (int64_t)value) {} + + template + explicit Equal(std::wstring T::* fn, const wchar_t* value) : Equal(fn, std::wstring(value)) {} +}; + +/// A wrapper for an inequality predicate, for which the value of the +/// struct member is required to be unequal to a given control value +class REFLECTION_EXPORT Unequal final : public QueryPredicate { +public: + template + explicit Unequal(R T::* fn, R value) : QueryPredicate(fn, value, "!=") {} + + template + explicit Unequal(int64_t T::* fn, int value) : Unequal(fn, (int64_t)value) {} + + template + explicit Unequal(std::wstring T::* fn, const wchar_t* value) : Unequal(fn, std::wstring(value)) {} +}; + +/// A wrapper for a similarity predicate, for which the value of the +/// struct member is required to be similar to a given control value +class REFLECTION_EXPORT Like final : public QueryPredicate { +public: + template + explicit Like(R T::* fn, R value) + : QueryPredicate(fn, value, "LIKE", + [&](void* v, SqliteStorageClass storage_class) { return GetSqlValue(v, storage_class); }) {} + + template + explicit Like(int64_t T::* fn, int value) : Like(fn, (int64_t)value) {} + + template + explicit Like(std::wstring T::* fn, const wchar_t* value) : Like(fn, std::wstring(value)) {} + +protected: + SqlValue GetSqlValue(void* v, SqliteStorageClass storage_class) const override; +}; + +/// A wrapper for a comparison predicate, for which the value of the +/// struct member is required to be greater than a given control value +class REFLECTION_EXPORT GreaterThan final : public QueryPredicate { +public: + template + explicit GreaterThan(int64_t T::* fn, int64_t value) : QueryPredicate(fn, value, ">") {} + + template + explicit GreaterThan(int64_t T::* fn, int value) : QueryPredicate(fn, (int64_t)value, ">") {} + + template + explicit GreaterThan(double T::* fn, double value) : QueryPredicate(fn, value, ">") {} +}; + +/// A wrapper for a comparison predicate, for which the value of the +/// struct member is required to be greater than or equal to a given control value +class REFLECTION_EXPORT GreaterThanOrEqual final : public QueryPredicate { +public: + template + explicit GreaterThanOrEqual(int64_t T::* fn, int64_t value) : QueryPredicate(fn, value, ">=") {} + + template + explicit GreaterThanOrEqual(int64_t T::* fn, int value) : QueryPredicate(fn, (int64_t)value, ">=") {} + + template + explicit GreaterThanOrEqual(double T::* fn, double value) : QueryPredicate(fn, value, ">=") {} +}; + +/// A wrapper for a comparison predicate, for which the value of the +/// struct member is required to be smaller than a given control value +class REFLECTION_EXPORT SmallerThan final : public QueryPredicate { +public: + template + explicit SmallerThan(int64_t T::* fn, int64_t value) : QueryPredicate(fn, value, "<") {} + + template + explicit SmallerThan(int64_t T::* fn, int value) : QueryPredicate(fn, (int64_t)value, "<") {} + + template + explicit SmallerThan(double T::* fn, double value) : QueryPredicate(fn, value, "<") {} +}; + +/// A wrapper for a comparison predicate, for which the value of the +/// struct member is required to be smaller than or equal to a given control value +class REFLECTION_EXPORT SmallerThanOrEqual final : public QueryPredicate { +public: + template + explicit SmallerThanOrEqual(int64_t T::* fn, int64_t value) : QueryPredicate(fn, value, "<=") {} + + template + explicit SmallerThanOrEqual(int64_t T::* fn, int value) : QueryPredicate(fn, (int64_t)value, "<=") {} + + template + explicit SmallerThanOrEqual(double T::* fn, double value) : QueryPredicate(fn, value, "<=") {} +}; + +/// A wrapper of a compound predicate, which combines two other predicates, +/// allowing the construction of more complex predicates from elementary predicates +class REFLECTION_EXPORT BinaryPredicate : public QueryPredicateBase { +public: + std::string Evaluate() const override; + std::vector Bindings() const override; + +protected: + BinaryPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right, const std::string& symbol); + + std::unique_ptr left_; + std::unique_ptr right_; + std::string symbol_; +}; + +/// A compound predicate, which requires that both predicates are valid at the same time +class REFLECTION_EXPORT AndPredicate final : public BinaryPredicate { +public: + AndPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right); + QueryPredicateBase* Clone() const override; +}; + +/// A compound predicate, which requires that either predicate is valid +class REFLECTION_EXPORT OrPredicate final : public BinaryPredicate { +public: + OrPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right); + QueryPredicateBase* Clone() const override; +}; +} // namespace sqlite_reflection diff --git a/include/reflection.h b/include/reflection.h index 8abe145..d0a97c7 100644 --- a/include/reflection.h +++ b/include/reflection.h @@ -36,65 +36,59 @@ /// The storage class in an SQLite column for a given member of a struct, for which reflection is enabled /// https://www.sqlite.org/datatype3.html -enum class REFLECTION_EXPORT SqliteStorageClass -{ - kInt, - kReal, - kText, - kDateTime, - kBool -}; +enum class REFLECTION_EXPORT SqliteStorageClass { kInt, kReal, kText, kDateTime, kBool }; /// A struct holding all information needed for introspection of user-defined structs /// which are meant to be saved to and retrieved from an SQLite database, using /// the technique of X macro (https://en.wikipedia.org/wiki/X_Macro) /// A reflectable struct meant to be saved in SQLite is also called in this project a "record" -struct REFLECTION_EXPORT Reflection -{ - /// This holds the metadata of a given struct member - class MemberMetadata - { - public: - MemberMetadata(const std::string& _name, SqliteStorageClass _storage_class, size_t _offset) - : name(_name), storage_class(_storage_class), sqlite_column_name(ToSqliteColumnName(_storage_class)), offset(_offset) { } - - /// The struct variable member name, as defined in the source code - std::string name; - - /// The storage class of this struct member, as defined by its type in the source code - /// https://www.tutorialspoint.com/sqlite/sqlite_data_types.htm - SqliteStorageClass storage_class; - - /// The name of the column for this member in the corresponding SQLite table of its containing struct - std::string sqlite_column_name; - - /// The memory offset in bytes of this member from the struct's start, including any padding bits - size_t offset; - - private: - /// Helper for conversion between member storage class and SQLite column name - static const char* ToSqliteColumnName(const SqliteStorageClass storage_class) { - switch (storage_class) { - case SqliteStorageClass::kInt: - case SqliteStorageClass::kBool: - return "INTEGER"; - case SqliteStorageClass::kReal: - return "REAL"; - case SqliteStorageClass::kText: - return "TEXT"; - case SqliteStorageClass::kDateTime: - return "DATETIME"; - default: - throw std::domain_error("Implementation error: storage class is not supported"); - } - } - }; - - /// The name of the corresponding struct, for which reflection is enabled, as defined in the source code - std::string name; - - /// All member metadata - std::vector member_metadata; +struct REFLECTION_EXPORT Reflection { + /// This holds the metadata of a given struct member + class MemberMetadata { + public: + MemberMetadata(const std::string& _name, SqliteStorageClass _storage_class, size_t _offset) + : name(_name), + storage_class(_storage_class), + sqlite_column_name(ToSqliteColumnName(_storage_class)), + offset(_offset) {} + + /// The struct variable member name, as defined in the source code + std::string name; + + /// The storage class of this struct member, as defined by its type in the source code + /// https://www.tutorialspoint.com/sqlite/sqlite_data_types.htm + SqliteStorageClass storage_class; + + /// The name of the column for this member in the corresponding SQLite table of its containing struct + std::string sqlite_column_name; + + /// The memory offset in bytes of this member from the struct's start, including any padding bits + size_t offset; + + private: + /// Helper for conversion between member storage class and SQLite column name + static const char* ToSqliteColumnName(const SqliteStorageClass storage_class) { + switch (storage_class) { + case SqliteStorageClass::kInt: + case SqliteStorageClass::kBool: + return "INTEGER"; + case SqliteStorageClass::kReal: + return "REAL"; + case SqliteStorageClass::kText: + return "TEXT"; + case SqliteStorageClass::kDateTime: + return "DATETIME"; + default: + throw std::domain_error("Implementation error: storage class is not supported"); + } + } + }; + + /// The name of the corresponding struct, for which reflection is enabled, as defined in the source code + std::string name; + + /// All member metadata + std::vector member_metadata; }; /// Returns the offset in bytes of a reflectable struct member from the struct's start, @@ -102,13 +96,13 @@ struct REFLECTION_EXPORT Reflection /// https://isocpp.org/wiki/faq/pointers-to-members template size_t OffsetFromStart(R T::* fn) { - const auto sf = sizeof(fn); - char bytes_f[sf]; - std::memcpy(bytes_f, reinterpret_cast(&fn), sf); - size_t offset = 0; - const auto bytes_to_copy = sf < sizeof(offset) ? sf : sizeof(offset); - std::memcpy(&offset, bytes_f, bytes_to_copy); - return offset; + const auto sf = sizeof(fn); + char bytes_f[sf]; + std::memcpy(bytes_f, reinterpret_cast(&fn), sf); + size_t offset = 0; + const auto bytes_to_copy = sf < sizeof(offset) ? sf : sizeof(offset); + std::memcpy(&offset, bytes_f, bytes_to_copy); + return offset; } #define STR_NOEXPAND(A) #A @@ -117,16 +111,16 @@ size_t OffsetFromStart(R T::* fn) { #define CAT_NOEXPAND(A, B) A##B #define CAT(A, B) CAT_NOEXPAND(A, B) -#define DEFINE_MEMBER(R, T) reflectable.member_metadata.push_back(Reflection::MemberMetadata(STR(R), T, offsetof(struct REFLECTABLE, R))); +#define DEFINE_MEMBER(R, T) \ + reflectable.member_metadata.push_back(Reflection::MemberMetadata(STR(R), T, offsetof(struct REFLECTABLE, R))); /// A singleton object which holds all reflectable structs, and is guaranteed to be /// instantiated before main.cpp starts -struct REFLECTION_EXPORT ReflectionRegister -{ - /// The keys are NOT the names of each struct as defined in source code, - /// but the identifiers returned from typeid(...).name(), which are typically - /// mangled in C++ and are compiler-specific - std::map records; +struct REFLECTION_EXPORT ReflectionRegister { + /// The keys are NOT the names of each struct as defined in source code, + /// but the identifiers returned from typeid(...).name(), which are typically + /// mangled in C++ and are compiler-specific + std::map records; }; /// Retrieves the singleton in a safe manner, creating it if needed @@ -146,29 +140,29 @@ REFLECTION_EXPORT Reflection& GetRecordFromTypeId(const std::string& type_id); /// } REFLECTION_EXPORT char* GetMemberAddress(void* p, const Reflection& record, size_t i); -#endif // REFLECTION_INTERNAL +#endif // REFLECTION_INTERNAL #include "time_point.h" -#if defined (REFLECTABLE) && defined (FIELDS) +#if defined(REFLECTABLE) && defined(FIELDS) #ifndef REFLECTABLE_DLL_EXPORT #define REFLECTABLE_DLL_EXPORT #endif -#pragma warning (push) -#pragma warning( disable:4002) // "too many actual parameters for macro 'MEMBER'" - - struct REFLECTABLE_DLL_EXPORT REFLECTABLE { - // member declaration according to the order given in source code -#define MEMBER_DECLARE(L, R) L R; -#define MEMBER_INT(R) MEMBER_DECLARE(int64_t, R) -#define MEMBER_REAL(R) MEMBER_DECLARE(double, R) -#define MEMBER_TEXT(R) MEMBER_DECLARE(std::wstring, R) -#define MEMBER_DATETIME(R) MEMBER_DECLARE(sqlite_reflection::TimePoint, R) -#define MEMBER_BOOL(R) MEMBER_DECLARE(bool, R) +#pragma warning(push) +#pragma warning(disable : 4002) // "too many actual parameters for macro 'MEMBER'" + +struct REFLECTABLE_DLL_EXPORT REFLECTABLE { + // member declaration according to the order given in source code +#define MEMBER_DECLARE(L, R) L R; +#define MEMBER_INT(R) MEMBER_DECLARE(int64_t, R) +#define MEMBER_REAL(R) MEMBER_DECLARE(double, R) +#define MEMBER_TEXT(R) MEMBER_DECLARE(std::wstring, R) +#define MEMBER_DATETIME(R) MEMBER_DECLARE(sqlite_reflection::TimePoint, R) +#define MEMBER_BOOL(R) MEMBER_DECLARE(bool, R) #define FUNC(SIGNATURE) - FIELDS + FIELDS #undef MEMBER_DECLARE #undef MEMBER_INT #undef MEMBER_REAL @@ -176,58 +170,59 @@ REFLECTION_EXPORT char* GetMemberAddress(void* p, const Reflection& record, size #undef MEMBER_DATETIME #undef MEMBER_BOOL #undef FUNC - int64_t id; + int64_t id; - // custom function declaration + // custom function declaration #define MEMBER_INT(R) #define MEMBER_REAL(R) #define MEMBER_TEXT(R) #define MEMBER_DATETIME(R) #define MEMBER_BOOL(R) -#define FUNC(SIGNATURE) SIGNATURE; - FIELDS +#define FUNC(SIGNATURE) SIGNATURE; + FIELDS #undef MEMBER_INT #undef MEMBER_REAL #undef MEMBER_TEXT #undef MEMBER_DATETIME #undef MEMBER_BOOL #undef FUNC - }; +}; - /// Provide a static registration function for each reflectable struct - static std::string CAT(Register, REFLECTABLE)() { - std::string type_id = typeid(REFLECTABLE).name(); - std::string name = STR(REFLECTABLE); - ReflectionRegister& instance = *GetReflectionRegisterInstance(); - auto isRecordRegisterd = instance.records.find(type_id) != instance.records.end(); - if (!isRecordRegisterd) { - auto& reflectable = GetRecordFromTypeId(type_id); - reflectable.name = name; - - // store member metadata - DEFINE_MEMBER(id, SqliteStorageClass::kInt) -#define MEMBER_INT(R) DEFINE_MEMBER(R, SqliteStorageClass::kInt) -#define MEMBER_REAL(R) DEFINE_MEMBER(R, SqliteStorageClass::kReal) -#define MEMBER_TEXT(R) DEFINE_MEMBER(R, SqliteStorageClass::kText) -#define MEMBER_DATETIME(R) DEFINE_MEMBER(R, SqliteStorageClass::kDateTime) -#define MEMBER_BOOL(R) DEFINE_MEMBER(R, SqliteStorageClass::kBool) +/// Provide a static registration function for each reflectable struct +static std::string CAT(Register, REFLECTABLE)() { + std::string type_id = typeid(REFLECTABLE).name(); + std::string name = STR(REFLECTABLE); + ReflectionRegister& instance = *GetReflectionRegisterInstance(); + auto isRecordRegisterd = instance.records.find(type_id) != instance.records.end(); + if (!isRecordRegisterd) { + auto& reflectable = GetRecordFromTypeId(type_id); + reflectable.name = name; + + // store member metadata + DEFINE_MEMBER(id, SqliteStorageClass::kInt) +#define MEMBER_INT(R) DEFINE_MEMBER(R, SqliteStorageClass::kInt) +#define MEMBER_REAL(R) DEFINE_MEMBER(R, SqliteStorageClass::kReal) +#define MEMBER_TEXT(R) DEFINE_MEMBER(R, SqliteStorageClass::kText) +#define MEMBER_DATETIME(R) DEFINE_MEMBER(R, SqliteStorageClass::kDateTime) +#define MEMBER_BOOL(R) DEFINE_MEMBER(R, SqliteStorageClass::kBool) #define FUNC(SIGNATURE) - FIELDS + FIELDS #undef MEMBER_INT #undef MEMBER_REAL #undef MEMBER_TEXT #undef MEMBER_DATETIME #undef MEMBER_BOOL #undef FUNC - } - return name; - }; + } + return name; +}; - /// In order to guarantee that all reflectable structs are registered before main starts, we use the C++ feature, that static variables - /// are initialized before the program starts. In order to trigger the registration function, we store its result to a global string - static std::string CAT(meta_registered_, REFLECTABLE) = CAT(Register, REFLECTABLE)(); +/// In order to guarantee that all reflectable structs are registered before main starts, we use the C++ feature, that +/// static variables are initialized before the program starts. In order to trigger the registration function, we store +/// its result to a global string +static std::string CAT(meta_registered_, REFLECTABLE) = CAT(Register, REFLECTABLE)(); -#pragma warning (pop) +#pragma warning(pop) #undef FIELDS #undef REFLECTABLE diff --git a/include/reflection_export.h b/include/reflection_export.h index f074d27..f7bf68a 100644 --- a/include/reflection_export.h +++ b/include/reflection_export.h @@ -22,23 +22,23 @@ #pragma once -#pragma warning(disable : 4018) // signed/unsigned warnings +#pragma warning(disable : 4018) // signed/unsigned warnings #pragma warning(disable : 4100) #pragma warning(disable : 4251) #pragma warning(disable : 4512) #pragma warning(disable : 4996) #ifdef _X64_ -#pragma warning(disable : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data -#pragma warning(disable : 4244) // warning C4244: '=' : conversion from '__int64' to 'int', possible loss of data +#pragma warning(disable : 4267) // warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data +#pragma warning(disable : 4244) // warning C4244: '=' : conversion from '__int64' to 'int', possible loss of data #endif #ifdef _WIN32 #ifdef BUILD_SQLITE_REFLECTION -#define REFLECTION_EXPORT __declspec( dllexport ) +#define REFLECTION_EXPORT __declspec(dllexport) #else -#define REFLECTION_EXPORT __declspec( dllimport ) +#define REFLECTION_EXPORT __declspec(dllimport) #endif #else -#define REFLECTION_EXPORT __attribute__ ((__visibility__("default"))) +#define REFLECTION_EXPORT __attribute__((__visibility__("default"))) #endif diff --git a/include/time_point.h b/include/time_point.h index 0c1f9f6..6be19af 100644 --- a/include/time_point.h +++ b/include/time_point.h @@ -22,10 +22,10 @@ #pragma once -#include "reflection_export.h" - -#include #include +#include + +#include "reflection_export.h" typedef std::chrono::time_point sys_seconds; @@ -35,30 +35,29 @@ namespace sqlite_reflection { /// System time is a very good approximation of UTC time, the only difference being the management of leap seconds /// (system time ignores leap seconds) /// https://en.wikipedia.org/wiki/Coordinated_Universal_Time - class REFLECTION_EXPORT TimePoint - { - public: - /// Defaults to start of system time (Unix epoch) - TimePoint(); - - // Creates a time representation based on the elapsed seconds from the Unix epoch. - // Negative valules represent time points before 1970-01-01T00:00:00Z, - // whereas positive values after 1970-01-01 00:00:00 UTC - explicit TimePoint(const int64_t& seconds_since_unix_epoch); - - // Creates a time representation based on the elapsed seconds from the Unix epoch. - // Negative valules represent time points before 1970-01-01T00:00:00Z, - // whereas positive values after 1970-01-01 00:00:00 UTC - explicit TimePoint(const sys_seconds& time_since_unix_epoch); - - /// Creates a time point instance from its equivalent ISO 8601 UTC format - static TimePoint FromSystemTime(const std::wstring& iso_8601_string); - - /// Returns a string representation of this instance, expressed in ISO 8601 UTC format - /// https://en.wikipedia.org/wiki/ISO_8601 - std::wstring SystemTime() const; +class REFLECTION_EXPORT TimePoint { +public: + /// Defaults to start of system time (Unix epoch) + TimePoint(); + + // Creates a time representation based on the elapsed seconds from the Unix epoch. + // Negative valules represent time points before 1970-01-01T00:00:00Z, + // whereas positive values after 1970-01-01 00:00:00 UTC + explicit TimePoint(const int64_t& seconds_since_unix_epoch); + + // Creates a time representation based on the elapsed seconds from the Unix epoch. + // Negative valules represent time points before 1970-01-01T00:00:00Z, + // whereas positive values after 1970-01-01 00:00:00 UTC + explicit TimePoint(const sys_seconds& time_since_unix_epoch); + + /// Creates a time point instance from its equivalent ISO 8601 UTC format + static TimePoint FromSystemTime(const std::wstring& iso_8601_string); + + /// Returns a string representation of this instance, expressed in ISO 8601 UTC format + /// https://en.wikipedia.org/wiki/ISO_8601 + std::wstring SystemTime() const; - private: - sys_seconds time_stamp_; - }; -} +private: + sys_seconds time_stamp_; +}; +} // namespace sqlite_reflection diff --git a/src/database.cc b/src/database.cc index 1416614..040cf3c 100644 --- a/src/database.cc +++ b/src/database.cc @@ -28,73 +28,72 @@ #include "queries.h" namespace sqlite_reflection { - Database* Database::instance_ = nullptr; - - const ReflectionRegister& GetReflectionRegister() { - return *GetReflectionRegisterInstance(); - } - - void Database::Initialize(const std::string& path) { - if (instance_ != nullptr) { - throw std::invalid_argument("Database has already been initialized"); - } - - const auto effective_path = path != "" ? path : ":memory:"; - instance_ = new Database(effective_path.data()); - } - - void Database::Finalize() { - if (instance_ != nullptr) { - sqlite3_close(instance_->db_); - delete instance_; - instance_ = nullptr; - } - } - - Database::Database(const char* path) - : db_(nullptr) { - if (sqlite3_open(path, &db_)) { - throw std::invalid_argument("Database could not be initialized"); - } - - auto& reg = GetReflectionRegister(); - for (const auto& contents : reg.records) { - const auto& record = contents.second; - CreateTableQuery query(db_, record); - query.Execute(); - } - } - - const Database& Database::Instance() { - return *instance_; - } - - FetchQueryResults Database::Fetch(const Reflection& record, const QueryPredicateBase* predicate) const { - FetchRecordsQuery query(db_, record, predicate); - return query.GetResults(); - } - - const Reflection& Database::GetRecord(const std::string& type_id) { - return GetReflectionRegister().records.at(type_id); - } - - void Database::Save(void* p, const Reflection& record) const { - InsertQuery query(db_, record, p); - query.Execute(); - } - - void Database::Update(void* p, const Reflection& record) const { - UpdateQuery query(db_, record, p); - query.Execute(); - } - - void Database::Delete(const Reflection& record, const QueryPredicateBase* predicate) const { - DeleteQuery query(db_, record, predicate); - query.Execute(); - } - - void Database::UnsafeSql(const std::string& raw_sql_query) const { - SqlQuery sql(db_, raw_sql_query); - sql.Execute(); +Database* Database::instance_ = nullptr; + +const ReflectionRegister& GetReflectionRegister() { + return *GetReflectionRegisterInstance(); +} + +void Database::Initialize(const std::string& path) { + if (instance_ != nullptr) { + throw std::invalid_argument("Database has already been initialized"); + } + + const auto effective_path = !path.empty() ? path : ":memory:"; + instance_ = new Database(effective_path.data()); +} + +void Database::Finalize() { + if (instance_ != nullptr) { + sqlite3_close(instance_->db_); + delete instance_; + instance_ = nullptr; } } + +Database::Database(const char* path) : db_(nullptr) { + if (sqlite3_open(path, &db_)) { + throw std::invalid_argument("Database could not be initialized"); + } + + auto& reg = GetReflectionRegister(); + for (const auto& contents : reg.records) { + const auto& record = contents.second; + CreateTableQuery query(db_, record); + query.Execute(); + } +} + +const Database& Database::Instance() { + return *instance_; +} + +FetchQueryResults Database::Fetch(const Reflection& record, const QueryPredicateBase* predicate) const { + FetchRecordsQuery query(db_, record, predicate); + return query.GetResults(); +} + +const Reflection& Database::GetRecord(const std::string& type_id) { + return GetReflectionRegister().records.at(type_id); +} + +void Database::Save(void* p, const Reflection& record) const { + InsertQuery query(db_, record, p); + query.Execute(); +} + +void Database::Update(void* p, const Reflection& record) const { + UpdateQuery query(db_, record, p); + query.Execute(); +} + +void Database::Delete(const Reflection& record, const QueryPredicateBase* predicate) const { + DeleteQuery query(db_, record, predicate); + query.Execute(); +} + +void Database::UnsafeSql(const std::string& raw_sql_query) const { + SqlQuery sql(db_, raw_sql_query); + sql.Execute(); +} +} // namespace sqlite_reflection diff --git a/src/internal/string_utilities.h b/src/internal/string_utilities.h index 22cad0e..79b87d9 100644 --- a/src/internal/string_utilities.h +++ b/src/internal/string_utilities.h @@ -21,27 +21,26 @@ // SOFTWARE. #pragma once -#include "reflection_export.h" - #include #include +#include "reflection_export.h" + namespace sqlite_reflection { - /// Some useful string utility functions converting back and forth - /// between strings and concrete types used by SQLite - class REFLECTION_EXPORT StringUtilities - { - public: - static int64_t ToInt(const std::wstring& s); - static std::string FromInt(int64_t value); +/// Some useful string utility functions converting back and forth +/// between strings and concrete types used by SQLite +class REFLECTION_EXPORT StringUtilities { +public: + static int64_t ToInt(const std::wstring& s); + static std::string FromInt(int64_t value); - static double ToDouble(const std::wstring& s); - static std::string FromDouble(double value); + static double ToDouble(const std::wstring& s); + static std::string FromDouble(double value); - static std::string ToUtf8(const std::wstring& wide_string); - static std::wstring FromUtf8(const char* utf8_string, size_t byte_count); + static std::string ToUtf8(const std::wstring& wide_string); + static std::wstring FromUtf8(const char* utf8_string, size_t byte_count); - static std::string Join(const std::vector& list, const std::string& separator); - static std::string Join(const std::vector& list, char c); - }; -} + static std::string Join(const std::vector& list, const std::string& separator); + static std::string Join(const std::vector& list, char c); +}; +} // namespace sqlite_reflection diff --git a/src/queries.cc b/src/queries.cc index 78d6686..f9d1ac9 100644 --- a/src/queries.cc +++ b/src/queries.cc @@ -22,17 +22,18 @@ #include "queries.h" -#include -#include #include + #include +#include #include #include +#include #include +#include "fetch_query_results.h" #include "internal/sqlite3.h" #include "internal/string_utilities.h" -#include "fetch_query_results.h" using namespace sqlite_reflection; @@ -43,22 +44,23 @@ std::string Placeholders(size_t count) { void BindValue(sqlite3_stmt* stmt, int index, const SqlValue& value) { switch (value.storage_class) { - case SqliteStorageClass::kInt: - sqlite3_bind_int64(stmt, index, value.int_value); - break; - - case SqliteStorageClass::kBool: - sqlite3_bind_int(stmt, index, value.bool_value ? 1 : 0); - break; - - case SqliteStorageClass::kReal: - sqlite3_bind_double(stmt, index, value.real_value); - break; - - case SqliteStorageClass::kText: - case SqliteStorageClass::kDateTime: - sqlite3_bind_text(stmt, index, value.text_value.data(), static_cast(value.text_value.size()), SQLITE_TRANSIENT); - break; + case SqliteStorageClass::kInt: + sqlite3_bind_int64(stmt, index, value.int_value); + break; + + case SqliteStorageClass::kBool: + sqlite3_bind_int(stmt, index, value.bool_value ? 1 : 0); + break; + + case SqliteStorageClass::kReal: + sqlite3_bind_double(stmt, index, value.real_value); + break; + + case SqliteStorageClass::kText: + case SqliteStorageClass::kDateTime: + sqlite3_bind_text(stmt, index, value.text_value.data(), static_cast(value.text_value.size()), + SQLITE_TRANSIENT); + break; } } @@ -68,360 +70,333 @@ void BindValues(sqlite3_stmt* stmt, const std::vector& values) { } } -Query::Query(sqlite3* db, const Reflection& record) - : db_(db), record_(record) {} +Query::Query(sqlite3* db, const Reflection& record) : db_(db), record_(record) {} std::string Query::JoinedRecordColumnNames() const { - const auto column_names = GetRecordColumnNames(); - return StringUtilities::Join(column_names, ", "); + const auto column_names = GetRecordColumnNames(); + return StringUtilities::Join(column_names, ", "); } std::vector Query::GetRecordColumnNames() const { - std::vector column_names; - column_names.reserve(record_.member_metadata.size()); - for (auto j = 0; j < record_.member_metadata.size(); ++j) { - column_names.emplace_back(CustomizedColumnName(j)); - } - return column_names; + std::vector column_names; + column_names.reserve(record_.member_metadata.size()); + for (auto j = 0; j < record_.member_metadata.size(); ++j) { + column_names.emplace_back(CustomizedColumnName(j)); + } + return column_names; } std::string Query::CustomizedColumnName(size_t index) const { - return record_.member_metadata[index].name; + return record_.member_metadata[index].name; } -ExecutionQuery::ExecutionQuery(sqlite3* db, const Reflection& record) - : Query(db, record) {} +ExecutionQuery::ExecutionQuery(sqlite3* db, const Reflection& record) : Query(db, record) {} void ExecutionQuery::Execute() const { - const auto sql = PrepareSql(); - const auto bindings = Bindings(); - if (sqlite3_exec(db_, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr)) { - throw std::domain_error("Fatal error in transaction start"); - } - - if (bindings.empty()) { - if (sqlite3_exec(db_, sql.data(), nullptr, nullptr, nullptr)) { - sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); - throw std::domain_error((sql + ": Query could not be executed").data()); - } - } else { - sqlite3_stmt* stmt = nullptr; - if (sqlite3_prepare_v2(db_, sql.data(), -1, &stmt, nullptr)) { - sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); - throw std::domain_error((sql + ": Query could not be prepared").data()); - } - - BindValues(stmt, bindings); - if (sqlite3_step(stmt) != SQLITE_DONE) { - sqlite3_finalize(stmt); - sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); - throw std::domain_error((sql + ": Query could not be executed").data()); - } - sqlite3_finalize(stmt); - } - - if (sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, nullptr)) { - throw std::domain_error("Fatal error in transaction commit"); - } + const auto sql = PrepareSql(); + const auto bindings = Bindings(); + if (sqlite3_exec(db_, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr)) { + throw std::domain_error("Fatal error in transaction start"); + } + + if (bindings.empty()) { + if (sqlite3_exec(db_, sql.data(), nullptr, nullptr, nullptr)) { + sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); + throw std::domain_error((sql + ": Query could not be executed").data()); + } + } else { + sqlite3_stmt* stmt = nullptr; + if (sqlite3_prepare_v2(db_, sql.data(), -1, &stmt, nullptr)) { + sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); + throw std::domain_error((sql + ": Query could not be prepared").data()); + } + + BindValues(stmt, bindings); + if (sqlite3_step(stmt) != SQLITE_DONE) { + sqlite3_finalize(stmt); + sqlite3_exec(db_, "ROLLBACK;", nullptr, nullptr, nullptr); + throw std::domain_error((sql + ": Query could not be executed").data()); + } + sqlite3_finalize(stmt); + } + + if (sqlite3_exec(db_, "COMMIT;", nullptr, nullptr, nullptr)) { + throw std::domain_error("Fatal error in transaction commit"); + } } std::vector ExecutionQuery::Bindings() const { - return {}; + return {}; } std::vector ExecutionQuery::GetValues(void* p) const { - const auto& members = record_.member_metadata; - std::vector values; - - for (auto j = 0; j < members.size(); j++) { - const auto current_storage_class = members[j].storage_class; - SqlValue value; - value.storage_class = current_storage_class; - - switch (current_storage_class) { - case SqliteStorageClass::kInt: - { - value.int_value = (*(int64_t*)((void*)GetMemberAddress(p, record_, j))); - break; - } - - case SqliteStorageClass::kBool: - { - value.bool_value = (*(bool*)((void*)GetMemberAddress(p, record_, j))); + const auto& members = record_.member_metadata; + std::vector values; + + for (auto j = 0; j < members.size(); j++) { + const auto current_storage_class = members[j].storage_class; + SqlValue value; + value.storage_class = current_storage_class; + + switch (current_storage_class) { + case SqliteStorageClass::kInt: { + value.int_value = *reinterpret_cast(GetMemberAddress(p, record_, j)); + break; + } + + case SqliteStorageClass::kBool: { + value.bool_value = *reinterpret_cast(GetMemberAddress(p, record_, j)); + break; + } + + case SqliteStorageClass::kReal: { + value.real_value = *reinterpret_cast(GetMemberAddress(p, record_, j)); break; } - case SqliteStorageClass::kReal: - { - value.real_value = (*(double*)((void*)GetMemberAddress(p, record_, j))); - break; - } - - case SqliteStorageClass::kText: - { - auto& text = (*(std::wstring*)((void*)GetMemberAddress(p, record_, j))); - value.text_value = StringUtilities::ToUtf8(text); - break; - } - - case SqliteStorageClass::kDateTime: - { - auto& time_point = (*(TimePoint*)((void*)GetMemberAddress(p, record_, j))); - value.text_value = StringUtilities::ToUtf8(time_point.SystemTime()); - break; - } - - default: - break; - } - values.emplace_back(value); - } - - return values; + case SqliteStorageClass::kText: { + auto& text = *reinterpret_cast(GetMemberAddress(p, record_, j)); + value.text_value = StringUtilities::ToUtf8(text); + break; + } + + case SqliteStorageClass::kDateTime: { + auto& time_point = *reinterpret_cast(GetMemberAddress(p, record_, j)); + value.text_value = StringUtilities::ToUtf8(time_point.SystemTime()); + break; + } + + default: + break; + } + values.emplace_back(value); + } + + return values; } -SqlQuery::SqlQuery(sqlite3* db, const std::string& sql) -: ExecutionQuery(db, Reflection()), sql_(sql) {} +SqlQuery::SqlQuery(sqlite3* db, const std::string& sql) : ExecutionQuery(db, Reflection()), sql_(sql) {} std::string SqlQuery::PrepareSql() const { - return sql_.length() > 0 && sql_[sql_.length() - 1] != ';' - ? sql_ + ";" - : sql_; + return !sql_.empty() && sql_[sql_.length() - 1] != ';' ? sql_ + ";" : sql_; } -CreateTableQuery::CreateTableQuery(sqlite3* db, const Reflection& record) - : ExecutionQuery(db, record) { } +CreateTableQuery::CreateTableQuery(sqlite3* db, const Reflection& record) : ExecutionQuery(db, record) {} std::string CreateTableQuery::PrepareSql() const { - std::string sql("CREATE TABLE IF NOT EXISTS "); - sql += record_.name + " (" + JoinedRecordColumnNames() + ");"; - return sql; + std::string sql("CREATE TABLE IF NOT EXISTS "); + sql += record_.name + " (" + JoinedRecordColumnNames() + ");"; + return sql; } std::string CreateTableQuery::CustomizedColumnName(size_t index) const { - auto name = Query::CustomizedColumnName(index); - const auto is_id = name.compare(std::string("id")) == 0; - name += " " + record_.member_metadata[index].sqlite_column_name; + auto name = Query::CustomizedColumnName(index); + const auto is_id = name.compare(std::string("id")) == 0; + name += " " + record_.member_metadata[index].sqlite_column_name; - return is_id - ? name + " PRIMARY KEY" - : name; + return is_id ? name + " PRIMARY KEY" : name; } DeleteQuery::DeleteQuery(sqlite3* db, const Reflection& record, const QueryPredicateBase* predicate) - : ExecutionQuery(db, record), predicate_(predicate) {} + : ExecutionQuery(db, record), predicate_(predicate) {} std::string DeleteQuery::PrepareSql() const { - std::string sql("DELETE FROM "); - sql += record_.name + " WHERE " + predicate_->Evaluate() + ";"; - return sql; + std::string sql("DELETE FROM "); + sql += record_.name + " WHERE " + predicate_->Evaluate() + ";"; + return sql; } std::vector DeleteQuery::Bindings() const { - return predicate_->Bindings(); + return predicate_->Bindings(); } -InsertQuery::InsertQuery(sqlite3* db, const Reflection& record, void* p) - : ExecutionQuery(db, record), p_(p) {} +InsertQuery::InsertQuery(sqlite3* db, const Reflection& record, void* p) : ExecutionQuery(db, record), p_(p) {} std::string InsertQuery::PrepareSql() const { - std::string sql("INSERT INTO "); - sql += record_.name + " (" + JoinedRecordColumnNames() + ") VALUES ("; - sql += Placeholders(record_.member_metadata.size()) + ");"; - return sql; + std::string sql("INSERT INTO "); + sql += record_.name + " (" + JoinedRecordColumnNames() + ") VALUES ("; + sql += Placeholders(record_.member_metadata.size()) + ");"; + return sql; } std::vector InsertQuery::Bindings() const { - return GetValues(p_); + return GetValues(p_); } -UpdateQuery::UpdateQuery(sqlite3* db, const Reflection& record, void* p) - : ExecutionQuery(db, record), p_(p) {} +UpdateQuery::UpdateQuery(sqlite3* db, const Reflection& record, void* p) : ExecutionQuery(db, record), p_(p) {} std::string UpdateQuery::PrepareSql() const { - std::string sql("UPDATE "); - sql += record_.name + " SET "; + std::string sql("UPDATE "); + sql += record_.name + " SET "; - auto columns = GetRecordColumnNames(); + auto columns = GetRecordColumnNames(); - std::vector columns_with_values; - columns_with_values.reserve(columns.size() - 1); - std::transform(columns.begin() + 1, - columns.end(), - std::back_inserter(columns_with_values), - [](const std::string& column){ - return column + "=?"; - }); + std::vector columns_with_values; + columns_with_values.reserve(columns.size() - 1); + std::transform(columns.begin() + 1, columns.end(), std::back_inserter(columns_with_values), + [](const std::string& column) { return column + "=?"; }); - sql += StringUtilities::Join(columns_with_values, ", "); - sql += " WHERE " + columns[0] + "=?"; - sql += ";"; + sql += StringUtilities::Join(columns_with_values, ", "); + sql += " WHERE " + columns[0] + "=?"; + sql += ";"; - return sql; + return sql; } std::vector UpdateQuery::Bindings() const { - const auto values = GetValues(p_); - std::vector bindings; - bindings.reserve(values.size()); - for (auto i = 1; i < values.size(); ++i) { - bindings.emplace_back(values[i]); - } - bindings.emplace_back(values[0]); - return bindings; + const auto values = GetValues(p_); + std::vector bindings; + bindings.reserve(values.size()); + for (auto i = 1; i < values.size(); ++i) { + bindings.emplace_back(values[i]); + } + bindings.emplace_back(values[0]); + return bindings; } -FetchMaxIdQuery::FetchMaxIdQuery(sqlite3* db, const Reflection& record) - : Query(db, record), stmt_(nullptr) {} +FetchMaxIdQuery::FetchMaxIdQuery(sqlite3* db, const Reflection& record) : Query(db, record), stmt_(nullptr) {} FetchMaxIdQuery::~FetchMaxIdQuery() { - if (stmt_) { - sqlite3_finalize(stmt_); - } + if (stmt_) { + sqlite3_finalize(stmt_); + } } std::string FetchMaxIdQuery::PrepareSql() const { - return "SELECT MAX(id) FROM " + record_.name + ";"; + return "SELECT MAX(id) FROM " + record_.name + ";"; } int64_t FetchMaxIdQuery::GetMaxId() { - const auto sql = PrepareSql(); + const auto sql = PrepareSql(); - if (sqlite3_prepare_v2(db_, sql.data(), -1, &stmt_, nullptr)) { - sqlite3_close(db_); - throw std::runtime_error("Could not retrieve max id for table " + record_.name); - } + if (sqlite3_prepare_v2(db_, sql.data(), -1, &stmt_, nullptr)) { + sqlite3_close(db_); + throw std::runtime_error("Could not retrieve max id for table " + record_.name); + } - const auto column_count = sqlite3_column_count(stmt_); - if (column_count != 1) { - throw std::runtime_error("Number of columns for max id is wrong for table " + record_.name); - } + const auto column_count = sqlite3_column_count(stmt_); + if (column_count != 1) { + throw std::runtime_error("Number of columns for max id is wrong for table " + record_.name); + } - if (sqlite3_step(stmt_) != SQLITE_ROW) { - throw std::runtime_error("Row result could not be read for max id of table " + record_.name); - } + if (sqlite3_step(stmt_) != SQLITE_ROW) { + throw std::runtime_error("Row result could not be read for max id of table " + record_.name); + } - const auto max_id = sqlite3_column_int(stmt_, 0); - return max_id; + const auto max_id = sqlite3_column_int(stmt_, 0); + return max_id; } FetchRecordsQuery::FetchRecordsQuery(sqlite3* db, const Reflection& record, const QueryPredicateBase* predicate) - : Query(db, record), stmt_(nullptr), predicate_(predicate) {} + : Query(db, record), stmt_(nullptr), predicate_(predicate) {} FetchRecordsQuery::~FetchRecordsQuery() { - if (stmt_) { - sqlite3_finalize(stmt_); - } + if (stmt_) { + sqlite3_finalize(stmt_); + } } FetchQueryResults FetchRecordsQuery::GetResults() { - const auto sql = PrepareSql(); - - if (sqlite3_prepare_v2(db_, sql.data(), -1, &stmt_, nullptr)) { - sqlite3_close(db_); - throw std::runtime_error((sql + ": could not get results").data()); - } - BindValues(stmt_, predicate_->Bindings()); - - const auto column_count = sqlite3_column_count(stmt_); - - FetchQueryResults results; - results.column_names.reserve(column_count); - for (auto i = 0; i < column_count; i++) { - results.column_names.emplace_back(sqlite3_column_name(stmt_, i)); - } - - while (sqlite3_step(stmt_) != SQLITE_DONE) { - std::vector row; - row.reserve(column_count); - for (auto col = 0; col < column_count; col++) { - auto value = GetColumnValue(col); - row.emplace_back(value); - } - results.row_values.emplace_back(row); - } - - return results; + const auto sql = PrepareSql(); + + if (sqlite3_prepare_v2(db_, sql.data(), -1, &stmt_, nullptr)) { + sqlite3_close(db_); + throw std::runtime_error((sql + ": could not get results").data()); + } + BindValues(stmt_, predicate_->Bindings()); + + const auto column_count = sqlite3_column_count(stmt_); + + FetchQueryResults results; + results.column_names.reserve(column_count); + for (auto i = 0; i < column_count; i++) { + results.column_names.emplace_back(sqlite3_column_name(stmt_, i)); + } + + while (sqlite3_step(stmt_) != SQLITE_DONE) { + std::vector row; + row.reserve(column_count); + for (auto col = 0; col < column_count; col++) { + auto value = GetColumnValue(col); + row.emplace_back(value); + } + results.row_values.emplace_back(row); + } + + return results; } void FetchRecordsQuery::Hydrate(void* p, const FetchQueryResults& query_results, const Reflection& record, size_t i) { - for (auto j = 0; j < query_results.column_names.size(); j++) { - const auto current_column = query_results.column_names[j]; - const auto current_storage_class = record.member_metadata[j].storage_class; - const auto& content = query_results.row_values[i][j]; - if (content == L"") { - continue; - } - - switch (current_storage_class) { - case SqliteStorageClass::kInt: - { - auto& v = (*(int64_t*)((void*)GetMemberAddress(p, record, j))); - v = StringUtilities::ToInt(content); - break; - } - - case SqliteStorageClass::kBool: - { - auto& v = (*(bool*)((void*)GetMemberAddress(p, record, j))); + for (auto j = 0; j < query_results.column_names.size(); j++) { + const auto current_storage_class = record.member_metadata[j].storage_class; + const auto& content = query_results.row_values[i][j]; + if (content.empty()) { + continue; + } + + switch (current_storage_class) { + case SqliteStorageClass::kInt: { + auto& v = *reinterpret_cast(GetMemberAddress(p, record, j)); + v = StringUtilities::ToInt(content); + break; + } + + case SqliteStorageClass::kBool: { + auto& v = *reinterpret_cast(GetMemberAddress(p, record, j)); v = StringUtilities::ToInt(content) == 1; break; } - case SqliteStorageClass::kReal: - { - auto& v = (*(double*)((void*)GetMemberAddress(p, record, j))); - v = StringUtilities::ToDouble(content); - break; - } - - case SqliteStorageClass::kText: - { - auto& v = (*(std::wstring*)((void*)GetMemberAddress(p, record, j))); - v = content; - break; - } - - case SqliteStorageClass::kDateTime: - { - auto& v = (*(TimePoint*)((void*)GetMemberAddress(p, record, j))); - v = TimePoint::FromSystemTime(content); - break; - } - - default: - break; - } - } + case SqliteStorageClass::kReal: { + auto& v = *reinterpret_cast(GetMemberAddress(p, record, j)); + v = StringUtilities::ToDouble(content); + break; + } + + case SqliteStorageClass::kText: { + auto& v = *reinterpret_cast(GetMemberAddress(p, record, j)); + v = content; + break; + } + + case SqliteStorageClass::kDateTime: { + auto& v = *reinterpret_cast(GetMemberAddress(p, record, j)); + v = TimePoint::FromSystemTime(content); + break; + } + + default: + break; + } + } } std::string FetchRecordsQuery::PrepareSql() const { - std::string sql("SELECT * FROM "); - sql += record_.name; - const auto condition_evaluation = predicate_->Evaluate(); - if (strcmp(condition_evaluation.data(), "") != 0) { - sql += " WHERE " + condition_evaluation; - } - return sql + ";"; + std::string sql("SELECT * FROM "); + sql += record_.name; + const auto condition_evaluation = predicate_->Evaluate(); + if (strcmp(condition_evaluation.data(), "") != 0) { + sql += " WHERE " + condition_evaluation; + } + return sql + ";"; } std::wstring FetchRecordsQuery::GetColumnValue(const int col) const { - const int col_type = sqlite3_column_type(stmt_, col); - switch (col_type) { - case SQLITE_INTEGER: - return std::to_wstring(sqlite3_column_int(stmt_, col)); - - case SQLITE_FLOAT: - return std::to_wstring(sqlite3_column_double(stmt_, col)); - - case SQLITE_TEXT: - { - const auto content = reinterpret_cast(sqlite3_column_text(stmt_, col)); - const auto byte_count = sqlite3_column_bytes(stmt_, col); - return StringUtilities::FromUtf8(content, byte_count); - } - - default: - return L""; - } + const int col_type = sqlite3_column_type(stmt_, col); + switch (col_type) { + case SQLITE_INTEGER: + return std::to_wstring(sqlite3_column_int(stmt_, col)); + + case SQLITE_FLOAT: + return std::to_wstring(sqlite3_column_double(stmt_, col)); + + case SQLITE_TEXT: { + const auto content = reinterpret_cast(sqlite3_column_text(stmt_, col)); + const auto byte_count = sqlite3_column_bytes(stmt_, col); + return StringUtilities::FromUtf8(content, byte_count); + } + + default: + return L""; + } } diff --git a/src/query_predicates.cc b/src/query_predicates.cc index 212eb68..5b6e1f8 100644 --- a/src/query_predicates.cc +++ b/src/query_predicates.cc @@ -21,136 +21,128 @@ // SOFTWARE. #include "query_predicates.h" + #include "internal/string_utilities.h" using namespace sqlite_reflection; -const std::string space(" "); -const std::string percent("%"); +constexpr char space[] = " "; +constexpr char percent[] = "%"; -SqlValue::SqlValue() - : storage_class(SqliteStorageClass::kText), - int_value(0), - bool_value(false), - real_value(0.0), - text_value() {} +SqlValue::SqlValue() : storage_class(SqliteStorageClass::kText), int_value(0), bool_value(false), real_value(0.0) {} QueryPredicateBase* QueryPredicate::Clone() const { - return new QueryPredicate(symbol_, member_name_, value_); + return new QueryPredicate(symbol_, member_name_, value_); } std::string EmptyPredicate::Evaluate() const { - return ""; + return ""; } std::vector EmptyPredicate::Bindings() const { - return {}; + return {}; } QueryPredicateBase* EmptyPredicate::Clone() const { - return new EmptyPredicate(); + return new EmptyPredicate(); } AndPredicate QueryPredicateBase::And(const QueryPredicateBase& other) const { - return AndPredicate(*this, other); + return AndPredicate(*this, other); } OrPredicate QueryPredicateBase::Or(const QueryPredicateBase& other) const { - return OrPredicate(*this, other); + return OrPredicate(*this, other); } std::string QueryPredicate::Evaluate() const { - return member_name_ + space + symbol_ + space + "?"; + return member_name_ + space + symbol_ + space + "?"; } std::vector QueryPredicate::Bindings() const { - return { value_ }; + return {value_}; } SqlValue QueryPredicate::GetSqlValue(void* v, SqliteStorageClass storage_class) const { - SqlValue result; - result.storage_class = storage_class; - switch (storage_class) { - case SqliteStorageClass::kInt: - { - result.int_value = *(int64_t*)(v); - return result; - } - case SqliteStorageClass::kBool: - { + SqlValue result; + result.storage_class = storage_class; + switch (storage_class) { + case SqliteStorageClass::kInt: { + result.int_value = *(int64_t*)(v); + return result; + } + case SqliteStorageClass::kBool: { result.bool_value = *(bool*)(v); return result; } - case SqliteStorageClass::kReal: - { - result.real_value = *(double*)(v); - return result; - } - case SqliteStorageClass::kText: - { - auto value = *(std::wstring*)(v); - result.text_value = StringUtilities::ToUtf8(value); - return result; - } - case SqliteStorageClass::kDateTime: - { - auto value = *(TimePoint*)(v); - result.text_value = StringUtilities::ToUtf8(value.SystemTime()); - return result; - } - default: - throw std::domain_error("Blob cannot be compared against equality"); - } + case SqliteStorageClass::kReal: { + result.real_value = *(double*)(v); + return result; + } + case SqliteStorageClass::kText: { + auto value = *(std::wstring*)(v); + result.text_value = StringUtilities::ToUtf8(value); + return result; + } + case SqliteStorageClass::kDateTime: { + auto value = *(TimePoint*)(v); + result.text_value = StringUtilities::ToUtf8(value.SystemTime()); + return result; + } + default: + throw std::domain_error("Blob cannot be compared against equality"); + } } SqlValue Like::GetSqlValue(void* v, SqliteStorageClass storage_class) const { - auto value = QueryPredicate::GetSqlValue(v, storage_class); - switch (storage_class) { - case SqliteStorageClass::kInt: - value.storage_class = SqliteStorageClass::kText; - value.text_value = percent + StringUtilities::FromInt(value.int_value) + percent; - return value; - case SqliteStorageClass::kBool: - value.storage_class = SqliteStorageClass::kText; - value.text_value = percent + StringUtilities::FromInt(value.bool_value ? 1 : 0) + percent; - return value; - case SqliteStorageClass::kReal: - value.storage_class = SqliteStorageClass::kText; - value.text_value = percent + StringUtilities::FromDouble(value.real_value) + percent; - return value; - case SqliteStorageClass::kText: - case SqliteStorageClass::kDateTime: - value.text_value = percent + value.text_value + percent; - return value; - default: - throw std::domain_error("Blob cannot be compared against similarity"); - } + auto value = QueryPredicate::GetSqlValue(v, storage_class); + switch (storage_class) { + case SqliteStorageClass::kInt: + value.storage_class = SqliteStorageClass::kText; + value.text_value = percent + StringUtilities::FromInt(value.int_value) + percent; + return value; + case SqliteStorageClass::kBool: + value.storage_class = SqliteStorageClass::kText; + value.text_value = percent + StringUtilities::FromInt(value.bool_value ? 1 : 0) + percent; + return value; + case SqliteStorageClass::kReal: + value.storage_class = SqliteStorageClass::kText; + value.text_value = percent + StringUtilities::FromDouble(value.real_value) + percent; + return value; + case SqliteStorageClass::kText: + case SqliteStorageClass::kDateTime: + value.text_value = percent + value.text_value + percent; + return value; + default: + throw std::domain_error("Blob cannot be compared against similarity"); + } } -BinaryPredicate::BinaryPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right, const std::string& symbol) - : left_(left.Clone()), right_(right.Clone()), symbol_(symbol) {} +BinaryPredicate::BinaryPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right, + const std::string& symbol) + : left_(left.Clone()), right_(right.Clone()), symbol_(symbol) {} std::string BinaryPredicate::Evaluate() const { - return "(" + left_->Evaluate() + space + symbol_ + space + right_->Evaluate() + ")"; + return "(" + left_->Evaluate() + space + symbol_ + space + right_->Evaluate() + ")"; } std::vector BinaryPredicate::Bindings() const { - auto bindings = left_->Bindings(); - const auto right_bindings = right_->Bindings(); - bindings.insert(bindings.end(), right_bindings.begin(), right_bindings.end()); - return bindings; + auto bindings = left_->Bindings(); + const auto right_bindings = right_->Bindings(); + bindings.insert(bindings.end(), right_bindings.begin(), right_bindings.end()); + return bindings; } AndPredicate::AndPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right) - : BinaryPredicate(left, right, "AND") {} + : BinaryPredicate(left, right, "AND") {} QueryPredicateBase* AndPredicate::Clone() const { - return new AndPredicate(*left_.get(), *right_.get()); + return new AndPredicate(*left_.get(), *right_.get()); } OrPredicate::OrPredicate(const QueryPredicateBase& left, const QueryPredicateBase& right) - : BinaryPredicate(left, right, "OR") {} + : BinaryPredicate(left, right, "OR") {} QueryPredicateBase* OrPredicate::Clone() const { - return new OrPredicate(*left_.get(), *right_.get()); + return new OrPredicate(*left_.get(), *right_.get()); } diff --git a/src/reflection.cc b/src/reflection.cc index e4a121b..d1e50b3 100644 --- a/src/reflection.cc +++ b/src/reflection.cc @@ -21,25 +21,26 @@ // SOFTWARE. #include "reflection.h" + #include static std::unique_ptr p = nullptr; ReflectionRegister* GetReflectionRegisterInstance() { - if (!p) { - p = std::unique_ptr(new ReflectionRegister()); - } - return p.get(); + if (!p) { + p = std::unique_ptr(new ReflectionRegister()); + } + return p.get(); } Reflection& GetRecordFromTypeId(const std::string& type_id) { - ReflectionRegister& instance = *GetReflectionRegisterInstance(); - auto& meta_struct = instance.records[type_id]; - return meta_struct; + ReflectionRegister& instance = *GetReflectionRegisterInstance(); + auto& meta_struct = instance.records[type_id]; + return meta_struct; } char* GetMemberAddress(void* precord, const Reflection& record, const size_t i) { - const auto struct_start = static_cast(precord); - const size_t var_offset = record.member_metadata[i].offset; - return struct_start + var_offset; + const auto struct_start = static_cast(precord); + const size_t var_offset = record.member_metadata[i].offset; + return struct_start + var_offset; } diff --git a/src/string_utilities.cc b/src/string_utilities.cc index b27b158..508568e 100644 --- a/src/string_utilities.cc +++ b/src/string_utilities.cc @@ -32,68 +32,61 @@ using namespace sqlite_reflection; int64_t StringUtilities::ToInt(const std::wstring& s) { - int result = 0; - try { - result = std::stoi(s); - } - catch (...) {} - return result; + try { + return std::stoll(s); + } catch (...) { + return 0; + } } std::string StringUtilities::FromInt(int64_t value) { - return std::to_string(value); + return std::to_string(value); } double StringUtilities::ToDouble(const std::wstring& s) { - double result = 0.0; - try { - result = std::stod(s); - } - catch (...) {} - return result; + try { + return std::stod(s); + } catch (...) { + return 0.0; + } } std::string StringUtilities::FromDouble(double value) { - auto textual_representation = std::to_string(value); - if (textual_representation.find(".") != std::string::npos) { - while (textual_representation.length() > 0 && textual_representation[textual_representation.length() - 1] == '0') { - textual_representation.erase(textual_representation.end() - 1); - } - } - return textual_representation; + auto textual_representation = std::to_string(value); + if (textual_representation.find('.') != std::string::npos) { + while (!textual_representation.empty() && textual_representation[textual_representation.length() - 1] == '0') { + textual_representation.erase(textual_representation.end() - 1); + } + } + return textual_representation; } std::string StringUtilities::ToUtf8(const std::wstring& wide_string) { - std::wstring_convert> converter; - auto utf8_string = converter.to_bytes(wide_string.data(), wide_string.data() + wide_string.size()); - return utf8_string; + std::wstring_convert> converter; + auto utf8_string = converter.to_bytes(wide_string.data(), wide_string.data() + wide_string.size()); + return utf8_string; } std::wstring StringUtilities::FromUtf8(const char* utf8_string, size_t byte_count) { - std::wstring_convert> converter; - auto wide_string = converter.from_bytes(utf8_string, utf8_string + byte_count); - return wide_string; + std::wstring_convert> converter; + auto wide_string = converter.from_bytes(utf8_string, utf8_string + byte_count); + return wide_string; } std::string StringUtilities::Join(const std::vector& list, const std::string& separator) { - const auto size = list.size(); - if (size == 0) { - return ""; - } + const auto size = list.size(); + if (size == 0) { + return ""; + } - if (size == 1) { - return list[0]; - } + if (size == 1) { + return list[0]; + } - return std::accumulate( - list.begin() + 1, - list.end(), - list[0], - [&](const std::string& a, const std::string& b){ - return a + separator + b; - }); + return std::accumulate(list.begin() + 1, list.end(), list[0], + [&](const std::string& a, const std::string& b) { return a + separator + b; }); } std::string StringUtilities::Join(const std::vector& list, char c) { - return Join(list, std::string(1, c)); + return Join(list, std::string(1, c)); } diff --git a/src/time_point.cc b/src/time_point.cc index 72520c5..39b586b 100644 --- a/src/time_point.cc +++ b/src/time_point.cc @@ -21,14 +21,15 @@ // SOFTWARE. #include "time_point.h" + +#include +#include + #include "internal/string_utilities.h" // This would not have been at all possible without this amazing library #include "internal/date.h" -#include -#include - using namespace sqlite_reflection; #if __cplusplus < 201907L @@ -45,35 +46,32 @@ using namespace sqlite_reflection; using namespace date; #endif -static std::wstring iso_format = L"%FT%TZ"; +constexpr wchar_t iso_format[] = L"%FT%TZ"; TimePoint::TimePoint() {} TimePoint::TimePoint(const int64_t& seconds_since_unix_epoch) - : time_stamp_(std::chrono::seconds(seconds_since_unix_epoch)) {} + : time_stamp_(std::chrono::seconds(seconds_since_unix_epoch)) {} -TimePoint::TimePoint(const sys_seconds& time_since_unix_epoch) - : time_stamp_(time_since_unix_epoch) {} +TimePoint::TimePoint(const sys_seconds& time_since_unix_epoch) : time_stamp_(time_since_unix_epoch) {} TimePoint TimePoint::FromSystemTime(const std::wstring& iso_8601_string) { - std::wistringstream in{iso_8601_string}; - sys_seconds time_stamp; - in >> date::parse(iso_format, time_stamp); - return TimePoint(time_stamp); + std::wistringstream in{iso_8601_string}; + sys_seconds time_stamp; + in >> date::parse(iso_format, time_stamp); + return TimePoint(time_stamp); } std::wstring TimePoint::SystemTime() const { - const auto tp = date::floor(time_stamp_); - const auto ymd = date::year_month_day(tp); - const auto time = date::make_time(time_stamp_ - tp); + const auto tp = date::floor(time_stamp_); + const auto ymd = date::year_month_day(tp); + const auto time = date::make_time(time_stamp_ - tp); - std::stringstream ss; - ss << static_cast(ymd.year()) << "-" - << std::setfill('0') << std::setw(2) << static_cast(ymd.month()) << "-" - << std::setfill('0') << std::setw(2) << static_cast(ymd.day()) << "T" - << std::setfill('0') << std::setw(2) << time.hours().count() << ":" - << std::setfill('0') << std::setw(2) << time.minutes().count() << ":" - << std::setfill('0') << std::setw(2) << time.seconds().count() << "Z"; - const auto utf8_string = ss.str(); - return StringUtilities::FromUtf8(utf8_string.data(), utf8_string.size()); + std::stringstream ss; + ss << static_cast(ymd.year()) << "-" << std::setfill('0') << std::setw(2) << static_cast(ymd.month()) + << "-" << std::setfill('0') << std::setw(2) << static_cast(ymd.day()) << "T" << std::setfill('0') + << std::setw(2) << time.hours().count() << ":" << std::setfill('0') << std::setw(2) << time.minutes().count() + << ":" << std::setfill('0') << std::setw(2) << time.seconds().count() << "Z"; + const auto utf8_string = ss.str(); + return StringUtilities::FromUtf8(utf8_string.data(), utf8_string.size()); } diff --git a/tests/company.h b/tests/company.h index d96f60a..2393693 100644 --- a/tests/company.h +++ b/tests/company.h @@ -25,9 +25,9 @@ #include #define REFLECTABLE Company -#define FIELDS \ -MEMBER_TEXT(name) \ -MEMBER_INT(age) \ -MEMBER_TEXT(address) \ -MEMBER_REAL(salary) +#define FIELDS \ + MEMBER_TEXT(name) \ + MEMBER_INT(age) \ + MEMBER_TEXT(address) \ + MEMBER_REAL(salary) #include "reflection.h" diff --git a/tests/database_test.cc b/tests/database_test.cc index f936fbe..cf97389 100644 --- a/tests/database_test.cc +++ b/tests/database_test.cc @@ -20,49 +20,49 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#include #include "database.h" +#include + +#include "company.h" #include "person.h" #include "pet.h" -#include "company.h" using namespace sqlite_reflection; -class DatabaseTest : public ::testing::Test -{ - void SetUp() override { - Database::Initialize(""); - } +class DatabaseTest : public ::testing::Test { + void SetUp() override { + Database::Initialize(""); + } - void TearDown() override { - Database::Finalize(); - } + void TearDown() override { + Database::Finalize(); + } }; TEST_F(DatabaseTest, Initialization) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - const auto all_persons = db.FetchAll(); - EXPECT_EQ(0, all_persons.size()); + const auto all_persons = db.FetchAll(); + EXPECT_EQ(0, all_persons.size()); - const auto all_pets = db.FetchAll(); - EXPECT_EQ(0, all_pets.size()); + const auto all_pets = db.FetchAll(); + EXPECT_EQ(0, all_pets.size()); } TEST_F(DatabaseTest, SingleInsertion) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - const Person p{L"παναγιώτης", L"ανδριανόπουλος", 39, 1}; - db.Save(p); + const Person p{L"παναγιώτης", L"ανδριανόπουλος", 39, 1}; + db.Save(p); - const auto all_persons = db.FetchAll(); - EXPECT_EQ(1, all_persons.size()); + const auto all_persons = db.FetchAll(); + EXPECT_EQ(1, all_persons.size()); - EXPECT_EQ(p.first_name, all_persons[0].first_name); - EXPECT_EQ(p.last_name, all_persons[0].last_name); - EXPECT_EQ(p.age, all_persons[0].age); - EXPECT_EQ(p.id, all_persons[0].id); + EXPECT_EQ(p.first_name, all_persons[0].first_name); + EXPECT_EQ(p.last_name, all_persons[0].last_name); + EXPECT_EQ(p.age, all_persons[0].age); + EXPECT_EQ(p.id, all_persons[0].id); } TEST_F(DatabaseTest, SingleInsertionWithAutoIdIncrement) { @@ -81,191 +81,191 @@ TEST_F(DatabaseTest, SingleInsertionWithAutoIdIncrement) { } TEST_F(DatabaseTest, MultipleInsertions) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); - persons.push_back({L"peter", L"meier", 32, true, 5}); + persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); + persons.push_back({L"peter", L"meier", 32, true, 5}); - db.Save(persons); + db.Save(persons); - const auto saved_persons = db.FetchAll(); - EXPECT_EQ(2, saved_persons.size()); + const auto saved_persons = db.FetchAll(); + EXPECT_EQ(2, saved_persons.size()); - for (auto i = 0; i < saved_persons.size(); ++i) { - EXPECT_EQ(persons[i].id, saved_persons[i].id); - EXPECT_EQ(persons[i].first_name, saved_persons[i].first_name); - EXPECT_EQ(persons[i].last_name, saved_persons[i].last_name); - EXPECT_EQ(persons[i].age, saved_persons[i].age); + for (auto i = 0; i < saved_persons.size(); ++i) { + EXPECT_EQ(persons[i].id, saved_persons[i].id); + EXPECT_EQ(persons[i].first_name, saved_persons[i].first_name); + EXPECT_EQ(persons[i].last_name, saved_persons[i].last_name); + EXPECT_EQ(persons[i].age, saved_persons[i].age); EXPECT_EQ(persons[i].is_vaccinated, saved_persons[i].is_vaccinated); - } + } } TEST_F(DatabaseTest, InsertionOnOneTypeDoesNotAffectOtherType) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - const Person p{L"παναγιώτης", L"ανδριανόπουλος", 39, 1}; - db.Save(p); + const Person p{L"παναγιώτης", L"ανδριανόπουλος", 39, 1}; + db.Save(p); - const auto all_pets = db.FetchAll(); - EXPECT_EQ(0, all_pets.size()); + const auto all_pets = db.FetchAll(); + EXPECT_EQ(0, all_pets.size()); } TEST_F(DatabaseTest, SingleUpdate) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - Person p{L"παναγιώτης", L"ανδριανόπουλος", 39, 1}; - db.Save(p); + Person p{L"παναγιώτης", L"ανδριανόπουλος", 39, 1}; + db.Save(p); - p.age = 23; - p.first_name = L"max"; + p.age = 23; + p.first_name = L"max"; - db.Update(p); + db.Update(p); - const auto all_persons = db.FetchAll(); - EXPECT_EQ(1, all_persons.size()); + const auto all_persons = db.FetchAll(); + EXPECT_EQ(1, all_persons.size()); - EXPECT_EQ(p.first_name, all_persons[0].first_name); - EXPECT_EQ(p.last_name, all_persons[0].last_name); - EXPECT_EQ(p.age, all_persons[0].age); - EXPECT_EQ(p.id, all_persons[0].id); + EXPECT_EQ(p.first_name, all_persons[0].first_name); + EXPECT_EQ(p.last_name, all_persons[0].last_name); + EXPECT_EQ(p.age, all_persons[0].age); + EXPECT_EQ(p.id, all_persons[0].id); } TEST_F(DatabaseTest, MultipleUpdates) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; persons.push_back({L"john", L"doe", 28, false, 3}); - persons.push_back({L"mary", L"poppins", 29, false, 5}); + persons.push_back({L"mary", L"poppins", 29, false, 5}); - db.Save(persons); + db.Save(persons); - persons[0].last_name = L"rambo"; - persons[1].age = 20; + persons[0].last_name = L"rambo"; + persons[1].age = 20; - db.Update(persons); + db.Update(persons); - const auto saved_persons = db.FetchAll(); - EXPECT_EQ(2, saved_persons.size()); + const auto saved_persons = db.FetchAll(); + EXPECT_EQ(2, saved_persons.size()); - for (auto i = 0; i < saved_persons.size(); ++i) { - EXPECT_EQ(persons[i].id, saved_persons[i].id); - EXPECT_EQ(persons[i].first_name, saved_persons[i].first_name); - EXPECT_EQ(persons[i].last_name, saved_persons[i].last_name); - EXPECT_EQ(persons[i].age, saved_persons[i].age); - } + for (auto i = 0; i < saved_persons.size(); ++i) { + EXPECT_EQ(persons[i].id, saved_persons[i].id); + EXPECT_EQ(persons[i].first_name, saved_persons[i].first_name); + EXPECT_EQ(persons[i].last_name, saved_persons[i].last_name); + EXPECT_EQ(persons[i].age, saved_persons[i].age); + } } TEST_F(DatabaseTest, InsertedTextSqlPayloadIsPersistedLiterally) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - const Person p{L"john'); DROP TABLE Person; --", L"payload", 39, false, 1}; - db.Save(p); + const Person p{L"john'); DROP TABLE Person; --", L"payload", 39, false, 1}; + db.Save(p); - const auto all_persons = db.FetchAll(); - EXPECT_EQ(1, all_persons.size()); - EXPECT_EQ(p.first_name, all_persons[0].first_name); - EXPECT_EQ(p.last_name, all_persons[0].last_name); - EXPECT_EQ(p.age, all_persons[0].age); - EXPECT_EQ(p.id, all_persons[0].id); + const auto all_persons = db.FetchAll(); + EXPECT_EQ(1, all_persons.size()); + EXPECT_EQ(p.first_name, all_persons[0].first_name); + EXPECT_EQ(p.last_name, all_persons[0].last_name); + EXPECT_EQ(p.age, all_persons[0].age); + EXPECT_EQ(p.id, all_persons[0].id); } TEST_F(DatabaseTest, InsertedTextWithEmbeddedNulIsPersistedLiterally) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - const Person p{std::wstring(L"a\0b", 3), L"payload", 39, false, 1}; - db.Save(p); + const Person p{std::wstring(L"a\0b", 3), L"payload", 39, false, 1}; + db.Save(p); - const auto all_persons = db.FetchAll(); - ASSERT_EQ(1, all_persons.size()); - EXPECT_EQ(p.first_name, all_persons[0].first_name); - EXPECT_EQ(3, all_persons[0].first_name.size()); + const auto all_persons = db.FetchAll(); + ASSERT_EQ(1, all_persons.size()); + EXPECT_EQ(p.first_name, all_persons[0].first_name); + EXPECT_EQ(3, all_persons[0].first_name.size()); - const auto predicate = Equal(&Person::first_name, std::wstring(L"a\0b", 3)); - const auto matching_persons = db.Fetch(&predicate); - ASSERT_EQ(1, matching_persons.size()); - EXPECT_EQ(p.first_name, matching_persons[0].first_name); + const auto predicate = Equal(&Person::first_name, std::wstring(L"a\0b", 3)); + const auto matching_persons = db.Fetch(&predicate); + ASSERT_EQ(1, matching_persons.size()); + EXPECT_EQ(p.first_name, matching_persons[0].first_name); } TEST_F(DatabaseTest, UpdatedTextSqlPayloadIsPersistedLiterally) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - Person p{L"john", L"payload", 39, false, 1}; - db.Save(p); + Person p{L"john", L"payload", 39, false, 1}; + db.Save(p); - p.first_name = L"updated'); DELETE FROM Person; --"; - db.Update(p); + p.first_name = L"updated'); DELETE FROM Person; --"; + db.Update(p); - const auto all_persons = db.FetchAll(); - EXPECT_EQ(1, all_persons.size()); - EXPECT_EQ(p.first_name, all_persons[0].first_name); - EXPECT_EQ(p.last_name, all_persons[0].last_name); - EXPECT_EQ(p.age, all_persons[0].age); - EXPECT_EQ(p.id, all_persons[0].id); + const auto all_persons = db.FetchAll(); + EXPECT_EQ(1, all_persons.size()); + EXPECT_EQ(p.first_name, all_persons[0].first_name); + EXPECT_EQ(p.last_name, all_persons[0].last_name); + EXPECT_EQ(p.age, all_persons[0].age); + EXPECT_EQ(p.id, all_persons[0].id); } TEST_F(DatabaseTest, DeleteWithRecord) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); - persons.push_back({L"peter", L"meier", 32, false, 5}); - persons.push_back({L"mary", L"poppins", 20, false, 13}); + persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); + persons.push_back({L"peter", L"meier", 32, false, 5}); + persons.push_back({L"mary", L"poppins", 20, false, 13}); - db.Save(persons); + db.Save(persons); - auto saved_persons = db.FetchAll(); - EXPECT_EQ(3, saved_persons.size()); + auto saved_persons = db.FetchAll(); + EXPECT_EQ(3, saved_persons.size()); - db.Delete(persons[1]); - saved_persons = db.FetchAll(); - EXPECT_EQ(2, saved_persons.size()); + db.Delete(persons[1]); + saved_persons = db.FetchAll(); + EXPECT_EQ(2, saved_persons.size()); - auto i = 0; - EXPECT_EQ(3, saved_persons[i].id); - EXPECT_EQ(L"παναγιώτης", saved_persons[i].first_name); - EXPECT_EQ(L"ανδριανόπουλος", saved_persons[i].last_name); - EXPECT_EQ(28, saved_persons[i].age); + auto i = 0; + EXPECT_EQ(3, saved_persons[i].id); + EXPECT_EQ(L"παναγιώτης", saved_persons[i].first_name); + EXPECT_EQ(L"ανδριανόπουλος", saved_persons[i].last_name); + EXPECT_EQ(28, saved_persons[i].age); - i++; - EXPECT_EQ(13, saved_persons[i].id); - EXPECT_EQ(L"mary", saved_persons[i].first_name); - EXPECT_EQ(L"poppins", saved_persons[i].last_name); - EXPECT_EQ(20, saved_persons[i].age); + i++; + EXPECT_EQ(13, saved_persons[i].id); + EXPECT_EQ(L"mary", saved_persons[i].first_name); + EXPECT_EQ(L"poppins", saved_persons[i].last_name); + EXPECT_EQ(20, saved_persons[i].age); } TEST_F(DatabaseTest, DeleteWithId) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); - persons.push_back({L"peter", L"meier", 32, false, 5}); - persons.push_back({L"mary", L"poppins", 20, false, 13}); + persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); + persons.push_back({L"peter", L"meier", 32, false, 5}); + persons.push_back({L"mary", L"poppins", 20, false, 13}); - db.Save(persons); + db.Save(persons); - auto saved_persons = db.FetchAll(); - EXPECT_EQ(3, saved_persons.size()); + auto saved_persons = db.FetchAll(); + EXPECT_EQ(3, saved_persons.size()); - db.Delete(persons[1].id); - saved_persons = db.FetchAll(); - EXPECT_EQ(2, saved_persons.size()); + db.Delete(persons[1].id); + saved_persons = db.FetchAll(); + EXPECT_EQ(2, saved_persons.size()); - auto i = 0; - EXPECT_EQ(3, saved_persons[i].id); - EXPECT_EQ(L"παναγιώτης", saved_persons[i].first_name); - EXPECT_EQ(L"ανδριανόπουλος", saved_persons[i].last_name); - EXPECT_EQ(28, saved_persons[i].age); + auto i = 0; + EXPECT_EQ(3, saved_persons[i].id); + EXPECT_EQ(L"παναγιώτης", saved_persons[i].first_name); + EXPECT_EQ(L"ανδριανόπουλος", saved_persons[i].last_name); + EXPECT_EQ(28, saved_persons[i].age); - i++; - EXPECT_EQ(13, saved_persons[i].id); - EXPECT_EQ(L"mary", saved_persons[i].first_name); - EXPECT_EQ(L"poppins", saved_persons[i].last_name); - EXPECT_EQ(20, saved_persons[i].age); + i++; + EXPECT_EQ(13, saved_persons[i].id); + EXPECT_EQ(L"mary", saved_persons[i].first_name); + EXPECT_EQ(L"poppins", saved_persons[i].last_name); + EXPECT_EQ(20, saved_persons[i].age); } TEST_F(DatabaseTest, DeleteWithPredicate) { @@ -279,8 +279,7 @@ TEST_F(DatabaseTest, DeleteWithPredicate) { db.Save(persons); - const auto age_match_predicate = SmallerThan(&Person::age, 30) - .And(Equal(&Person::is_vaccinated, true)); + const auto age_match_predicate = SmallerThan(&Person::age, 30).And(Equal(&Person::is_vaccinated, true)); db.Delete(&age_match_predicate); const auto fetched_persons = db.FetchAll(); @@ -294,220 +293,219 @@ TEST_F(DatabaseTest, DeleteWithPredicate) { } TEST_F(DatabaseTest, DeleteWithInjectedPredicateDoesNotDeleteRows) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"john", L"doe", 28, false, 3}); - persons.push_back({L"mary", L"poppins", 20, false, 5}); + persons.push_back({L"john", L"doe", 28, false, 3}); + persons.push_back({L"mary", L"poppins", 20, false, 5}); - db.Save(persons); + db.Save(persons); - const auto injected_predicate = Equal(&Person::first_name, L"nobody' OR 1=1 --"); - db.Delete(&injected_predicate); + const auto injected_predicate = Equal(&Person::first_name, L"nobody' OR 1=1 --"); + db.Delete(&injected_predicate); - const auto fetched_persons = db.FetchAll(); - EXPECT_EQ(2, fetched_persons.size()); + const auto fetched_persons = db.FetchAll(); + EXPECT_EQ(2, fetched_persons.size()); } TEST_F(DatabaseTest, SingleFetch) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); - persons.push_back({L"peter", L"meier", 32, false, 5}); - persons.push_back({L"mary", L"poppins", 20, false, 13}); + persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); + persons.push_back({L"peter", L"meier", 32, false, 5}); + persons.push_back({L"mary", L"poppins", 20, false, 13}); - db.Save(persons); + db.Save(persons); - const auto fetched_person = db.Fetch(5); - EXPECT_EQ(5, fetched_person.id); - EXPECT_EQ(L"peter", fetched_person.first_name); - EXPECT_EQ(L"meier", fetched_person.last_name); - EXPECT_EQ(32, fetched_person.age); + const auto fetched_person = db.Fetch(5); + EXPECT_EQ(5, fetched_person.id); + EXPECT_EQ(L"peter", fetched_person.first_name); + EXPECT_EQ(L"meier", fetched_person.last_name); + EXPECT_EQ(32, fetched_person.age); } TEST_F(DatabaseTest, SingleFetchWithoutExistingRecordExpectingException) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); - persons.push_back({L"peter", L"meier", 32, false, 5}); - persons.push_back({L"mary", L"poppins", 20, false}); + persons.push_back({L"παναγιώτης", L"ανδριανόπουλος", 28, false, 3}); + persons.push_back({L"peter", L"meier", 32, false, 5}); + persons.push_back({L"mary", L"poppins", 20, false}); - db.Save(persons); + db.Save(persons); - EXPECT_ANY_THROW(db.Fetch(15)); + EXPECT_ANY_THROW(db.Fetch(15)); } TEST_F(DatabaseTest, FetchWithInjectedPredicateDoesNotMatchAllRows) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"john", L"doe", 28, false, 3}); - persons.push_back({L"mary", L"poppins", 20, false, 5}); + persons.push_back({L"john", L"doe", 28, false, 3}); + persons.push_back({L"mary", L"poppins", 20, false, 5}); - db.Save(persons); + db.Save(persons); - const auto injected_predicate = Equal(&Person::first_name, L"john' OR 1=1 --"); - const auto fetched_persons = db.Fetch(&injected_predicate); - EXPECT_EQ(0, fetched_persons.size()); + const auto injected_predicate = Equal(&Person::first_name, L"john' OR 1=1 --"); + const auto fetched_persons = db.Fetch(&injected_predicate); + EXPECT_EQ(0, fetched_persons.size()); } TEST_F(DatabaseTest, FetchWithSimilarPredicateString) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector company; + std::vector company; - company.push_back({L"Paul", 32, L"California", 20000.0, 1}); - company.push_back({L"Allen", 25, L"Texas", 15000.0, 2}); - company.push_back({L"Teddy", 23, L"Norway", 20000.0, 3}); - company.push_back({L"Mark", 25, L"Rich-Mond", 65000.0, 4}); - company.push_back({L"David", 27, L"Texas", 85000.0, 5}); - company.push_back({L"Kim", 22, L"South-Hall", 45000.0, 6}); - company.push_back({L"Janes", 24, L"Houston", 10000.0, 7}); + company.push_back({L"Paul", 32, L"California", 20000.0, 1}); + company.push_back({L"Allen", 25, L"Texas", 15000.0, 2}); + company.push_back({L"Teddy", 23, L"Norway", 20000.0, 3}); + company.push_back({L"Mark", 25, L"Rich-Mond", 65000.0, 4}); + company.push_back({L"David", 27, L"Texas", 85000.0, 5}); + company.push_back({L"Kim", 22, L"South-Hall", 45000.0, 6}); + company.push_back({L"Janes", 24, L"Houston", 10000.0, 7}); - db.Save(company); + db.Save(company); - auto fetch_condition = Like(&Company::address, L"-"); + auto fetch_condition = Like(&Company::address, L"-"); - auto fetched_persons = db.Fetch(&fetch_condition); - EXPECT_EQ(2, fetched_persons.size()); + auto fetched_persons = db.Fetch(&fetch_condition); + EXPECT_EQ(2, fetched_persons.size()); - EXPECT_EQ(4, fetched_persons[0].id); - EXPECT_EQ(L"Mark", fetched_persons[0].name); - EXPECT_EQ(25, fetched_persons[0].age); - EXPECT_EQ(L"Rich-Mond", fetched_persons[0].address); - EXPECT_EQ(65000.0, fetched_persons[0].salary); + EXPECT_EQ(4, fetched_persons[0].id); + EXPECT_EQ(L"Mark", fetched_persons[0].name); + EXPECT_EQ(25, fetched_persons[0].age); + EXPECT_EQ(L"Rich-Mond", fetched_persons[0].address); + EXPECT_EQ(65000.0, fetched_persons[0].salary); - EXPECT_EQ(6, fetched_persons[1].id); - EXPECT_EQ(L"Kim", fetched_persons[1].name); - EXPECT_EQ(22, fetched_persons[1].age); - EXPECT_EQ(L"South-Hall", fetched_persons[1].address); - EXPECT_EQ(45000.0, fetched_persons[1].salary); + EXPECT_EQ(6, fetched_persons[1].id); + EXPECT_EQ(L"Kim", fetched_persons[1].name); + EXPECT_EQ(22, fetched_persons[1].age); + EXPECT_EQ(L"South-Hall", fetched_persons[1].address); + EXPECT_EQ(45000.0, fetched_persons[1].salary); } TEST_F(DatabaseTest, FetchWithSimilarPredicateDouble) { - const auto& db = Database::Instance(); - - std::vector company; - - company.push_back({L"Paul", 32, L"California", 20000.0, 1}); - company.push_back({L"Allen", 25, L"Texas", 15000.0, 2}); - company.push_back({L"Teddy", 23, L"Norway", 20000.0, 3}); - company.push_back({L"Mark", 25, L"Rich-Mond", 65000.0, 4}); - company.push_back({L"David", 27, L"Texas", 85000.0, 5}); - company.push_back({L"Kim", 22, L"South-Hall", 45000.0, 6}); - company.push_back({L"Janes", 24, L"Houston", 10000.0, 7}); - - db.Save(company); - - auto fetch_condition = Like(&Company::salary, 5000.0); - - auto fetched_persons = db.Fetch(&fetch_condition); - EXPECT_EQ(4, fetched_persons.size()); - - EXPECT_EQ(2, fetched_persons[0].id); - EXPECT_EQ(L"Allen", fetched_persons[0].name); - EXPECT_EQ(25, fetched_persons[0].age); - EXPECT_EQ(L"Texas", fetched_persons[0].address); - EXPECT_EQ(15000, fetched_persons[0].salary); - - EXPECT_EQ(4, fetched_persons[1].id); - EXPECT_EQ(L"Mark", fetched_persons[1].name); - EXPECT_EQ(25, fetched_persons[1].age); - EXPECT_EQ(L"Rich-Mond", fetched_persons[1].address); - EXPECT_EQ(65000, fetched_persons[1].salary); - - EXPECT_EQ(5, fetched_persons[2].id); - EXPECT_EQ(L"David", fetched_persons[2].name); - EXPECT_EQ(27, fetched_persons[2].age); - EXPECT_EQ(L"Texas", fetched_persons[2].address); - EXPECT_EQ(85000, fetched_persons[2].salary); - - EXPECT_EQ(6, fetched_persons[3].id); - EXPECT_EQ(L"Kim", fetched_persons[3].name); - EXPECT_EQ(22, fetched_persons[3].age); - EXPECT_EQ(L"South-Hall", fetched_persons[3].address); - EXPECT_EQ(45000.0, fetched_persons[3].salary); + const auto& db = Database::Instance(); + + std::vector company; + + company.push_back({L"Paul", 32, L"California", 20000.0, 1}); + company.push_back({L"Allen", 25, L"Texas", 15000.0, 2}); + company.push_back({L"Teddy", 23, L"Norway", 20000.0, 3}); + company.push_back({L"Mark", 25, L"Rich-Mond", 65000.0, 4}); + company.push_back({L"David", 27, L"Texas", 85000.0, 5}); + company.push_back({L"Kim", 22, L"South-Hall", 45000.0, 6}); + company.push_back({L"Janes", 24, L"Houston", 10000.0, 7}); + + db.Save(company); + + auto fetch_condition = Like(&Company::salary, 5000.0); + + auto fetched_persons = db.Fetch(&fetch_condition); + EXPECT_EQ(4, fetched_persons.size()); + + EXPECT_EQ(2, fetched_persons[0].id); + EXPECT_EQ(L"Allen", fetched_persons[0].name); + EXPECT_EQ(25, fetched_persons[0].age); + EXPECT_EQ(L"Texas", fetched_persons[0].address); + EXPECT_EQ(15000, fetched_persons[0].salary); + + EXPECT_EQ(4, fetched_persons[1].id); + EXPECT_EQ(L"Mark", fetched_persons[1].name); + EXPECT_EQ(25, fetched_persons[1].age); + EXPECT_EQ(L"Rich-Mond", fetched_persons[1].address); + EXPECT_EQ(65000, fetched_persons[1].salary); + + EXPECT_EQ(5, fetched_persons[2].id); + EXPECT_EQ(L"David", fetched_persons[2].name); + EXPECT_EQ(27, fetched_persons[2].age); + EXPECT_EQ(L"Texas", fetched_persons[2].address); + EXPECT_EQ(85000, fetched_persons[2].salary); + + EXPECT_EQ(6, fetched_persons[3].id); + EXPECT_EQ(L"Kim", fetched_persons[3].name); + EXPECT_EQ(22, fetched_persons[3].age); + EXPECT_EQ(L"South-Hall", fetched_persons[3].address); + EXPECT_EQ(45000.0, fetched_persons[3].salary); } TEST_F(DatabaseTest, FetchWithSimilarPredicateInt) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector company; + std::vector company; - company.push_back({L"Paul", 32, L"California", 20000.0, 1}); - company.push_back({L"Allen", 25, L"Texas", 15000.0, 2}); - company.push_back({L"Teddy", 23, L"Norway", 20000.0, 3}); - company.push_back({L"Mark", 25, L"Rich-Mond", 65000.0, 4}); - company.push_back({L"David", 27, L"Texas", 85000.0, 5}); - company.push_back({L"Kim", 22, L"South-Hall", 45000.0, 6}); - company.push_back({L"Janes", 24, L"Houston", 10000.0, 7}); + company.push_back({L"Paul", 32, L"California", 20000.0, 1}); + company.push_back({L"Allen", 25, L"Texas", 15000.0, 2}); + company.push_back({L"Teddy", 23, L"Norway", 20000.0, 3}); + company.push_back({L"Mark", 25, L"Rich-Mond", 65000.0, 4}); + company.push_back({L"David", 27, L"Texas", 85000.0, 5}); + company.push_back({L"Kim", 22, L"South-Hall", 45000.0, 6}); + company.push_back({L"Janes", 24, L"Houston", 10000.0, 7}); - db.Save(company); + db.Save(company); - const auto fetch_condition = Like(&Company::age, 7); + const auto fetch_condition = Like(&Company::age, 7); - const auto fetched_persons = db.Fetch(&fetch_condition); - EXPECT_EQ(1, fetched_persons.size()); + const auto fetched_persons = db.Fetch(&fetch_condition); + EXPECT_EQ(1, fetched_persons.size()); - EXPECT_EQ(5, fetched_persons[0].id); - EXPECT_EQ(L"David", fetched_persons[0].name); - EXPECT_EQ(27, fetched_persons[0].age); - EXPECT_EQ(L"Texas", fetched_persons[0].address); - EXPECT_EQ(85000, fetched_persons[0].salary); + EXPECT_EQ(5, fetched_persons[0].id); + EXPECT_EQ(L"David", fetched_persons[0].name); + EXPECT_EQ(27, fetched_persons[0].age); + EXPECT_EQ(L"Texas", fetched_persons[0].address); + EXPECT_EQ(85000, fetched_persons[0].salary); } TEST_F(DatabaseTest, FetchWithPredicateChaining) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"name1", L"surname1", 13, false, 1}); - persons.push_back({L"john", L"surname2", 25, false, 2}); - persons.push_back({L"john", L"surname3", 37, false, 3}); - persons.push_back({L"jame", L"surname4", 45, false, 4}); - persons.push_back({L"name5", L"surname5", 56, false, 5}); + persons.push_back({L"name1", L"surname1", 13, false, 1}); + persons.push_back({L"john", L"surname2", 25, false, 2}); + persons.push_back({L"john", L"surname3", 37, false, 3}); + persons.push_back({L"jame", L"surname4", 45, false, 4}); + persons.push_back({L"name5", L"surname5", 56, false, 5}); - db.Save(persons); + db.Save(persons); - const auto fetch_condition = GreaterThanOrEqual(&Person::id, 2) - .And(SmallerThan(&Person::id, 5)) - .And(Equal(&Person::first_name, L"john")); + const auto fetch_condition = + GreaterThanOrEqual(&Person::id, 2).And(SmallerThan(&Person::id, 5)).And(Equal(&Person::first_name, L"john")); - const auto fetched_persons = db.Fetch(&fetch_condition); - EXPECT_EQ(2, fetched_persons.size()); + const auto fetched_persons = db.Fetch(&fetch_condition); + EXPECT_EQ(2, fetched_persons.size()); - EXPECT_EQ(2, fetched_persons[0].id); - EXPECT_EQ(L"john", fetched_persons[0].first_name); - EXPECT_EQ(L"surname2", fetched_persons[0].last_name); - EXPECT_EQ(25, fetched_persons[0].age); + EXPECT_EQ(2, fetched_persons[0].id); + EXPECT_EQ(L"john", fetched_persons[0].first_name); + EXPECT_EQ(L"surname2", fetched_persons[0].last_name); + EXPECT_EQ(25, fetched_persons[0].age); - EXPECT_EQ(3, fetched_persons[1].id); - EXPECT_EQ(L"john", fetched_persons[1].first_name); - EXPECT_EQ(L"surname3", fetched_persons[1].last_name); - EXPECT_EQ(37, fetched_persons[1].age); + EXPECT_EQ(3, fetched_persons[1].id); + EXPECT_EQ(L"john", fetched_persons[1].first_name); + EXPECT_EQ(L"surname3", fetched_persons[1].last_name); + EXPECT_EQ(37, fetched_persons[1].age); } TEST_F(DatabaseTest, ReadMaxId) { - const auto& db = Database::Instance(); + const auto& db = Database::Instance(); - std::vector persons; + std::vector persons; - persons.push_back({L"john", L"appleseed", 28, false, 54}); - persons.push_back({L"mary", L"poppins", 20, false, 156}); + persons.push_back({L"john", L"appleseed", 28, false, 54}); + persons.push_back({L"mary", L"poppins", 20, false, 156}); - db.Save(persons); + db.Save(persons); - const auto max_id_person = db.GetMaxId(); - EXPECT_EQ(156, max_id_person); + const auto max_id_person = db.GetMaxId(); + EXPECT_EQ(156, max_id_person); - const auto max_id_pet = db.GetMaxId(); - EXPECT_EQ(0, max_id_pet); + const auto max_id_pet = db.GetMaxId(); + EXPECT_EQ(0, max_id_pet); } TEST_F(DatabaseTest, RawSqlQueryForPersistedRecord) { @@ -520,7 +518,7 @@ TEST_F(DatabaseTest, RawSqlQueryForPersistedRecord) { db.Save(persons); db.UnsafeSql("DELETE FROM Person WHERE length(first_name) <= 4"); - + const auto fetched_persons = db.FetchAll(); EXPECT_EQ(1, fetched_persons.size()); EXPECT_EQ(52, fetched_persons[0].id); diff --git a/tests/date_time_test.cc b/tests/date_time_test.cc index 2641f15..70a58c3 100644 --- a/tests/date_time_test.cc +++ b/tests/date_time_test.cc @@ -22,61 +22,60 @@ #include -#include "datetime_container.h" #include "database.h" +#include "datetime_container.h" using namespace sqlite_reflection; -class DateTimeTest : public ::testing::Test -{ - void SetUp() override { - Database::Initialize(); - } +class DateTimeTest : public ::testing::Test { + void SetUp() override { + Database::Initialize(); + } - void TearDown() override { - Database::Finalize(); - } + void TearDown() override { + Database::Finalize(); + } }; std::wstring RemoveLeadingZeros(std::wstring& str) { - while (str.length() > 0 && str[0] == L'0') { + while (!str.empty() && str[0] == L'0') { str.erase(str.begin()); } return str; } void ControlRoundTrip(const int64_t& time_point, const wchar_t* sys_time) { - DatetimeContainer f; - f.id = 0; - f.creation_date = TimePoint(time_point); + DatetimeContainer f; + f.id = 0; + f.creation_date = TimePoint(time_point); - const auto& db = Database::Instance(); - db.Save(f); + const auto& db = Database::Instance(); + db.Save(f); - const auto retrieved = db.FetchAll(); - EXPECT_EQ(1, retrieved.size()); + const auto retrieved = db.FetchAll(); + EXPECT_EQ(1, retrieved.size()); - auto sys_time_actual = retrieved[0].creation_date.SystemTime(); + auto sys_time_actual = retrieved[0].creation_date.SystemTime(); sys_time_actual = RemoveLeadingZeros(sys_time_actual); - + auto sys_time_expected = std::wstring(sys_time); sys_time_expected = RemoveLeadingZeros(sys_time_expected); - - EXPECT_EQ(sys_time_expected, sys_time_actual); + + EXPECT_EQ(sys_time_expected, sys_time_actual); } TEST_F(DateTimeTest, UnixEpoch) { - ControlRoundTrip(0, L"1970-01-01T00:00:00Z"); + ControlRoundTrip(0, L"1970-01-01T00:00:00Z"); } TEST_F(DateTimeTest, UnixEpochSystemTimeIsFormattedExactly) { - const TimePoint time_point(0); - EXPECT_EQ(L"1970-01-01T00:00:00Z", time_point.SystemTime()); + const TimePoint time_point(0); + EXPECT_EQ(L"1970-01-01T00:00:00Z", time_point.SystemTime()); } TEST_F(DateTimeTest, SystemTimePadsMonthDayHourMinuteAndSecond) { - const auto time_point = TimePoint::FromSystemTime(L"2023-07-08T09:02:04Z"); - EXPECT_EQ(L"2023-07-08T09:02:04Z", time_point.SystemTime()); + const auto time_point = TimePoint::FromSystemTime(L"2023-07-08T09:02:04Z"); + EXPECT_EQ(L"2023-07-08T09:02:04Z", time_point.SystemTime()); } TEST_F(DateTimeTest, RoundtripBefore1000) { @@ -84,17 +83,17 @@ TEST_F(DateTimeTest, RoundtripBefore1000) { } TEST_F(DateTimeTest, RoundtripBefore1900) { - ControlRoundTrip(-2359097130LL, L"1895-03-30T15:14:30Z"); + ControlRoundTrip(-2359097130LL, L"1895-03-30T15:14:30Z"); } TEST_F(DateTimeTest, RoundtripBefore1970) { - ControlRoundTrip(-1508726294, L"1922-03-11T21:21:46Z"); + ControlRoundTrip(-1508726294, L"1922-03-11T21:21:46Z"); } TEST_F(DateTimeTest, RoundtripAfter2000) { - ControlRoundTrip(1688842924, L"2023-07-08T19:02:04Z"); + ControlRoundTrip(1688842924, L"2023-07-08T19:02:04Z"); } TEST_F(DateTimeTest, RoundtripAfter2038) { - ControlRoundTrip(2333089535, L"2043-12-07T08:25:35Z"); + ControlRoundTrip(2333089535, L"2043-12-07T08:25:35Z"); } diff --git a/tests/datetime_container.h b/tests/datetime_container.h index 7fefe04..02d6c81 100644 --- a/tests/datetime_container.h +++ b/tests/datetime_container.h @@ -23,6 +23,5 @@ #pragma once #define REFLECTABLE DatetimeContainer -#define FIELDS \ -MEMBER_DATETIME(creation_date) +#define FIELDS MEMBER_DATETIME(creation_date) #include "reflection.h" diff --git a/tests/person.h b/tests/person.h index eb80c38..3f0e23c 100644 --- a/tests/person.h +++ b/tests/person.h @@ -25,14 +25,14 @@ #include #define REFLECTABLE Person -#define FIELDS \ -MEMBER_TEXT(first_name) \ -MEMBER_TEXT(last_name) \ -MEMBER_INT(age) \ -MEMBER_BOOL(is_vaccinated) \ -FUNC(std::wstring GetFullName() const) +#define FIELDS \ + MEMBER_TEXT(first_name) \ + MEMBER_TEXT(last_name) \ + MEMBER_INT(age) \ + MEMBER_BOOL(is_vaccinated) \ + FUNC(std::wstring GetFullName() const) #include "reflection.h" inline std::wstring Person::GetFullName() const { - return first_name + L" " + last_name; + return first_name + L" " + last_name; } diff --git a/tests/pet.h b/tests/pet.h index 2057a9a..359f568 100644 --- a/tests/pet.h +++ b/tests/pet.h @@ -25,7 +25,7 @@ #include #define REFLECTABLE Pet -#define FIELDS \ -MEMBER_TEXT(name) \ -MEMBER_REAL(weight) +#define FIELDS \ + MEMBER_TEXT(name) \ + MEMBER_REAL(weight) #include "reflection.h" diff --git a/tests/query_predicates_test.cc b/tests/query_predicates_test.cc index 1fc3516..af81f60 100644 --- a/tests/query_predicates_test.cc +++ b/tests/query_predicates_test.cc @@ -20,161 +20,161 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -#include #include "query_predicates.h" +#include + #include "person.h" #include "pet.h" using namespace sqlite_reflection; TEST(QueryPredicatesTest, EqualityInt) { - const Equal condition(&Person::id, 65); - const auto evalution = condition.Evaluate(); - const auto bindings = condition.Bindings(); - EXPECT_EQ(0, strcmp(evalution.data(), "id = ?")); - ASSERT_EQ(1, bindings.size()); - EXPECT_EQ(SqliteStorageClass::kInt, bindings[0].storage_class); - EXPECT_EQ(65, bindings[0].int_value); + const Equal condition(&Person::id, 65); + const auto evalution = condition.Evaluate(); + const auto bindings = condition.Bindings(); + EXPECT_EQ(0, strcmp(evalution.data(), "id = ?")); + ASSERT_EQ(1, bindings.size()); + EXPECT_EQ(SqliteStorageClass::kInt, bindings[0].storage_class); + EXPECT_EQ(65, bindings[0].int_value); } TEST(QueryPredicatesTest, EqualityString) { - const Equal condition(&Person::first_name, L"john"); - const auto evalution = condition.Evaluate(); - const auto bindings = condition.Bindings(); - EXPECT_EQ(0, strcmp(evalution.data(), "first_name = ?")); - ASSERT_EQ(1, bindings.size()); - EXPECT_EQ(SqliteStorageClass::kText, bindings[0].storage_class); - EXPECT_EQ("john", bindings[0].text_value); + const Equal condition(&Person::first_name, L"john"); + const auto evalution = condition.Evaluate(); + const auto bindings = condition.Bindings(); + EXPECT_EQ(0, strcmp(evalution.data(), "first_name = ?")); + ASSERT_EQ(1, bindings.size()); + EXPECT_EQ(SqliteStorageClass::kText, bindings[0].storage_class); + EXPECT_EQ("john", bindings[0].text_value); } TEST(QueryPredicatesTest, EqualityDouble) { - const Equal condition(&Pet::weight, 32.4); - const auto evalution = condition.Evaluate(); - const auto bindings = condition.Bindings(); - EXPECT_EQ(0, strcmp(evalution.data(), "weight = ?")); - ASSERT_EQ(1, bindings.size()); - EXPECT_EQ(SqliteStorageClass::kReal, bindings[0].storage_class); - EXPECT_EQ(32.4, bindings[0].real_value); + const Equal condition(&Pet::weight, 32.4); + const auto evalution = condition.Evaluate(); + const auto bindings = condition.Bindings(); + EXPECT_EQ(0, strcmp(evalution.data(), "weight = ?")); + ASSERT_EQ(1, bindings.size()); + EXPECT_EQ(SqliteStorageClass::kReal, bindings[0].storage_class); + EXPECT_EQ(32.4, bindings[0].real_value); } TEST(QueryPredicatesTest, InequalityInt) { - const Unequal condition(&Person::id, 65); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "id != ?")); + const Unequal condition(&Person::id, 65); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "id != ?")); } TEST(QueryPredicatesTest, InequalityString) { - const Unequal condition(&Person::first_name, L"john"); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "first_name != ?")); + const Unequal condition(&Person::first_name, L"john"); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "first_name != ?")); } TEST(QueryPredicatesTest, InequalityDouble) { - const Unequal condition(&Pet::weight, 32.4); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "weight != ?")); + const Unequal condition(&Pet::weight, 32.4); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "weight != ?")); } TEST(QueryPredicatesTest, GreaterThanInt) { - const GreaterThan condition(&Person::id, 65); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "id > ?")); + const GreaterThan condition(&Person::id, 65); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "id > ?")); } TEST(QueryPredicatesTest, GreaterThanOrEqualInt) { - const GreaterThanOrEqual condition(&Person::id, 65); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "id >= ?")); + const GreaterThanOrEqual condition(&Person::id, 65); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "id >= ?")); } TEST(QueryPredicatesTest, GreaterThanDouble) { - const GreaterThan condition(&Pet::weight, 32.4); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "weight > ?")); + const GreaterThan condition(&Pet::weight, 32.4); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "weight > ?")); } TEST(QueryPredicatesTest, GreaterThanOrEqualDouble) { - const GreaterThanOrEqual condition(&Pet::weight, 32.4); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "weight >= ?")); + const GreaterThanOrEqual condition(&Pet::weight, 32.4); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "weight >= ?")); } TEST(QueryPredicatesTest, SmallerThanInt) { - const SmallerThan condition(&Person::id, 65); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "id < ?")); + const SmallerThan condition(&Person::id, 65); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "id < ?")); } TEST(QueryPredicatesTest, SmallerThanEqualInt) { - const SmallerThanOrEqual condition(&Person::id, 65); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "id <= ?")); + const SmallerThanOrEqual condition(&Person::id, 65); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "id <= ?")); } TEST(QueryPredicatesTest, SmallerThanDouble) { - const SmallerThan condition(&Pet::weight, 32.4); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "weight < ?")); + const SmallerThan condition(&Pet::weight, 32.4); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "weight < ?")); } TEST(QueryPredicatesTest, SmallerThanOrEqualDouble) { - const SmallerThanOrEqual condition(&Pet::weight, 32.4); - const auto evalution = condition.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "weight <= ?")); + const SmallerThanOrEqual condition(&Pet::weight, 32.4); + const auto evalution = condition.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "weight <= ?")); } TEST(QueryPredicatesTest, And) { - const Unequal c1(&Person::id, 65); - const Equal c2(&Person::first_name, L"john"); - const AndPredicate c3(c1, c2); - const auto evalution = c3.Evaluate(); - const auto bindings = c3.Bindings(); - EXPECT_EQ(0, strcmp(evalution.data(), "(id != ? AND first_name = ?)")); - ASSERT_EQ(2, bindings.size()); - EXPECT_EQ(65, bindings[0].int_value); - EXPECT_EQ("john", bindings[1].text_value); + const Unequal c1(&Person::id, 65); + const Equal c2(&Person::first_name, L"john"); + const AndPredicate c3(c1, c2); + const auto evalution = c3.Evaluate(); + const auto bindings = c3.Bindings(); + EXPECT_EQ(0, strcmp(evalution.data(), "(id != ? AND first_name = ?)")); + ASSERT_EQ(2, bindings.size()); + EXPECT_EQ(65, bindings[0].int_value); + EXPECT_EQ("john", bindings[1].text_value); } TEST(QueryPredicatesTest, Or) { - const Unequal c1(&Person::id, 65); - const Equal c2(&Person::first_name, L"john"); - const OrPredicate c3(c1, c2); - const auto evalution = c3.Evaluate(); - EXPECT_EQ(0, strcmp(evalution.data(), "(id != ? OR first_name = ?)")); + const Unequal c1(&Person::id, 65); + const Equal c2(&Person::first_name, L"john"); + const OrPredicate c3(c1, c2); + const auto evalution = c3.Evaluate(); + EXPECT_EQ(0, strcmp(evalution.data(), "(id != ? OR first_name = ?)")); } TEST(QueryPredicatesTest, PredicateChaining) { - const auto predicate = Equal(&Person::id, 65) - .Or(Equal(&Person::first_name, L"john")) - .And(Unequal(&Person::last_name, L"appleseed")); + const auto predicate = + Equal(&Person::id, 65).Or(Equal(&Person::first_name, L"john")).And(Unequal(&Person::last_name, L"appleseed")); - const auto evaluation = predicate.Evaluate(); + const auto evaluation = predicate.Evaluate(); - const auto bindings = predicate.Bindings(); - EXPECT_EQ(0, strcmp(evaluation.data(), "((id = ? OR first_name = ?) AND last_name != ?)")); - ASSERT_EQ(3, bindings.size()); - EXPECT_EQ(65, bindings[0].int_value); - EXPECT_EQ("john", bindings[1].text_value); - EXPECT_EQ("appleseed", bindings[2].text_value); + const auto bindings = predicate.Bindings(); + EXPECT_EQ(0, strcmp(evaluation.data(), "((id = ? OR first_name = ?) AND last_name != ?)")); + ASSERT_EQ(3, bindings.size()); + EXPECT_EQ(65, bindings[0].int_value); + EXPECT_EQ("john", bindings[1].text_value); + EXPECT_EQ("appleseed", bindings[2].text_value); } TEST(QueryPredicatesTest, StringInjectionPayloadStaysInBindings) { - const Equal condition(&Person::first_name, L"john' OR 1=1 --"); - const auto evalution = condition.Evaluate(); - const auto bindings = condition.Bindings(); + const Equal condition(&Person::first_name, L"john' OR 1=1 --"); + const auto evalution = condition.Evaluate(); + const auto bindings = condition.Bindings(); - EXPECT_EQ(0, strcmp(evalution.data(), "first_name = ?")); - ASSERT_EQ(1, bindings.size()); - EXPECT_EQ("john' OR 1=1 --", bindings[0].text_value); + EXPECT_EQ(0, strcmp(evalution.data(), "first_name = ?")); + ASSERT_EQ(1, bindings.size()); + EXPECT_EQ("john' OR 1=1 --", bindings[0].text_value); } TEST(QueryPredicatesTest, LikePayloadStaysInBindings) { - const Like condition(&Person::first_name, L"john%' OR 1=1 --"); - const auto evalution = condition.Evaluate(); - const auto bindings = condition.Bindings(); + const Like condition(&Person::first_name, L"john%' OR 1=1 --"); + const auto evalution = condition.Evaluate(); + const auto bindings = condition.Bindings(); - EXPECT_EQ(0, strcmp(evalution.data(), "first_name LIKE ?")); - ASSERT_EQ(1, bindings.size()); - EXPECT_EQ("%john%' OR 1=1 --%", bindings[0].text_value); + EXPECT_EQ(0, strcmp(evalution.data(), "first_name LIKE ?")); + ASSERT_EQ(1, bindings.size()); + EXPECT_EQ("%john%' OR 1=1 --%", bindings[0].text_value); }