From a2714be30ee11105bd975a3462a4600a6ff031d2 Mon Sep 17 00:00:00 2001 From: silverqx Date: Tue, 22 Mar 2022 09:53:33 +0100 Subject: [PATCH] =?UTF-8?q?added=20schema=20builder=20=F0=9F=94=A5?= =?UTF-8?q?=F0=9F=9A=80=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added practically everything I wanted to have in except for updating columns. Needed to name the schema namespace as Orm::SchemaNs to avoid collision with the Orm::Schema class. - unit tests with great coverage - new schema constants - a new prefix_indexes and engine DB conncetion configurations Others: - IsModel tag - enhanced columnize in the BaseGrammar - used a new columnize logic in all grammars - new constants - new container utils class for joining containers - new DB::driver() getter for QSqlDriver - avoid possible crash in tests with pretend, added empty log checks - clang tidy, excluded to word from short variable names --- .clang-tidy | 4 +- cmake/Modules/TinySources.cmake | 29 +- include/include.pri | 17 +- include/orm/basegrammar.hpp | 34 +- include/orm/constants_extern.hpp | 8 + include/orm/constants_inline.hpp | 10 + include/orm/databaseconnection.hpp | 9 +- include/orm/databasemanager.hpp | 3 + include/orm/db.hpp | 3 + include/orm/ormconcepts.hpp | 16 + include/orm/schema.hpp | 140 ++ include/orm/schema/blueprint.hpp | 690 +++++++ include/orm/schema/columndefinition.hpp | 132 ++ .../orm/schema/columndefinitionreference.hpp | 332 ++++ .../foreignidcolumndefinitionreference.hpp | 63 + .../schema/foreignkeydefinitionreference.hpp | 71 + .../schema/grammars/mysqlschemagrammar.hpp | 268 ++- .../schema/grammars/postgresschemagrammar.hpp | 29 +- include/orm/schema/grammars/schemagrammar.hpp | 95 +- .../schema/grammars/sqliteschemagrammar.hpp | 29 +- include/orm/schema/mysqlschemabuilder.hpp | 24 +- include/orm/schema/postgresschemabuilder.hpp | 6 +- include/orm/schema/schemabuilder.hpp | 96 +- include/orm/schema/schemaconstants.hpp | 16 + include/orm/schema/schemaconstants_extern.hpp | 60 + include/orm/schema/schemaconstants_inline.hpp | 60 + include/orm/schema/schematypes.hpp | 80 + include/orm/schema/sqliteschemabuilder.hpp | 6 +- .../tiny/concerns/queriesrelationships.hpp | 2 +- include/orm/tiny/model.hpp | 5 +- include/orm/tiny/tinybuilder.hpp | 2 +- include/orm/tiny/tinyconcepts.hpp | 6 + include/orm/tiny/tinytypes.hpp | 4 + include/orm/utils/container.hpp | 59 + src/orm/basegrammar.cpp | 29 +- src/orm/connectors/connectionfactory.cpp | 5 + src/orm/connectors/postgresconnector.cpp | 7 +- src/orm/constants_extern.cpp | 8 + src/orm/databaseconnection.cpp | 5 + src/orm/databasemanager.cpp | 14 +- src/orm/db.cpp | 5 + src/orm/mysqlconnection.cpp | 4 +- src/orm/postgresconnection.cpp | 4 +- src/orm/query/grammars/grammar.cpp | 26 +- src/orm/query/grammars/mysqlgrammar.cpp | 1 + src/orm/query/grammars/postgresgrammar.cpp | 2 +- src/orm/query/grammars/sqlitegrammar.cpp | 2 +- src/orm/schema.cpp | 178 ++ src/orm/schema/blueprint.cpp | 733 ++++++++ .../foreignidcolumndefinitionreference.cpp | 43 + .../schema/foreignkeydefinitionreference.cpp | 82 + .../schema/grammars/mysqlschemagrammar.cpp | 919 +++++++++- .../schema/grammars/postgresschemagrammar.cpp | 46 +- src/orm/schema/grammars/schemagrammar.cpp | 156 ++ .../schema/grammars/sqliteschemagrammar.cpp | 48 +- src/orm/schema/mysqlschemabuilder.cpp | 92 +- src/orm/schema/postgresschemabuilder.cpp | 6 +- src/orm/schema/schemabuilder.cpp | 186 +- src/orm/schema/schemaconstants_extern.cpp | 40 + src/orm/sqliteconnection.cpp | 4 +- src/src.pri | 9 +- tests/TinyUtils/src/databases.cpp | 14 +- tests/auto/unit/orm/CMakeLists.txt | 1 + tests/auto/unit/orm/orm.pro | 1 + .../tst_mysql_querybuilder.cpp | 25 + .../tst_postgresql_querybuilder.cpp | 16 + .../tst_sqlite_querybuilder.cpp | 16 + tests/auto/unit/orm/schema/CMakeLists.txt | 2 + .../unit/orm/schema/blueprint/CMakeLists.txt | 12 + .../unit/orm/schema/blueprint/blueprint.pro | 4 + .../orm/schema/blueprint/tst_blueprint.cpp | 259 +++ .../schema/mysql_schemabuilder/CMakeLists.txt | 12 + .../mysql_schemabuilder.pro | 5 + .../tst_mysql_schemabuilder.cpp | 1622 +++++++++++++++++ tests/auto/unit/orm/schema/schema.pro | 5 + 75 files changed, 6939 insertions(+), 117 deletions(-) create mode 100644 include/orm/schema.hpp create mode 100644 include/orm/schema/blueprint.hpp create mode 100644 include/orm/schema/columndefinition.hpp create mode 100644 include/orm/schema/columndefinitionreference.hpp create mode 100644 include/orm/schema/foreignidcolumndefinitionreference.hpp create mode 100644 include/orm/schema/foreignkeydefinitionreference.hpp create mode 100644 include/orm/schema/schemaconstants.hpp create mode 100644 include/orm/schema/schemaconstants_extern.hpp create mode 100644 include/orm/schema/schemaconstants_inline.hpp create mode 100644 include/orm/schema/schematypes.hpp create mode 100644 include/orm/utils/container.hpp create mode 100644 src/orm/schema.cpp create mode 100644 src/orm/schema/blueprint.cpp create mode 100644 src/orm/schema/foreignidcolumndefinitionreference.cpp create mode 100644 src/orm/schema/foreignkeydefinitionreference.cpp create mode 100644 src/orm/schema/grammars/schemagrammar.cpp create mode 100644 src/orm/schema/schemaconstants_extern.cpp create mode 100644 tests/auto/unit/orm/schema/CMakeLists.txt create mode 100644 tests/auto/unit/orm/schema/blueprint/CMakeLists.txt create mode 100644 tests/auto/unit/orm/schema/blueprint/blueprint.pro create mode 100644 tests/auto/unit/orm/schema/blueprint/tst_blueprint.cpp create mode 100644 tests/auto/unit/orm/schema/mysql_schemabuilder/CMakeLists.txt create mode 100644 tests/auto/unit/orm/schema/mysql_schemabuilder/mysql_schemabuilder.pro create mode 100644 tests/auto/unit/orm/schema/mysql_schemabuilder/tst_mysql_schemabuilder.cpp create mode 100644 tests/auto/unit/orm/schema/schema.pro diff --git a/.clang-tidy b/.clang-tidy index 981a44990..f82c70b30 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: '-*,bugprone-*,clang-*,concurrency-*,cppcoreguidelines-*,google-*,llvm-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-parameters,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-c-copy-assignment-signature,-cppcoreguidelines-macro-usage,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-const-cast,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-special-member-functions,-google-default-arguments,-google-readability-braces-around-statements,-google-readability-function-size,-google-readability-todo,-llvm-header-guard,-misc-non-private-member-variables-in-classes,-modernize-use-nodiscard,-modernize-use-trailing-return-type,-performance-move-const-arg,-readability-braces-around-statements,-readability-convert-member-functions-to-static,-readability-magic-numbers' +Checks: '-*,bugprone-*,clang-*,concurrency-*,cppcoreguidelines-*,google-*,llvm-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-bugprone-easily-swappable-parameters,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-avoid-non-const-global-variables,-cppcoreguidelines-c-copy-assignment-signature,-cppcoreguidelines-macro-usage,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-const-cast,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-special-member-functions,-google-default-arguments,-google-readability-braces-around-statements,-google-readability-function-size,-google-readability-todo,-llvm-else-after-return,-llvm-header-guard,-misc-non-private-member-variables-in-classes,-modernize-use-nodiscard,-modernize-use-trailing-return-type,-performance-move-const-arg,-readability-braces-around-statements,-readability-convert-member-functions-to-static,-readability-magic-numbers' WarningsAsErrors: '*' HeaderFilterRegex: 'orm/.+\.(h|hpp)$' FormatStyle: none @@ -9,3 +9,5 @@ CheckOptions: value: '5' - key: llvm-namespace-comment.ShortNamespaceLines value: '5' + - key: readability-identifier-length.IgnoredParameterNames + value: '^to$' diff --git a/cmake/Modules/TinySources.cmake b/cmake/Modules/TinySources.cmake index fa14a27e6..d1ac0a952 100644 --- a/cmake/Modules/TinySources.cmake +++ b/cmake/Modules/TinySources.cmake @@ -5,9 +5,15 @@ function(tiny_sources out_headers out_sources) set(headers) if(TINY_EXTERN_CONSTANTS) - list(APPEND headers constants_extern.hpp) + list(APPEND headers + constants_extern.hpp + schema/schemaconstants_extern.hpp + ) else() - list(APPEND headers constants_inline.hpp) + list(APPEND headers + constants_inline.hpp + schema/schemaconstants_inline.hpp + ) endif() list(APPEND headers @@ -61,6 +67,12 @@ function(tiny_sources out_headers out_sources) query/processors/processor.hpp query/processors/sqliteprocessor.hpp query/querybuilder.hpp + schema.hpp + schema/blueprint.hpp + schema/columndefinition.hpp + schema/columndefinitionreference.hpp + schema/foreignidcolumndefinitionreference.hpp + schema/foreignkeydefinitionreference.hpp schema/grammars/mysqlschemagrammar.hpp schema/grammars/postgresschemagrammar.hpp schema/grammars/schemagrammar.hpp @@ -68,6 +80,8 @@ function(tiny_sources out_headers out_sources) schema/mysqlschemabuilder.hpp schema/postgresschemabuilder.hpp schema/schemabuilder.hpp + schema/schemaconstants.hpp + schema/schematypes.hpp schema/sqliteschemabuilder.hpp sqliteconnection.hpp support/configurationoptionsparser.hpp @@ -75,6 +89,7 @@ function(tiny_sources out_headers out_sources) support/databaseconnectionsmap.hpp types/log.hpp types/statementscounter.hpp + utils/container.hpp utils/fs.hpp utils/query.hpp utils/thread.hpp @@ -123,7 +138,10 @@ function(tiny_sources out_headers out_sources) set(sources) if(TINY_EXTERN_CONSTANTS) - list(APPEND sources constants_extern.cpp) + list(APPEND sources + constants_extern.cpp + schema/schemaconstants_extern.cpp + ) endif() list(APPEND sources @@ -158,8 +176,13 @@ function(tiny_sources out_headers out_sources) query/processors/processor.cpp query/processors/sqliteprocessor.cpp query/querybuilder.cpp + schema.cpp + schema/blueprint.cpp + schema/foreignidcolumndefinitionreference.cpp + schema/foreignkeydefinitionreference.cpp schema/grammars/mysqlschemagrammar.cpp schema/grammars/postgresschemagrammar.cpp + schema/grammars/schemagrammar.cpp schema/grammars/sqliteschemagrammar.cpp schema/mysqlschemabuilder.cpp schema/postgresschemabuilder.cpp diff --git a/include/include.pri b/include/include.pri index 606e9b7df..c0cc97c6b 100644 --- a/include/include.pri +++ b/include/include.pri @@ -1,9 +1,13 @@ INCLUDEPATH += $$PWD extern_constants: \ - headersList += $$PWD/orm/constants_extern.hpp + headersList += \ + $$PWD/orm/constants_extern.hpp \ + $$PWD/orm/schema/schemaconstants_extern.hpp else: \ - headersList += $$PWD/orm/constants_inline.hpp + headersList += \ + $$PWD/orm/constants_inline.hpp \ + $$PWD/orm/schema/schemaconstants_inline.hpp headersList += \ $$PWD/orm/basegrammar.hpp \ @@ -59,6 +63,12 @@ headersList += \ $$PWD/orm/query/processors/processor.hpp \ $$PWD/orm/query/processors/sqliteprocessor.hpp \ $$PWD/orm/query/querybuilder.hpp \ + $$PWD/orm/schema.hpp \ + $$PWD/orm/schema/blueprint.hpp \ + $$PWD/orm/schema/columndefinition.hpp \ + $$PWD/orm/schema/columndefinitionreference.hpp \ + $$PWD/orm/schema/foreignidcolumndefinitionreference.hpp \ + $$PWD/orm/schema/foreignkeydefinitionreference.hpp \ $$PWD/orm/schema/grammars/mysqlschemagrammar.hpp \ $$PWD/orm/schema/grammars/postgresschemagrammar.hpp \ $$PWD/orm/schema/grammars/schemagrammar.hpp \ @@ -66,6 +76,8 @@ headersList += \ $$PWD/orm/schema/mysqlschemabuilder.hpp \ $$PWD/orm/schema/postgresschemabuilder.hpp \ $$PWD/orm/schema/schemabuilder.hpp \ + $$PWD/orm/schema/schemaconstants.hpp \ + $$PWD/orm/schema/schematypes.hpp \ $$PWD/orm/schema/sqliteschemabuilder.hpp \ $$PWD/orm/sqliteconnection.hpp \ $$PWD/orm/support/configurationoptionsparser.hpp \ @@ -73,6 +85,7 @@ headersList += \ $$PWD/orm/support/databaseconnectionsmap.hpp \ $$PWD/orm/types/log.hpp \ $$PWD/orm/types/statementscounter.hpp \ + $$PWD/orm/utils/container.hpp \ $$PWD/orm/utils/fs.hpp \ $$PWD/orm/utils/query.hpp \ $$PWD/orm/utils/thread.hpp \ diff --git a/include/orm/basegrammar.hpp b/include/orm/basegrammar.hpp index 8ae37fe1a..f9fc99da6 100644 --- a/include/orm/basegrammar.hpp +++ b/include/orm/basegrammar.hpp @@ -5,7 +5,9 @@ #include "orm/macros/systemheader.hpp" TINY_SYSTEM_HEADER +#include "orm/ormconcepts.hpp" #include "orm/ormtypes.hpp" +#include "orm/utils/container.hpp" TINYORM_BEGIN_COMMON_NAMESPACE @@ -16,12 +18,6 @@ namespace Query class Expression; } // namespace Query - /*! QString container concept (QStringList or QVector). */ - template - concept ColumnContainer = std::convertible_to || - std::convertible_to &> || - std::convertible_to &>; - /*! Concept for container passed to the parametrize() method (QVariantMap or QVector). */ template @@ -33,6 +29,7 @@ namespace Query { Q_DISABLE_COPY(BaseGrammar) + protected: /*! Expression alias. */ using Expression = Query::Expression; @@ -82,9 +79,12 @@ namespace Query QString unqualifyColumn(const QString &column) const; protected: - /*! Convert the vector of column names into a delimited string. */ + /*! Convert the vector of column names into a wrapped comma delimited string. */ template - inline QString columnize(const T &columns) const; + QString columnize(T &&columns) const; + /*! Convert the vector of column names into a comma delimited string. */ + template + QString columnizeWithoutWrap(T &&columns) const; /*! Create query parameter place-holders for the vector. */ template @@ -110,16 +110,20 @@ namespace Query // FEATURE qt6, use everywhere QLatin1String("") instead of = "", BUT Qt6 has char8_t ctor, so u"" can be used, I will wait with this problem silverqx /*! The grammar table prefix. */ QString m_tablePrefix {}; - - private: - /*! Convert the vector of column names into a delimited string. */ - QString columnizeInternal(const QVector &columns) const; }; template - QString BaseGrammar::columnize(const T &columns) const + QString BaseGrammar::columnize(T &&columns) const { - return columnizeInternal(wrapArray(columns)); + return columnizeWithoutWrap(wrapArray(std::forward(columns))); + } + + /* I leave this method here because it has meaningful name, not make it inline to avoid + utils/container.hpp include in the header file. */ + template + QString BaseGrammar::columnizeWithoutWrap(T &&columns) const + { + return Utils::Container::join(std::forward(columns)); } QString BaseGrammar::getTablePrefix() const @@ -168,7 +172,7 @@ namespace Query compiledParameters << parameter(value); // CUR1 QString allocation 😟 solve everywhere 😭 silverqx - return compiledParameters.join(COMMA); + return columnizeWithoutWrap(compiledParameters); } } // namespace Orm diff --git a/include/orm/constants_extern.hpp b/include/orm/constants_extern.hpp index 356f3fecc..839f5a285 100644 --- a/include/orm/constants_extern.hpp +++ b/include/orm/constants_extern.hpp @@ -49,6 +49,8 @@ namespace Orm::Constants SHAREDLIB_EXPORT extern const QString UPDATED_AT; SHAREDLIB_EXPORT extern const QString PARENTH_ONE; SHAREDLIB_EXPORT extern const QString NEWLINE; + SHAREDLIB_EXPORT extern const QString SPACE_IN; + SHAREDLIB_EXPORT extern const QString NOSPACE; SHAREDLIB_EXPORT extern const QString QMYSQL; SHAREDLIB_EXPORT extern const QString QPSQL; @@ -70,10 +72,12 @@ namespace Orm::Constants SHAREDLIB_EXPORT extern const QString prefix_; SHAREDLIB_EXPORT extern const QString options_; SHAREDLIB_EXPORT extern const QString strict_; + SHAREDLIB_EXPORT extern const QString engine_; SHAREDLIB_EXPORT extern const QString isolation_level; SHAREDLIB_EXPORT extern const QString foreign_key_constraints; SHAREDLIB_EXPORT extern const QString check_database_exists; + SHAREDLIB_EXPORT extern const QString prefix_indexes; SHAREDLIB_EXPORT extern const QString H127001; SHAREDLIB_EXPORT extern const QString LOCALHOST; @@ -86,6 +90,10 @@ namespace Orm::Constants SHAREDLIB_EXPORT extern const QString PUBLIC; SHAREDLIB_EXPORT extern const QString UTF8; SHAREDLIB_EXPORT extern const QString UTF8MB4; + SHAREDLIB_EXPORT extern const QString InnoDB; + SHAREDLIB_EXPORT extern const QString MyISAM; + + SHAREDLIB_EXPORT extern const QString NotImplemented; // Comparison/logical/search operators SHAREDLIB_EXPORT extern const QString EQ; diff --git a/include/orm/constants_inline.hpp b/include/orm/constants_inline.hpp index 7d60d4af4..82f163494 100644 --- a/include/orm/constants_inline.hpp +++ b/include/orm/constants_inline.hpp @@ -14,6 +14,7 @@ TINYORM_BEGIN_COMMON_NAMESPACE /*! Namespace constains common chars and strings. */ namespace Orm::Constants { + // CUR1 sort it silverqx // Common chars inline const QChar SPACE = QChar(' '); @@ -48,6 +49,8 @@ namespace Orm::Constants inline const QString UPDATED_AT = QStringLiteral("updated_at"); inline const QString PARENTH_ONE = QStringLiteral("(%1)"); inline const QString NEWLINE = QStringLiteral("\n"); + inline const QString SPACE_IN = QStringLiteral("%1 %2"); + inline const QString NOSPACE = QStringLiteral("%1%2"); inline const QString QMYSQL = QStringLiteral("QMYSQL"); inline const QString QPSQL = QStringLiteral("QPSQL"); @@ -69,6 +72,7 @@ namespace Orm::Constants inline const QString prefix_ = QStringLiteral("prefix"); inline const QString options_ = QStringLiteral("options"); inline const QString strict_ = QStringLiteral("strict"); + inline const QString engine_ = QStringLiteral("engine"); inline const QString isolation_level = QStringLiteral("isolation_level"); @@ -76,6 +80,8 @@ namespace Orm::Constants foreign_key_constraints = QStringLiteral("foreign_key_constraints"); inline const QString check_database_exists = QStringLiteral("check_database_exists"); + inline const QString + prefix_indexes = QStringLiteral("prefix_indexes"); inline const QString H127001 = QStringLiteral("127.0.0.1"); inline const QString LOCALHOST = QStringLiteral("localhost"); @@ -88,6 +94,10 @@ namespace Orm::Constants inline const QString PUBLIC = QStringLiteral("public"); inline const QString UTF8 = QStringLiteral("utf8"); inline const QString UTF8MB4 = QStringLiteral("utf8mb4"); + inline const QString InnoDB = QStringLiteral("InnoDB"); + inline const QString MyISAM = QStringLiteral("MyISAM"); + + inline const QString NotImplemented = QStringLiteral("Not implemented :/."); // Comparison/logical/search operators inline const QString EQ = QStringLiteral("="); diff --git a/include/orm/databaseconnection.hpp b/include/orm/databaseconnection.hpp index 6cf5b437b..f5985583e 100644 --- a/include/orm/databaseconnection.hpp +++ b/include/orm/databaseconnection.hpp @@ -35,7 +35,7 @@ namespace Processors } } // namespace Query -namespace Schema +namespace SchemaNs { class SchemaBuilder; } @@ -45,9 +45,9 @@ namespace Schema /*! QueryProcessor alias. */ using QueryProcessor = Query::Processors::Processor; /*! SchemaBuilder alias. */ - using SchemaBuilder = Schema::SchemaBuilder; + using SchemaBuilder = SchemaNs::SchemaBuilder; /*! SchemaGrammar alias. */ - using SchemaGrammar = Schema::Grammars::SchemaGrammar; + using SchemaGrammar = SchemaNs::Grammars::SchemaGrammar; /*! Database connection base class. TinyORM's DatabaseConnection never physically connects to the database after @@ -162,6 +162,9 @@ namespace Schema /*! Check database connection and show warnings when the state changed. */ virtual bool pingDatabase(); + /*! Returns the database driver used to access the database connection. */ + QSqlDriver *driver(); + /*! Reconnect to the database (doesn't create physical connection, only refreshs a connection resolver). */ void reconnect() const; diff --git a/include/orm/databasemanager.hpp b/include/orm/databasemanager.hpp index b52da0f3f..4fb2ba3cc 100644 --- a/include/orm/databasemanager.hpp +++ b/include/orm/databasemanager.hpp @@ -121,6 +121,9 @@ namespace Query /*! Check database connection and show warnings when the state changed. */ bool pingDatabase(const QString &connection = ""); + /*! Returns the database driver used to access the database connection. */ + QSqlDriver *driver(const QString &connection = ""); + /* DatabaseManager */ /*! Obtain a shared pointer to the DatabaseManager. */ static std::shared_ptr instance(); diff --git a/include/orm/db.hpp b/include/orm/db.hpp index 377c76502..3d59775e3 100644 --- a/include/orm/db.hpp +++ b/include/orm/db.hpp @@ -165,6 +165,9 @@ namespace Orm /*! Check database connection and show warnings when the state changed. */ static bool pingDatabase(const QString &connection = ""); + /*! Returns the database driver used to access the database connection. */ + static QSqlDriver *driver(const QString &connection = ""); + /* Queries execution time counter */ /*! Determine whether we're counting queries execution time. */ static bool diff --git a/include/orm/ormconcepts.hpp b/include/orm/ormconcepts.hpp index 328a9c356..2a49a3f5c 100644 --- a/include/orm/ormconcepts.hpp +++ b/include/orm/ormconcepts.hpp @@ -61,6 +61,22 @@ namespace Query concept QStringConcept = std::convertible_to || std::convertible_to; + /*! Type for the database column. */ + using Column = std::variant; + + /*! QString container concept (QStringList or QVector). */ + template + concept ColumnContainer = std::convertible_to || + std::convertible_to &> || + std::convertible_to &>; + + // CUR concepts, check if const QString & vs QString in convertible_to<> makes sense silverqx + /*! Concept for delimiter for joining containers. */ + template + concept DelimiterConcept = std::convertible_to || + std::convertible_to || + std::convertible_to; + } // namespace Orm TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema.hpp b/include/orm/schema.hpp new file mode 100644 index 000000000..f45a59afc --- /dev/null +++ b/include/orm/schema.hpp @@ -0,0 +1,140 @@ +#pragma once +#ifndef ORM_SCHEMA_HPP +#define ORM_SCHEMA_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include "orm/databasemanager.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm +{ + + /*! Facade class for the Schema builder. */ + class SHAREDLIB_EXPORT Schema final + { + Q_DISABLE_COPY(Schema) + + // To access m_schemaBuildersCache + friend bool DatabaseManager::removeConnection(const QString &name); + + /*! Alias for the Blueprint. */ + using Blueprint = SchemaNs::Blueprint; + + public: + /*! Deleted default constructor, this is a pure library class. */ + Schema() = delete; + /*! Deleted destructor. */ + ~Schema() = delete; + + /* Proxy methods to the SchemaBuilder */ + /*! Create a database in the schema. */ + static QSqlQuery createDatabase(const QString &name, + const QString &connection = ""); + /*! Drop a database from the schema if the database exists. */ + static QSqlQuery dropDatabaseIfExists(const QString &name, + const QString &connection = ""); + + /*! Create a new table on the schema. */ + static void create(const QString &table, + const std::function &callback, + const QString &connection = ""); + /*! Modify a table on the schema. */ + static void table(const QString &table, + const std::function &callback, + const QString &connection = ""); + /*! Drop a table from the schema. */ + static void drop(const QString &table, + const QString &connection = ""); + /*! Drop a table from the schema if it exists. */ + static void dropIfExists(const QString &table, + const QString &connection = ""); + + /*! Rename a table on the schema. */ + static void rename(const QString &from, const QString &to, + const QString &connection = ""); + + /*! Drop columns from a table schema. */ + static void dropColumns(const QString &table, const QVector &columns, + const QString &connection = ""); + /*! Drop columns from a table schema. */ + template + static void dropColumns(const QString &table, Args &&...columns); + /*! Drop one column from a table schema. */ + static void dropColumn(const QString &table, const QString &column, + const QString &connection = ""); + + /*! Rename the given column on the schema. */ + static void renameColumn(const QString &table, const QString &from, + const QString &to, const QString &connection = ""); + + /*! Drop all tables from the database. */ + static void dropAllTables(const QString &connection = ""); + /*! Drop all views from the database. */ + static void dropAllViews(const QString &connection = ""); + /*! Drop all types from the database. */ + static void dropAllTypes(const QString &connection = ""); + + /*! Get all of the table names for the database. */ + static QSqlQuery getAllTables(const QString &connection = ""); + /*! Get all of the view names for the database. */ + static QSqlQuery getAllViews(const QString &connection = ""); + + /*! Enable foreign key constraints. */ + static QSqlQuery enableForeignKeyConstraints(const QString &connection = ""); + /*! Disable foreign key constraints. */ + static QSqlQuery disableForeignKeyConstraints(const QString &connection = ""); + + /*! Get the column listing for a given table. */ + static QStringList getColumnListing(const QString &table, + const QString &connection = ""); + + /*! Determine if the given table exists. */ + static bool hasTable(const QString &table, const QString &connection = ""); + /*! Determine if the given table has a given column. */ + static bool hasColumn(const QString &table, const QString &column, + const QString &connection = ""); + /*! Determine if the given table has given columns. */ + static bool hasColumns(const QString &table, const QVector &columns, + const QString &connection = ""); + + /* Schema */ + /*! Get a schema builder instance for the default connection. */ + static SchemaBuilder &connection(const QString &name = ""); + /*! Get a schema builder instance for a connection. (alias for the connection() + method). */ + static SchemaBuilder &on(const QString &name); + + /* Others */ + /*! Set the default string length for migrations. */ + static void defaultStringLength(int length); + + private: + /*! Alias for the schema builder. */ + using SchemaBuilder = SchemaNs::SchemaBuilder; + + /*! Get a reference to the SchemaBuilder. */ + static SchemaBuilder &schemaBuilder(const QString &connection = ""); + /*! Get a reference to the DatabaseManager. */ + static DatabaseManager &manager(); + + /*! Schema builders cache by connection name. */ + static std::unordered_map> m_schemaBuildersCache; + /*! Pointer to the DatabaseManager. */ + static std::shared_ptr m_manager; + }; + + template + void Schema::dropColumns(const QString &table, Args &&...columns) + { + dropColumns(table, {std::forward(columns)...}); + } + +} // namespace Orm + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_HPP diff --git a/include/orm/schema/blueprint.hpp b/include/orm/schema/blueprint.hpp new file mode 100644 index 000000000..57cbb0df2 --- /dev/null +++ b/include/orm/schema/blueprint.hpp @@ -0,0 +1,690 @@ +#pragma once +#ifndef ORM_SCHEMA_BLUEPRINT_HPP +#define ORM_SCHEMA_BLUEPRINT_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include "orm/ormconcepts.hpp" +#include "orm/schema/foreignidcolumndefinitionreference.hpp" +#include "orm/schema/schemaconstants.hpp" +#ifndef TINYORM_DISABLE_ORM +# include "orm/tiny/tinytypes.hpp" +#endif + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm +{ + class DatabaseConnection; + +namespace SchemaNs +{ +namespace Grammars +{ + class SchemaGrammar; +} + + /*! Table blueprint for the schema grammar. */ + class SHAREDLIB_EXPORT Blueprint + { + Q_DISABLE_COPY(Blueprint) + + /*! Alias for the Query::Expression. */ + using Expression = Query::Expression; + /*! Alias for the SchemaGrammar. */ + using SchemaGrammar = Grammars::SchemaGrammar; + + public: + /*! Constructor. */ + explicit Blueprint(QString table, + const std::function &callback = nullptr, + QString &&prefix = ""); + /*! Default destructor. */ + inline ~Blueprint() = default; + + /*! Execute the blueprint against the database. */ + void build(DatabaseConnection &connection, const SchemaGrammar &grammar); + /*! Get the raw SQL statements for the blueprint. */ + QVector + toSql(const DatabaseConnection &connection, const SchemaGrammar &grammar); + + /*! Indicate that the table needs to be created. */ + ColumnDefinitionReference<> create(); + /*! Add the columns from the callback after the given column. */ + void after(const QString &column, + const std::function &callback); + /*! Indicate that the table should be dropped. */ + ColumnDefinitionReference<> drop(); + /*! Indicate that the table should be dropped if it exists. */ + ColumnDefinitionReference<> dropIfExists(); + + /*! Indicate that the given columns should be dropped. */ + ColumnDefinitionReference<> dropColumns(const QVector &columns); + /*! Indicate that the given columns should be dropped. */ + template + ColumnDefinitionReference<> dropColumns(Args &&...columns); + /*! Indicate that the given columns should be dropped. */ + ColumnDefinitionReference<> dropColumn(const QString &column); + + /*! Indicate that the timestamp columns should be dropped. */ + void dropTimestamps(); + /*! Indicate that the timestamp columns should be dropped. */ + inline void dropTimestampsTz(); + /*! Indicate that the remember token column should be dropped. */ + inline void dropRememberToken(); + + /*! Rename the table to a given name. */ + ColumnDefinitionReference<> rename(const QString &to); + + /*! Indicate that the given columns should be renamed. */ + ColumnDefinitionReference<> renameColumn(const QString &from, const QString &to); + + /*! Specify the primary key(s) for the table. */ + ColumnDefinitionReference<> + primary(const QVector &columns, const QString &name = "", + const QString &algorithm = ""); + /*! Specify the primary key(s) for the table. */ + template + ColumnDefinitionReference<> + primary(const QString &column, const QString &name = "", + const QString &algorithm = ""); + /*! Specify a unique index for the table. */ + ColumnDefinitionReference<> + unique(const QVector &columns, const QString &name = "", + const QString &algorithm = ""); + /*! Specify a unique index for the table. */ + template + ColumnDefinitionReference<> + unique(const QString &column, const QString &name = "", + const QString &algorithm = ""); + /*! Specify an index for the table. */ + ColumnDefinitionReference<> + index(const QVector &columns, const QString &name = "", + const QString &algorithm = ""); + /*! Specify an index for the table. */ + template + ColumnDefinitionReference<> + index(const QString &column, const QString &name = "", + const QString &algorithm = ""); + /*! Specify an fulltext for the table. */ + ColumnDefinitionReference<> + fullText(const QVector &columns, const QString &name = "", + const QString &algorithm = ""); + /*! Specify an fulltext for the table. */ + template + ColumnDefinitionReference<> + fullText(const QString &column, const QString &name = "", + const QString &algorithm = ""); + /*! Specify a spatial index for the table. */ + ColumnDefinitionReference<> + spatialIndex(const QVector &columns, const QString &name = ""); + /*! Specify a spatial index for the table. */ + template + ColumnDefinitionReference<> + spatialIndex(const QString &column, const QString &name = ""); + /*! Specify a raw index for the table. */ + ColumnDefinitionReference<> + rawIndex(const Expression &expression, const QString &name); + /*! Specify a foreign key for the table. */ + ForeignKeyDefinitionReference + foreign(const QVector &columns, const QString &name = ""); + /*! Specify a foreign key for the table. */ + template + ForeignKeyDefinitionReference + foreign(const QString &column, const QString &name = ""); + + /*! Drop primary key by the given column names. */ + ColumnDefinitionReference<> dropPrimary(const QVector &columns = {}); + /*! Drop unique key by the given column names. */ + ColumnDefinitionReference<> dropUnique(const QVector &columns); + /*! Drop index by the given column names. */ + ColumnDefinitionReference<> dropIndex(const QVector &columns); + /*! Drop fulltext index by the given column names. */ + ColumnDefinitionReference<> dropFullText(const QVector &columns); + /*! Drop spatial index by the given column names. */ + ColumnDefinitionReference<> dropSpatialIndex(const QVector &columns); + /*! Drop foreign key by the given column names. */ + ColumnDefinitionReference<> dropForeign(const QVector &columns); + /*! Drop a column and foreign key by the given column name. */ + ColumnDefinitionReference<> dropConstrainedForeignId(const QString &column); + + /*! Drop primary key by the given index name. */ + template + ColumnDefinitionReference<> dropPrimary(const QString &index = ""); + /*! Drop unique key by the given index name. */ + template + ColumnDefinitionReference<> dropUnique(const QString &index); + /*! Drop index by the given index name. */ + template + ColumnDefinitionReference<> dropIndex(const QString &index); + /*! Drop fulltext index by the given index name. */ + template + ColumnDefinitionReference<> dropFullText(const QString &index); + /*! Drop spatial index by the given index name. */ + template + ColumnDefinitionReference<> dropSpatialIndex(const QString &index); + /*! Drop foreign key by the given index name. */ + template + ColumnDefinitionReference<> dropForeign(const QString &index); + + /*! Indicate that the given indexes should be renamed. */ + ColumnDefinitionReference<> renameIndex(const QString &from, const QString &to); + + /*! Create a new auto-incrementing big integer (8-byte) column on the table. */ + inline ColumnDefinitionReference<> id(const QString &column = Orm::Constants::ID); + + /*! Create a new unsigned big integer (8-byte) column on the table. */ + ForeignIdColumnDefinitionReference foreignId(const QString &column); +#ifndef TINYORM_DISABLE_ORM + /*! Create a foreign ID column for the given model. */ + template + ForeignIdColumnDefinitionReference foreignIdFor(const Model &model, + const QString &column = ""); +#endif + /*! Create a new UUID column on the table with a foreign key constraint. */ + ForeignIdColumnDefinitionReference foreignUuid(const QString &column); + + /*! Create a new auto-incrementing integer (4-byte) column on the table. */ + inline ColumnDefinitionReference<> increments(const QString &column); + /*! Create a new auto-incrementing integer (4-byte) column on the table. */ + inline ColumnDefinitionReference<> integerIncrements(const QString &column); + /*! Create a new auto-incrementing tiny integer (1-byte) column on the table. */ + inline ColumnDefinitionReference<> tinyIncrements(const QString &column); + /*! Create a new auto-incrementing small integer (2-byte) column on the table. */ + inline ColumnDefinitionReference<> smallIncrements(const QString &column); + /*! Create a new auto-incrementing medium integer (3-byte) column on the table. */ + inline ColumnDefinitionReference<> mediumIncrements(const QString &column); + /*! Create a new auto-incrementing big integer (8-byte) column on the table. */ + inline ColumnDefinitionReference<> bigIncrements(const QString &column); + + /*! Create a new integer (4-byte) column on the table. */ + ColumnDefinitionReference<> + integer(const QString &column, bool autoIncrement = false, + bool isUnsigned = false); + /*! Create a new tiny integer (1-byte) column on the table. */ + ColumnDefinitionReference<> + tinyInteger(const QString &column, bool autoIncrement = false, + bool isUnsigned = false); + /*! Create a new small integer (2-byte) column on the table. */ + ColumnDefinitionReference<> + smallInteger(const QString &column, bool autoIncrement = false, + bool isUnsigned = false); + /*! Create a new medium integer (3-byte) column on the table. */ + ColumnDefinitionReference<> + mediumInteger(const QString &column, bool autoIncrement = false, + bool isUnsigned = false); + /*! Create a new unsigned big integer (8-byte) column on the table. */ + ColumnDefinitionReference<> + bigInteger(const QString &column, bool autoIncrement = false, + bool isUnsigned = false); + + /*! Create a new unsigned integer (4-byte) column on the table. */ + inline ColumnDefinitionReference<> + unsignedInteger(const QString &column, bool autoIncrement = false); + /*! Create a new unsigned tiny integer (1-byte) column on the table. */ + inline ColumnDefinitionReference<> + unsignedTinyInteger(const QString &column, bool autoIncrement = false); + /*! Create a new unsigned small integer (2-byte) column on the table. */ + inline ColumnDefinitionReference<> + unsignedSmallInteger(const QString &column, bool autoIncrement = false); + /*! Create a new unsigned medium integer (3-byte) column on the table. */ + inline ColumnDefinitionReference<> + unsignedMediumInteger(const QString &column, bool autoIncrement = false); + /*! Create a new unsigned big integer (8-byte) column on the table. */ + inline ColumnDefinitionReference<> + unsignedBigInteger(const QString &column, bool autoIncrement = false); + + /*! Create a new char column on the table. */ + ColumnDefinitionReference<> + Char(const QString &column, int length = DefaultStringLength); + + /*! Create a new string column on the table. */ + ColumnDefinitionReference<> string(const QString &column, + int length = DefaultStringLength); + /*! Create a new tiny text column on the table. */ + ColumnDefinitionReference<> tinyText(const QString &column); + /*! Create a new text column on the table. */ + ColumnDefinitionReference<> text(const QString &column); + /*! Create a new medium text column on the table. */ + ColumnDefinitionReference<> mediumText(const QString &column); + /*! Create a new long text column on the table. */ + ColumnDefinitionReference<> longText(const QString &column); + + /*! Create a new float column on the table. */ + ColumnDefinitionReference<> + Float(const QString &column, std::optional total = 8, + std::optional places = 2, bool isUnsigned = false); + /*! Create a new double column on the table. */ + ColumnDefinitionReference<> + Double(const QString &column, std::optional total = std::nullopt, + std::optional places = std::nullopt, bool isUnsigned = false); + /*! Create a new decimal column on the table. */ + ColumnDefinitionReference<> + decimal(const QString &column, std::optional total = 8, + std::optional places = 2, bool isUnsigned = false); + + /*! Create a new unsigned float column on the table. */ + inline ColumnDefinitionReference<> + unsignedFloat(const QString &column, std::optional total = 8, + std::optional places = 2); + /*! Create a new unsigned double column on the table. */ + inline ColumnDefinitionReference<> + unsignedDouble(const QString &column, std::optional total = std::nullopt, + std::optional places = std::nullopt); + /*! Create a new unsigned decimal column on the table. */ + inline ColumnDefinitionReference<> + unsignedDecimal(const QString &column, std::optional total = 8, + std::optional places = 2); + + /*! Create a new boolean column on the table. */ + ColumnDefinitionReference<> boolean(const QString &column); + + /*! Create a new enum column on the table. */ + ColumnDefinitionReference<> Enum(const QString &column, + const QVector &allowed); + /*! Create a new set column on the table. */ + ColumnDefinitionReference<> set(const QString &column, + const QVector &allowed); + + /*! Create a new json column on the table. */ + ColumnDefinitionReference<> json(const QString &column); + /*! Create a new jsonb column on the table. */ + ColumnDefinitionReference<> jsonb(const QString &column); + + /*! Create a new date column on the table. */ + ColumnDefinitionReference<> date(const QString &column); + /*! Create a new date-time column on the table. */ + ColumnDefinitionReference<> dateTime(const QString &column, int precision = 0); + /*! Create a new date-time column (with time zone) on the table. */ + ColumnDefinitionReference<> dateTimeTz(const QString &column, int precision = 0); + + /*! Create a new time column on the table. */ + ColumnDefinitionReference<> time(const QString &column, int precision = 0); + /*! Create a new time column (with time zone) on the table. */ + ColumnDefinitionReference<> timeTz(const QString &column, int precision = 0); + + /*! Create a new timestamp column on the table. */ + ColumnDefinitionReference<> timestamp(const QString &column, int precision = 0); + /*! Create a new timestamp (with time zone) column on the table. */ + ColumnDefinitionReference<> timestampTz(const QString &column, int precision = 0); + + /*! Add nullable creation and update timestamps to the table. */ + void timestamps(int precision = 0); + /*! Add creation and update timestampTz columns to the table. */ + void timestampsTz(int precision = 0); + + /*! Create a new year column on the table. */ + ColumnDefinitionReference<> year(const QString &column); + + /*! Create a new binary column on the table. */ + ColumnDefinitionReference<> binary(const QString &column); + + /*! Create a new uuid column on the table. */ + ColumnDefinitionReference<> + uuid(const QString &column = QStringLiteral("uuid")); + + /*! Create a new IP address column on the table. */ + ColumnDefinitionReference<> + ipAddress(const QString &column = QStringLiteral("ip_address")); + /*! Create a new MAC address column on the table. */ + ColumnDefinitionReference<> + macAddress(const QString &column = QStringLiteral("mac_address")); + + /*! Create a new geometry column on the table. */ + ColumnDefinitionReference<> geometry(const QString &column); + /*! Create a new point column on the table. */ + ColumnDefinitionReference<> point(const QString &column, + std::optional srid = std::nullopt); + /*! Create a new linestring column on the table. */ + ColumnDefinitionReference<> lineString(const QString &column); + /*! Create a new polygon column on the table. */ + ColumnDefinitionReference<> polygon(const QString &column); + /*! Create a new geometrycollection column on the table. */ + ColumnDefinitionReference<> geometryCollection(const QString &column); + /*! Create a new multipoint column on the table. */ + ColumnDefinitionReference<> multiPoint(const QString &column); + /*! Create a new multilinestring column on the table. */ + ColumnDefinitionReference<> multiLineString(const QString &column); + /*! Create a new multipolygon column on the table. */ + ColumnDefinitionReference<> multiPolygon(const QString &column); + /*! Create a new multipolygon column on the table. */ + ColumnDefinitionReference<> multiPolygonZ(const QString &column); + + /*! Create a new generated, computed column on the table. */ + ColumnDefinitionReference<> computed(const QString &column, + const QString &expression); + + /*! Adds the `remember_token` column to the table. */ + ColumnDefinitionReference<> rememberToken(); + + /*! Add a new column to the blueprint. */ + ColumnDefinitionReference<> addColumn(ColumnType type, const QString &name, + ColumnDefinition &¶meters = {}); + + /* Others */ + /*! Get the columns on the blueprint that should be added. */ + QVector getAddedColumns() const; + /*! Get the columns on the blueprint that should be changed. */ + QVector getChangedColumns() const; + + /*! Determine if the blueprint has auto-increment columns. */ + bool hasAutoIncrementColumn() const; + /*! Get the auto-increment column starting values. */ + QVector autoIncrementStartingValues() const; + /*! Determine if the blueprint has a create command. */ + bool creating() const; + + /*! Remove a column from the schema blueprint. */ + Blueprint &removeColumn(const QString &name); + + /* Getters */ + /*! Get the table the blueprint describes. */ + inline const QString &getTable() const noexcept; + /*! Get the columns on the blueprint. */ + inline const QVector &getColumns() const noexcept; + /*! Get the commands on the blueprint. */ + inline const QVector &getCommands() const noexcept; + /*! Determine whether the blueprint describes temporary table. */ + inline bool isTemporary() const noexcept; + + /* Others */ + /*! Indicate that the table needs to be temporary. */ + inline void temporary() noexcept; + + /*! Set the default string length for migrations. */ + static void defaultStringLength(int length) noexcept; + + // CUR schema, should be T_THREAD_LOCAL? cant be now, thread_local can not be part of dll interface silverqx + /*! The default string length for migrations. */ + static int DefaultStringLength; + + /*! The storage engine that should be used for the table. */ + QString engine; + /*! The default character set that should be used for the table. */ + QString charset; + /*! The collation that should be used for the table. */ + QString collation; + + protected: + /*! Expose column types. */ +// using enum ColumnType; + + /*! Add a new column definition to the blueprint. */ + ColumnDefinitionReference<> addColumnDefinition(ColumnDefinition &&definition); + + /*! Add a new command to the blueprint. */ + ColumnDefinitionReference<> addCommand(const QString &name, + ColumnDefinition &¶meters = {}); + /*! Create a new Fluent command.. */ + ColumnDefinition createCommand(const QString &name, + ColumnDefinition &¶meters = {}); + + /*! Add the commands that are implied by the blueprint's state. */ + void addImpliedCommands(const SchemaGrammar &grammar); + /*! Add the index commands fluently specified on columns. */ + void addFluentIndexes(); + + /*! Add a new index command to the blueprint. */ + ColumnDefinitionReference<> + indexCommand(const QString &type, const QVector &columns, + const QString &indexName, const QString &algorithm = ""); + /*! Create a new drop index command on the blueprint. */ + ColumnDefinitionReference<> + dropIndexCommand(const QString &command, const QString &type, + const QVector &columns); + /*! Create a new drop index command on the blueprint. */ + ColumnDefinitionReference<> + dropIndexCommand(const QString &command, const QString &index); + + /*! Create a default index name for the table. */ + QString createIndexName(const QString &type, + const QVector &columns) const; + + /*! The table the blueprint describes. */ + QString m_table; + /*! The prefix of the table. */ + QString m_prefix; + + /*! The columns that should be added to the table. */ + QVector m_columns; + /*! The commands that should be run for the table. */ + QVector m_commands; + + /*! Whether to make the table temporary. */ + bool m_temporary = false; + /*! The column to add new columns after. */ + QString m_after; + }; + + /* public */ + +#ifndef TINYORM_DISABLE_ORM + template + ForeignIdColumnDefinitionReference + Blueprint::foreignIdFor(const Model &model, const QString &column) + { + const auto column_ = column.isEmpty() ? model.getForeignKey() : column; + + // FEATURE dilemma primarykey, Model::KeyType vs QVariant silverqx + return std::is_integral_v && model.getIncrementing() + ? foreignId(column_) + : foreignUuid(column_); + } +#endif + + template + ColumnDefinitionReference<> Blueprint::dropColumns(Args &&...columns) + { + return dropColumns(QVector {std::forward(columns)...}); + } + + void Blueprint::dropTimestampsTz() + { + dropTimestamps(); + } + + void Blueprint::dropRememberToken() + { + dropColumns({QStringLiteral("remember_token")}); + } + + template + ColumnDefinitionReference<> + Blueprint::primary(const QString &column, const QString &name, + const QString &algorithm) + { + return primary(QVector {column}, name, algorithm); + } + + template + ColumnDefinitionReference<> + Blueprint::unique(const QString &column, const QString &name, + const QString &algorithm) + { + return unique(QVector {column}, name, algorithm); + } + + template + ColumnDefinitionReference<> + Blueprint::index(const QString &column, const QString &name, + const QString &algorithm) + { + return index(QVector {column}, name, algorithm); + } + + template + ColumnDefinitionReference<> + Blueprint::fullText(const QString &column, const QString &name, + const QString &algorithm) + { + return fullText(QVector {column}, name, algorithm); + } + + template + ColumnDefinitionReference<> + Blueprint::spatialIndex(const QString &column, const QString &name) + { + return spatialIndex(QVector {column}, name); + } + + template + ForeignKeyDefinitionReference + Blueprint::foreign(const QString &column, const QString &name) + { + return foreign(QVector {column}, name); + } + + template + ColumnDefinitionReference<> Blueprint::dropPrimary(const QString &index) + { + return dropIndexCommand(DropPrimary, index); + } + + template + ColumnDefinitionReference<> Blueprint::dropUnique(const QString &index) + { + return dropIndexCommand(DropUnique, index); + } + + template + ColumnDefinitionReference<> Blueprint::dropIndex(const QString &index) + { + return dropIndexCommand(DropIndex, index); + } + + template + ColumnDefinitionReference<> Blueprint::dropFullText(const QString &index) + { + return dropIndexCommand(DropFullText, index); + } + + template + ColumnDefinitionReference<> Blueprint::dropSpatialIndex(const QString &index) + { + return dropIndexCommand(DropSpatialIndex, index); + } + + template + ColumnDefinitionReference<> Blueprint::dropForeign(const QString &index) + { + return dropIndexCommand(DropForeign, index); + } + + ColumnDefinitionReference<> Blueprint::id(const QString &column) + { + return bigIncrements(column); + } + + ColumnDefinitionReference<> Blueprint::increments(const QString &column) + { + return unsignedInteger(column, true); + } + + ColumnDefinitionReference<> Blueprint::integerIncrements(const QString &column) + { + return unsignedInteger(column, true); + } + + ColumnDefinitionReference<> Blueprint::tinyIncrements(const QString &column) + { + return unsignedTinyInteger(column, true); + } + + ColumnDefinitionReference<> Blueprint::smallIncrements(const QString &column) + { + return unsignedSmallInteger(column, true); + } + + ColumnDefinitionReference<> Blueprint::mediumIncrements(const QString &column) + { + return unsignedMediumInteger(column, true); + } + + ColumnDefinitionReference<> Blueprint::bigIncrements(const QString &column) + { + return unsignedBigInteger(column, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedInteger(const QString &column, const bool autoIncrement) + { + return integer(column, autoIncrement, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedTinyInteger(const QString &column, const bool autoIncrement) + { + return tinyInteger(column, autoIncrement, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedSmallInteger(const QString &column, const bool autoIncrement) + { + return smallInteger(column, autoIncrement, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedMediumInteger(const QString &column, const bool autoIncrement) + { + return mediumInteger(column, autoIncrement, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedBigInteger(const QString &column, const bool autoIncrement) + { + return bigInteger(column, autoIncrement, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedFloat(const QString &column, const std::optional total, + const std::optional places) + { + return Float(column, total, places, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedDouble(const QString &column, const std::optional total, + const std::optional places) + { + return Double(column, total, places, true); + } + + ColumnDefinitionReference<> + Blueprint::unsignedDecimal(const QString &column, const std::optional total, + const std::optional places) + { + return decimal(column, total, places, true); + } + + const QString &Blueprint::getTable() const noexcept + { + return m_table; + } + + const QVector &Blueprint::getColumns() const noexcept + { + return m_columns; + } + + const QVector &Blueprint::getCommands() const noexcept + { + return m_commands; + } + + bool Blueprint::isTemporary() const noexcept + { + return m_temporary; + } + + void Blueprint::temporary() noexcept + { + m_temporary = true; + } + +} // namespace SchemaNs +} // namespace Orm + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_BLUEPRINT_HPP diff --git a/include/orm/schema/columndefinition.hpp b/include/orm/schema/columndefinition.hpp new file mode 100644 index 000000000..9d0731b60 --- /dev/null +++ b/include/orm/schema/columndefinition.hpp @@ -0,0 +1,132 @@ +#pragma once +#ifndef ORM_SCHEMA_COLUMNDEFINITION_HPP +#define ORM_SCHEMA_COLUMNDEFINITION_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include + +#include "orm/query/expression.hpp" +#include "orm/schema/schematypes.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + + /*! Database column definition. */ + class ColumnDefinition + { + /*! Type for the database column. */ + using Column = std::variant; + + public: + /*! Column type. */ + ColumnType type; + /*! Column name. */ + QString name; + /*! Indicates whether a column will be changed or created. */ + bool change = false; + + /* Commands */ + // indexes, and columns also used in dropColumns() + /*! Index name. */ + QString index; + /*! Columns for which to create an index. */ + QVector columns; // foreign key + /*! Algorith to use during index creation. */ + QString algorithm; + + // rename index + /*! Rename index from. */ + QString from_; + /*! Rename index to. */ + QString to; + + // foreign key + /*! Specifies the referenced columns. */ + QVector references; + /*! Specifies the referenced table. */ + QString on; + /*! Specifies ON DELETE action (cascade/restrict/set null/no action/ + set default). */ + QString onDelete; + /*! Specifies ON UPDATE action (cascade/restrict/set null/no action/ + set default). */ + QString onUpdate; + + /* Internal - used from blueprint */ + /*! Allowed index values for Enumaration Literals (enum/set). */ + QVector allowed; + /*! Value for a generated, computed column type (SQL Server). */ + QString expression; + /*! Length of the char or varchar column. */ + int length = DefaultStringLength; + /*! Number of digits after the decimal point for floating-point types. */ + std::optional places; + /*! Determine fractional Seconds in Time Values (MySQL 0-6). */ + int precision; + /* srid max. value should be 2^32-1 as is described here, so unsigned int + should be ok: + https://dev.mysql.com/doc/refman/8.0/en/spatial-function-argument-handling.html */ + /*! The spatial reference identifier (SRID) of a geometry identifies the SRS + in which the geometry is defined. */ + std::optional srid; + /*! Number of digits before the decimal point for floating-point types. */ + std::optional total; + + /* Indexes */ + /*! Add an index. */ + std::variant index_; + /*! Add a primary index. */ + std::variant primary; + /*! Add a fulltext index. */ + std::variant fulltext; + /*! Add a spatial index. */ + std::variant spatialIndex; + /*! Add a unique index. */ + std::variant unique; + + /* Column definitions */ + /*! Determine "after" which column to place a current column (MySQL). */ + QString after; + /*! Specify a character set for the column (MySQL). */ + QString charset; + /*! Specify a collation for the column (MySQL/PostgreSQL/SQL Server). */ + QString collation; + /*! Add a comment to the column (MySQL/PostgreSQL). */ + QString comment; + /*! Specify a "default" value for the column. */ + QVariant defaultValue; + /*! Set the starting value of an auto-incrementing field (MySQL / PostgreSQL). */ + std::optional from; + /*! Set the starting value of an auto-incrementing field (MySQL / PostgreSQL). */ + std::optional startingValue; + /*! Create a stored generated column (MySQL/PostgreSQL/SQLite). */ + QString storedAs; + /*! Create a virtual generated column (MySQL/PostgreSQL/SQLite). */ + QString virtualAs; + + // Place boolean data members at the end to avoid excessive padding + /*! Determine whether the INTEGER column is auto-increment (primary key). */ + bool autoIncrement = false; + /*! Place the column "first" in the table (MySQL). */ + bool first = false; + /*! Specify that the column should be invisible to "SELECT *" (MySQL). */ + bool invisible = false; + /*! Determine whether the INTEGER column is UNSIGNED (MySQL). */ + bool isUnsigned = false; + /*! Allow NULL values to be inserted into the column. */ + bool nullable = false; + /*! Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value. */ + bool useCurrent = false; + /*! Set the TIMESTAMP column to use CURRENT_TIMESTAMP when updating (MySQL). */ + bool useCurrentOnUpdate = false; + }; + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_COLUMNDEFINITION_HPP diff --git a/include/orm/schema/columndefinitionreference.hpp b/include/orm/schema/columndefinitionreference.hpp new file mode 100644 index 000000000..6153d7442 --- /dev/null +++ b/include/orm/schema/columndefinitionreference.hpp @@ -0,0 +1,332 @@ +#pragma once +#ifndef ORM_SCHEMA_COLUMNDEFINITIONREFERENCE_HPP +#define ORM_SCHEMA_COLUMNDEFINITIONREFERENCE_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include "orm/schema/columndefinition.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + + class ForeignIdColumnDefinitionReference; + + /*! Concept for the column reference return type, based on this template parameter + will be decided what type it will be. */ + template + concept ColumnReferenceReturn = std::is_void_v || + std::same_as; + + /*! Reference class to the ColumnDefinition, provides setters with a nice API + for the database column (includes indexes). */ + template + class ColumnDefinitionReference + { + // To access m_columnDefinition data member + friend ForeignIdColumnDefinitionReference; + // To access m_columnDefinition data member + friend class ForeignKeyDefinitionReference; + + public: + /*! CRTP return type by the passed R template parameter. */ + using ColumnReferenceType = + std::conditional_t, ColumnDefinitionReference, R>; + + /*! Constructor. */ + ColumnDefinitionReference(ColumnDefinition &columnDefinition); // NOLINT(google-explicit-constructor) + /*! Default destructor. */ + inline ~ColumnDefinitionReference() = default; + + /*! Place the column "after" another column (MySQL). */ + ColumnReferenceType &after(const QString &column); + /*! Set INTEGER column as auto-increment (primary key). */ + ColumnReferenceType &autoIncrement(); + /*! Specify a character set for the column (MySQL). */ + ColumnReferenceType &charset(const QString &charset); + /*! Specify a collation for the column (MySQL/PostgreSQL/SQL Server). */ + ColumnReferenceType &collation(const QString &collation); + /*! Add a comment to the column (MySQL/PostgreSQL). */ + ColumnReferenceType &comment(const QString &comment); + /*! Specify a "default" value for the column. */ + ColumnReferenceType &defaultValue(const QVariant &value); + /*! Place the column "first" in the table (MySQL). */ + ColumnReferenceType &first(); + /*! Set the starting value of an auto-incrementing field (MySQL / PostgreSQL). */ + ColumnReferenceType &from(int startingValue); + /*! Specify that the column should be invisible to "SELECT *" (MySQL). */ + ColumnReferenceType &invisible(); + /*! Set the INTEGER column as UNSIGNED (MySQL). */ + ColumnReferenceType &isUnsigned(); + /*! Allow NULL values to be inserted into the column. */ + ColumnReferenceType &nullable(bool value = true); + /*! Set the starting value of an auto-incrementing field (MySQL/PostgreSQL). */ + ColumnReferenceType &startingValue(int startingValue); + /*! Create a stored generated column (MySQL/PostgreSQL/SQLite). */ + ColumnReferenceType &storedAs(const QString &expression); + /*! Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value. */ + ColumnReferenceType &useCurrent(); + /*! Set the TIMESTAMP column to use CURRENT_TIMESTAMP when updating (MySQL). */ + ColumnReferenceType &useCurrentOnUpdate(); + /*! Create a virtual generated column (MySQL/PostgreSQL/SQLite). */ + ColumnReferenceType &virtualAs(const QString &expression); + + /*! Add an index. */ + ColumnReferenceType &index(const QString &indexName = ""); + /*! Add a primary index. */ + ColumnReferenceType &primary(); + /*! Add a fulltext index. */ + ColumnReferenceType &fulltext(const QString &indexName = ""); + /*! Add a spatial index. */ + ColumnReferenceType &spatialIndex(const QString &indexName = ""); + /*! Add a unique index. */ + ColumnReferenceType &unique(const QString &indexName = ""); + + private: + /*! Static cast this to a child's instance type (CRTP). */ + ColumnReferenceType &columnReference(); + + /*! Reference to a column definition. */ + std::reference_wrapper m_columnDefinition; + }; + + /* I had to make this class templated to be able call eg. foreignId().nullable(), + be able to call ColumnDefinitionReference methods + on the ForeignIdColumnDefinitionReference class. + ColumnDefinitionReference methods have to return + the ForeignIdColumnDefinitionReference & if 'this' is + the ForeignIdColumnDefinitionReference instance. + It added a little mess but it was absolutely necessary. + I can return ColumnReferenceType & because a real instance is created by + Blueprint methods and lives in that fluent call, exception are + ForeignKeyDefinitionReference methods, there & can not be returned because + a real instance is created inside + the ForeignIdColumnDefinitionReference::references() method so returning by + a value is necessary (if not then dangling reference occurs and crash happens). */ + + /* public */ + + template + ColumnDefinitionReference::ColumnDefinitionReference( + ColumnDefinition &columnDefinition + ) + : m_columnDefinition(columnDefinition) + {} + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::after(const QString &column) + { + m_columnDefinition.get().after = column; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::autoIncrement() + { + m_columnDefinition.get().autoIncrement = true; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::charset(const QString &charset) + { + m_columnDefinition.get().charset = charset; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::collation(const QString &collation) + { + m_columnDefinition.get().collation = collation; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::comment(const QString &comment) + { + m_columnDefinition.get().comment = comment; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::defaultValue(const QVariant &value) + { + m_columnDefinition.get().defaultValue = value; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::first() + { + m_columnDefinition.get().first = true; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::from(const int startingValue) + { + m_columnDefinition.get().from = startingValue; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::invisible() + { + m_columnDefinition.get().invisible = true; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::isUnsigned() + { + m_columnDefinition.get().isUnsigned = true; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::nullable(const bool value) + { + m_columnDefinition.get().nullable = value; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::startingValue(const int startingValue) + { + m_columnDefinition.get().startingValue = startingValue; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::storedAs(const QString &expression) + { + m_columnDefinition.get().storedAs = expression; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::useCurrent() + { + m_columnDefinition.get().useCurrent = true; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::useCurrentOnUpdate() + { + m_columnDefinition.get().useCurrentOnUpdate = true; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::virtualAs(const QString &expression) + { + m_columnDefinition.get().virtualAs = expression; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::index(const QString &indexName) + { + if (indexName.isEmpty()) + m_columnDefinition.get().index_ = true; + else + m_columnDefinition.get().index_ = indexName; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::primary() + { + m_columnDefinition.get().primary = true; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::fulltext(const QString &indexName) + { + if (indexName.isEmpty()) + m_columnDefinition.get().fulltext = true; + else + m_columnDefinition.get().fulltext = indexName; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::spatialIndex(const QString &indexName) + { + if (indexName.isEmpty()) + m_columnDefinition.get().spatialIndex = true; + else + m_columnDefinition.get().spatialIndex = indexName; + + return columnReference(); + } + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::unique(const QString &indexName) + { + if (indexName.isEmpty()) + m_columnDefinition.get().unique = true; + else + m_columnDefinition.get().unique = indexName; + + return columnReference(); + } + + /* private */ + + template + typename ColumnDefinitionReference::ColumnReferenceType & + ColumnDefinitionReference::columnReference() + { + return static_cast::ColumnReferenceType &>(*this); + } + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_COLUMNDEFINITIONREFERENCE_HPP diff --git a/include/orm/schema/foreignidcolumndefinitionreference.hpp b/include/orm/schema/foreignidcolumndefinitionreference.hpp new file mode 100644 index 000000000..32c2d857f --- /dev/null +++ b/include/orm/schema/foreignidcolumndefinitionreference.hpp @@ -0,0 +1,63 @@ +#pragma once +#ifndef ORM_SCHEMA_FOREIGNIDCOLUMNDEFINITIONREFERENCE_HPP +#define ORM_SCHEMA_FOREIGNIDCOLUMNDEFINITIONREFERENCE_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include "orm/constants.hpp" +#include "orm/schema/foreignkeydefinitionreference.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + + class Blueprint; + + /*! Reference class to the ColumnDefinition provides setters with a nice API using + terser syntax for the foreign key column. */ + class SHAREDLIB_EXPORT ForeignIdColumnDefinitionReference : + public ColumnDefinitionReference + { + // To access m_columnDefinition data member + friend ForeignKeyDefinitionReference; + + public: + /*! Constructor. */ + ForeignIdColumnDefinitionReference( // NOLINT(google-explicit-constructor) + Blueprint &blueprint, + ColumnDefinitionReference<> columnDefinitionReference); + /*! Default destructor. */ + inline ~ForeignIdColumnDefinitionReference() = default; + + /*! Create a foreign key constraint on this column referencing the "id" column + of the conventionally related table. */ + ForeignKeyDefinitionReference + constrained(const QString &table = "", const QString &column = Constants::ID); + + /*! Specify the referenced columns. */ + ForeignKeyDefinitionReference references(const QVector &columns); + /*! Specify the referenced column. */ + template + ForeignKeyDefinitionReference references(const QString &column); + + private: + /*! Reference to the schema builder blueprint instance. */ + Blueprint &m_blueprint; + /*! Reference to a column definition. */ + std::reference_wrapper m_columnDefinition; + }; + + template + ForeignKeyDefinitionReference + ForeignIdColumnDefinitionReference::references(const QString &column) + { + return references(QVector {column}); + } + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_FOREIGNIDCOLUMNDEFINITIONREFERENCE_HPP diff --git a/include/orm/schema/foreignkeydefinitionreference.hpp b/include/orm/schema/foreignkeydefinitionreference.hpp new file mode 100644 index 000000000..fe95865a2 --- /dev/null +++ b/include/orm/schema/foreignkeydefinitionreference.hpp @@ -0,0 +1,71 @@ +#pragma once +#ifndef ORM_SCHEMA_FOREIGNKEYDEFINITIONREFERENCE_HPP +#define ORM_SCHEMA_FOREIGNKEYDEFINITIONREFERENCE_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include "orm/macros/export.hpp" +#include "orm/schema/columndefinitionreference.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + + /*! Reference class to the ColumnDefinition, provides setters with a nice API + for the foreign key column. */ + class SHAREDLIB_EXPORT ForeignKeyDefinitionReference + { + public: + /*! Constructor. */ + ForeignKeyDefinitionReference( // NOLINT(google-explicit-constructor) + ColumnDefinitionReference<> columnDefinitionReference); + /*! Constructor. */ + ForeignKeyDefinitionReference( // NOLINT(google-explicit-constructor) + ForeignIdColumnDefinitionReference foreignIdColumnReference); + /*! Default destructor. */ + inline ~ForeignKeyDefinitionReference() = default; + + /*! Specify the referenced columns. */ + ForeignKeyDefinitionReference &references(const QVector &columns); + /*! Specify the referenced column. */ + template + ForeignKeyDefinitionReference &references(const QString &column); + + /*! Specify the referenced table. */ + ForeignKeyDefinitionReference &on(const QString &table); + + /*! Add an ON DELETE action. */ + ForeignKeyDefinitionReference &onDelete(const QString &action); + /*! Add an ON UPDATE action. */ + ForeignKeyDefinitionReference &onUpdate(const QString &action); + + /*! Indicate that updates should cascade. */ + ForeignKeyDefinitionReference &cascadeOnUpdate(); + /*! Indicate that updates should be restricted. */ + ForeignKeyDefinitionReference &restrictOnUpdate(); + /*! Indicate that deletes should cascade. */ + ForeignKeyDefinitionReference &cascadeOnDelete(); + /*! Indicate that deletes should be restricted. */ + ForeignKeyDefinitionReference &restrictOnDelete(); + /*! Indicate that deletes should set the foreign key value to null. */ + ForeignKeyDefinitionReference &nullOnDelete(); + + private: + /*! Reference to a column definition. */ + std::reference_wrapper m_columnDefinition; + }; + + template + ForeignKeyDefinitionReference & + ForeignKeyDefinitionReference::references(const QString &column) + { + return references(QVector {column}); + } + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_FOREIGNKEYDEFINITIONREFERENCE_HPP diff --git a/include/orm/schema/grammars/mysqlschemagrammar.hpp b/include/orm/schema/grammars/mysqlschemagrammar.hpp index 142c07217..881bb8989 100644 --- a/include/orm/schema/grammars/mysqlschemagrammar.hpp +++ b/include/orm/schema/grammars/mysqlschemagrammar.hpp @@ -9,7 +9,7 @@ TINY_SYSTEM_HEADER TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema::Grammars +namespace Orm::SchemaNs::Grammars { /*! MySql schema grammar. */ @@ -23,11 +23,275 @@ namespace Orm::Schema::Grammars /*! Virtual destructor. */ inline ~MySqlSchemaGrammar() override = default; + /* Compile methods for the SchemaBuilder */ + /*! Compile a create database command. */ + QString compileCreateDatabase(const QString &name, + DatabaseConnection &connection) const override; + /*! Compile a drop database if exists command. */ + QString compileDropDatabaseIfExists(const QString &name) const override; + + /*! Compile the SQL needed to drop all tables. */ + QString compileDropAllTables(const QVector &tables) const override; + /*! Compile the SQL needed to drop all views. */ + QString compileDropAllViews(const QVector &views) const override; + + /*! Compile the SQL needed to retrieve all table names. */ + QString compileGetAllTables() const override; + /*! Compile the SQL needed to retrieve all view names. */ + QString compileGetAllViews() const override; + + /*! Compile the command to enable foreign key constraints. */ + QString compileEnableForeignKeyConstraints() const override; + /*! Compile the command to disable foreign key constraints. */ + QString compileDisableForeignKeyConstraints() const override; + + /*! Compile the query to determine the list of tables. */ + QString compileTableExists() const override; /*! Compile the query to determine the list of columns. */ QString compileColumnListing(const QString &table = "") const override; + + /* Compile methods for commands */ + /*! Compile a create table command. */ + QVector + compileCreate(const Blueprint &blueprint, const ColumnDefinition &command, + const DatabaseConnection &connection) const; + + /*! Compile a drop table command. */ + QVector compileDrop(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a drop table (if exists) command. */ + QVector compileDropIfExists(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile a rename table command. */ + QVector compileRename(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile an add column command. */ + QVector compileAdd(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a drop column command. */ + QVector compileDropColumn(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a rename column command. */ + QVector compileRenameColumn(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile a primary key command. */ + QVector compilePrimary(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a unique key command. */ + QVector compileUnique(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a plain index key command. */ + QVector compileIndex(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a fulltext index key command. */ + QVector compileFullText(const Blueprint &blueprint, + const ColumnDefinition &command) const override; + /*! Compile a spatial index key command. */ + QVector compileSpatialIndex(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile a drop primary key command. */ + QVector compileDropPrimary(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a drop unique key command. */ + inline QVector compileDropUnique(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a drop index command. */ + QVector compileDropIndex(const Blueprint &blueprint, + const ColumnDefinition &command) const; + /*! Compile a drop fulltext index command. */ + inline QVector + compileDropFullText(const Blueprint &blueprint, + const ColumnDefinition &command) const override; + /*! Compile a drop spatial index command. */ + inline QVector + compileDropSpatialIndex(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile a drop foreign key command. */ + QVector compileDropForeign(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile a rename index command. */ + QVector compileRenameIndex(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Map a command name to SchemaGrammar::compileXx() methods. */ + QVector + invokeCompileMethod(const ColumnDefinition &command, + const DatabaseConnection &connection, + const Blueprint &blueprint) const override; + + protected: + // BUG schema, change after upgrade to QtCreator with clang 13 silverqx + /*! Expose column types. */ +// using enum ColumnType; + + /*! Create the main create table clause. */ + QString + compileCreateTable(const Blueprint &blueprint, const ColumnDefinition &command, + const DatabaseConnection &connection) const; + /*! Add the column modifiers to the definition. */ + QString addModifiers(QString &&sql, + const ColumnDefinition &column) const override; + + /*! Append the character set specifications to a command. */ + void compileCreateEncoding(QString &sql, const DatabaseConnection &connection, + const Blueprint &blueprint) const; + /*! Append the engine specifications to a command. */ + void compileCreateEngine(QString &sql, const DatabaseConnection &connection, + const Blueprint &blueprint) const; + /*! Compile the auto-incrementing column starting values. */ + QVector + compileAutoIncrementStartingValues(const Blueprint &blueprint) const; + + /*! Compile an index creation command. */ + QString compileKey(const Blueprint &blueprint, const ColumnDefinition &command, + const QString &type) const; + + /*! Wrap a single string in keyword identifiers. */ + QString wrapValue(QString value) const override; + + /*! Escape all MySQL spacial characters desribed in String Literal docs. */ + QString addSlashes(QString value) const; + + /*! Get the SQL for the column data type. */ + QString getType(const ColumnDefinition &column) const override; + + /*! Create the column definition for a char type. */ + QString typeChar(const ColumnDefinition &column) const; + /*! Create the column definition for a string type. */ + QString typeString(const ColumnDefinition &column) const; + /*! Create the column definition for a tiny text type. */ + QString typeTinyText(const ColumnDefinition &column) const; + /*! Create the column definition for a text type. */ + QString typeText(const ColumnDefinition &column) const; + /*! Create the column definition for a medium text type. */ + QString typeMediumText(const ColumnDefinition &column) const; + /*! Create the column definition for a long text type. */ + QString typeLongText(const ColumnDefinition &column) const; + /*! Create the column definition for a big integer type. */ + QString typeBigInteger(const ColumnDefinition &column) const; + /*! Create the column definition for an integer type. */ + QString typeInteger(const ColumnDefinition &column) const; + /*! Create the column definition for a medium integer type. */ + QString typeMediumInteger(const ColumnDefinition &column) const; + /*! Create the column definition for a tiny integer type. */ + QString typeTinyInteger(const ColumnDefinition &column) const; + /*! Create the column definition for a small integer type. */ + QString typeSmallInteger(const ColumnDefinition &column) const; + /*! Create the column definition for a float type. */ + QString typeFloat(const ColumnDefinition &column) const; + /*! Create the column definition for a double type. */ + QString typeDouble(const ColumnDefinition &column) const; + /*! Create the column definition for a decimal type. */ + QString typeDecimal(const ColumnDefinition &column) const; + /*! Create the column definition for a boolean type. */ + QString typeBoolean(const ColumnDefinition &column) const; + /*! Create the column definition for an enumeration type. */ + QString typeEnum(const ColumnDefinition &column) const; + /*! Create the column definition for a set enumeration type. */ + QString typeSet(const ColumnDefinition &column) const; + /*! Create the column definition for a json type. */ + QString typeJson(const ColumnDefinition &column) const; + /*! Create the column definition for a jsonb type. */ + QString typeJsonb(const ColumnDefinition &column) const; + /*! Create the column definition for a date type. */ + QString typeDate(const ColumnDefinition &column) const; + /*! Create the column definition for a date-time type. */ + QString typeDateTime(const ColumnDefinition &column) const; + /*! Create the column definition for a date-time (with time zone) type. */ + QString typeDateTimeTz(const ColumnDefinition &column) const; + /*! Create the column definition for a time type. */ + QString typeTime(const ColumnDefinition &column) const; + /*! Create the column definition for a time (with time zone) type. */ + QString typeTimeTz(const ColumnDefinition &column) const; + /*! Create the column definition for a timestamp type. */ + QString typeTimestamp(const ColumnDefinition &column) const; + /*! Create the column definition for a timestamp (with time zone) type. */ + QString typeTimestampTz(const ColumnDefinition &column) const; + /*! Create the column definition for a year type. */ + QString typeYear(const ColumnDefinition &column) const; + /*! Create the column definition for a binary type. */ + QString typeBinary(const ColumnDefinition &column) const; + /*! Create the column definition for a uuid type. */ + QString typeUuid(const ColumnDefinition &column) const; + /*! Create the column definition for an IP address type. */ + QString typeIpAddress(const ColumnDefinition &column) const; + /*! Create the column definition for a MAC address type. */ + QString typeMacAddress(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial Geometry type. */ + QString typeGeometry(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial Point type. */ + QString typePoint(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial LineString type. */ + QString typeLineString(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial Polygon type. */ + QString typePolygon(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial GeometryCollection type. */ + QString typeGeometryCollection(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial MultiPoint type. */ + QString typeMultiPoint(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial MultiLineString type. */ + QString typeMultiLineString(const ColumnDefinition &column) const; + /*! Create the column definition for a spatial MultiPolygon type. */ + QString typeMultiPolygon(const ColumnDefinition &column) const; + /*! Create the column definition for a generated, computed column type. */ + QString typeComputed(const ColumnDefinition &column) const override; + + /*! Get the SQL for a generated virtual column modifier. */ + QString modifyVirtualAs(const ColumnDefinition &column) const; + /*! Get the SQL for a generated stored column modifier. */ + QString modifyStoredAs(const ColumnDefinition &column) const; + /*! Get the SQL for an unsigned column modifier. */ + QString modifyUnsigned(const ColumnDefinition &column) const; + /*! Get the SQL for a character set column modifier.*/ + QString modifyCharset(const ColumnDefinition &column) const; + /*! Get the SQL for a collation column modifier. */ + QString modifyCollate(const ColumnDefinition &column) const; + /*! Get the SQL for a nullable column modifier. */ + QString modifyNullable(const ColumnDefinition &column) const; + /*! Get the SQL for an invisible column modifier. */ + QString modifyInvisible(const ColumnDefinition &column) const; + /*! Get the SQL for a default column modifier. */ + QString modifyDefault(const ColumnDefinition &column) const; + /*! Get the SQL for an auto-increment column modifier. */ + QString modifyIncrement(const ColumnDefinition &column) const; + /*! Get the SQL for a "first" column modifier. */ + QString modifyFirst(const ColumnDefinition &column) const; + /*! Get the SQL for an "after" column modifier. */ + QString modifyAfter(const ColumnDefinition &column) const; + /*! Get the SQL for a "comment" column modifier. */ + QString modifyComment(const ColumnDefinition &column) const; + /*! Get the SQL for a SRID column modifier. */ + QString modifySrid(const ColumnDefinition &column) const; }; -} // namespace Orm::Schema::Grammars + QVector + MySqlSchemaGrammar::compileDropUnique(const Blueprint &blueprint, + const ColumnDefinition &command) const + { + return compileDropIndex(blueprint, command); + } + + QVector + MySqlSchemaGrammar::compileDropFullText(const Blueprint &blueprint, + const ColumnDefinition &command) const + { + return compileDropIndex(blueprint, command); + } + + QVector + MySqlSchemaGrammar::compileDropSpatialIndex(const Blueprint &blueprint, + const ColumnDefinition &command) const + { + return compileDropIndex(blueprint, command); + } + +} // namespace Orm::SchemaNs::Grammars TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema/grammars/postgresschemagrammar.hpp b/include/orm/schema/grammars/postgresschemagrammar.hpp index 246e61130..91b790138 100644 --- a/include/orm/schema/grammars/postgresschemagrammar.hpp +++ b/include/orm/schema/grammars/postgresschemagrammar.hpp @@ -9,7 +9,7 @@ TINY_SYSTEM_HEADER TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema::Grammars +namespace Orm::SchemaNs::Grammars { /*! PostgreSql schemma grammar. */ @@ -23,11 +23,36 @@ namespace Orm::Schema::Grammars /*! Virtual destructor. */ inline ~PostgresSchemaGrammar() override = default; + /* Compile methods for the SchemaBuilder */ + /*! Compile the command to enable foreign key constraints. */ + QString compileEnableForeignKeyConstraints() const override; + /*! Compile the command to disable foreign key constraints. */ + QString compileDisableForeignKeyConstraints() const override; + /*! Compile the query to determine the list of columns. */ QString compileColumnListing(const QString &table = "") const override; + + /* Compile methods for commands */ + /*! Compile a foreign key command. */ + QVector compileForeign(const Blueprint &blueprint, + const ColumnDefinition &command) const override; + + /*! Run command's compile method and return SQL queries. */ + QVector + invokeCompileMethod(const ColumnDefinition &command, + const DatabaseConnection &connection, + const Blueprint &blueprint) const override; + + protected: + /*! Add the column modifiers to the definition. */ + QString addModifiers(QString &&sql, + const ColumnDefinition &column) const override; + + /*! Get the SQL for the column data type. */ + QString getType(const ColumnDefinition &column) const override; }; -} // namespace Orm::Schema::Grammars +} // namespace Orm::SchemaNs::Grammars TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema/grammars/schemagrammar.hpp b/include/orm/schema/grammars/schemagrammar.hpp index 168c49f81..172cb28f0 100644 --- a/include/orm/schema/grammars/schemagrammar.hpp +++ b/include/orm/schema/grammars/schemagrammar.hpp @@ -5,11 +5,19 @@ #include "orm/macros/systemheader.hpp" TINY_SYSTEM_HEADER +#include +#include + #include "orm/basegrammar.hpp" TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema::Grammars +namespace Orm::SchemaNs +{ + class Blueprint; + class ColumnDefinition; + +namespace Grammars { /*! Database schema grammar base class. */ @@ -23,13 +31,96 @@ namespace Orm::Schema::Grammars /*! Pure virtual destructor. */ inline ~SchemaGrammar() override = 0; + /* Compile methods for the SchemaBuilder */ + /*! Compile a create database command. */ + virtual QString compileCreateDatabase(const QString &name, + DatabaseConnection &connection) const; + /*! Compile a drop database if exists command. */ + virtual QString compileDropDatabaseIfExists(const QString &name) const; + + /*! Compile the SQL needed to drop all tables. */ + virtual QString compileDropAllTables(const QVector &tables) const; + /*! Compile the SQL needed to drop all views. */ + virtual QString compileDropAllViews(const QVector &views) const; + + /*! Compile the SQL needed to retrieve all table names. */ + virtual QString compileGetAllTables() const; + /*! Compile the SQL needed to retrieve all view names. */ + virtual QString compileGetAllViews() const; + + /*! Compile the command to enable foreign key constraints. */ + virtual QString compileEnableForeignKeyConstraints() const = 0; + /*! Compile the command to disable foreign key constraints. */ + virtual QString compileDisableForeignKeyConstraints() const = 0; + + /*! Compile the query to determine the list of tables. */ + virtual QString compileTableExists() const; /*! Compile the query to determine the list of columns. */ virtual QString compileColumnListing(const QString &table = "") const = 0; + + /* Compile methods for commands */ + /*! Compile a fulltext index key command. */ + virtual QVector + compileFullText(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile a foreign key command. */ + virtual QVector + compileForeign(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Compile a drop fulltext index command. */ + virtual QVector + compileDropFullText(const Blueprint &blueprint, + const ColumnDefinition &command) const; + + /*! Wrap a value in keyword identifiers. */ + QString wrap(const ColumnDefinition &column, bool prefixAlias = false) const; + /*! Wrap a table in keyword identifiers. */ + QString wrapTable(const Blueprint &blueprint) const; + /*! Add a prefix to an array of values. */ + template + QVector prefixArray(const QString &prefix, const T &values) const; + + /*! Run command's compile method and return SQL queries. */ + virtual QVector + invokeCompileMethod(const ColumnDefinition &command, + const DatabaseConnection &connection, + const Blueprint &blueprint) const = 0; + + protected: + /*! Get the SQL for the column data type. */ + virtual QString getType(const ColumnDefinition &column) const = 0; + /*! Compile the blueprint's column definitions. */ + QStringList getColumns(const Blueprint &blueprint) const; + + /*! Format a value so that it can be used in "default" clauses. */ + QString getDefaultValue(const QVariant &value) const; + + /*! Add the column modifiers to the definition. */ + virtual QString + addModifiers(QString &&sql, const ColumnDefinition &column) const = 0; + + /*! Create the column definition for a generated, computed column type. */ + virtual QString typeComputed(const ColumnDefinition &column) const; }; SchemaGrammar::~SchemaGrammar() = default; -} // namespace Orm::Schema::Grammars + template + QVector + SchemaGrammar::prefixArray(const QString &prefix, const T &values) const + { + return values + | ranges::views::transform([&prefix](const auto &value) + { + return SPACE_IN.arg(prefix, value); + }) + | ranges::to>(); + } + +} // namespace Grammars +} // namespace Orm::SchemaNs TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema/grammars/sqliteschemagrammar.hpp b/include/orm/schema/grammars/sqliteschemagrammar.hpp index db37b5610..b929e06c5 100644 --- a/include/orm/schema/grammars/sqliteschemagrammar.hpp +++ b/include/orm/schema/grammars/sqliteschemagrammar.hpp @@ -9,7 +9,7 @@ TINY_SYSTEM_HEADER TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema::Grammars +namespace Orm::SchemaNs::Grammars { /*! SQLite schemma grammar. */ @@ -23,11 +23,36 @@ namespace Orm::Schema::Grammars /*! Virtual destructor. */ inline ~SQLiteSchemaGrammar() override = default; + /* Compile methods for the SchemaBuilder */ + /*! Compile the command to enable foreign key constraints. */ + QString compileEnableForeignKeyConstraints() const override; + /*! Compile the command to disable foreign key constraints. */ + QString compileDisableForeignKeyConstraints() const override; + /*! Compile the query to determine the list of columns. */ QString compileColumnListing(const QString &table = "") const override; + + /* Compile methods for commands */ + /*! Compile a foreign key command. */ + QVector compileForeign(const Blueprint &blueprint, + const ColumnDefinition &command) const override; + + /*! Run command's compile method and return SQL queries. */ + QVector + invokeCompileMethod(const ColumnDefinition &command, + const DatabaseConnection &connection, + const Blueprint &blueprint) const override; + + protected: + /*! Add the column modifiers to the definition. */ + QString addModifiers(QString &&sql, + const ColumnDefinition &column) const override; + + /*! Get the SQL for the column data type. */ + QString getType(const ColumnDefinition &column) const override; }; -} // namespace Orm::Schema::Grammars +} // namespace Orm::SchemaNs::Grammars TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema/mysqlschemabuilder.hpp b/include/orm/schema/mysqlschemabuilder.hpp index ed3b085a7..fd3d4f162 100644 --- a/include/orm/schema/mysqlschemabuilder.hpp +++ b/include/orm/schema/mysqlschemabuilder.hpp @@ -9,10 +9,10 @@ TINY_SYSTEM_HEADER TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema +namespace Orm::SchemaNs { - /*! MySql schema repository class. */ + /*! MySql schema builder class. */ class SHAREDLIB_EXPORT MySqlSchemaBuilder : public SchemaBuilder { Q_DISABLE_COPY(MySqlSchemaBuilder) @@ -24,11 +24,29 @@ namespace Orm::Schema /*! Virtual destructor. */ inline ~MySqlSchemaBuilder() override = default; + /*! Create a database in the schema. */ + QSqlQuery createDatabase(const QString &name) const override; + /*! Drop a database from the schema if the database exists. */ + QSqlQuery dropDatabaseIfExists(const QString &name) const override; + + /*! Drop all tables from the database. */ + void dropAllTables() const override; + /*! Drop all views from the database. */ + void dropAllViews() const override; + + /*! Get all of the table names for the database. */ + QSqlQuery getAllTables() const override; + /*! Get all of the view names for the database. */ + QSqlQuery getAllViews() const override; + /*! Get the column listing for a given table. */ QStringList getColumnListing(const QString &table) const override; + + /*! Determine if the given table exists. */ + bool hasTable(const QString &table) const override; }; -} // namespace Orm::Schema +} // namespace Orm::SchemaNs TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema/postgresschemabuilder.hpp b/include/orm/schema/postgresschemabuilder.hpp index 4f45584e1..30d4f1887 100644 --- a/include/orm/schema/postgresschemabuilder.hpp +++ b/include/orm/schema/postgresschemabuilder.hpp @@ -9,10 +9,10 @@ TINY_SYSTEM_HEADER TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema +namespace Orm::SchemaNs { - /*! PostgreSql schema repository class. */ + /*! PostgreSql schema builder class. */ class SHAREDLIB_EXPORT PostgresSchemaBuilder : public SchemaBuilder { Q_DISABLE_COPY(PostgresSchemaBuilder) @@ -33,7 +33,7 @@ namespace Orm::Schema parseSchemaAndTable(const QString &table) const; }; -} // namespace Orm::Schema +} // namespace Orm::SchemaNs TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema/schemabuilder.hpp b/include/orm/schema/schemabuilder.hpp index d6affd3d5..deef97b11 100644 --- a/include/orm/schema/schemabuilder.hpp +++ b/include/orm/schema/schemabuilder.hpp @@ -5,11 +5,13 @@ #include "orm/macros/systemheader.hpp" TINY_SYSTEM_HEADER -#include -#include +#include #include "orm/macros/commonnamespace.hpp" #include "orm/macros/export.hpp" +// CUR check this on clean project silverqx +// Include the blueprint here so a user doesn't have to +#include "orm/schema/blueprint.hpp" TINYORM_BEGIN_COMMON_NAMESPACE @@ -17,14 +19,14 @@ namespace Orm { class DatabaseConnection; -namespace Schema +namespace SchemaNs { namespace Grammars { class SchemaGrammar; } - /*! Database schema repository base class. */ + /*! Database schema builder base class. */ class SHAREDLIB_EXPORT SchemaBuilder { Q_DISABLE_COPY(SchemaBuilder) @@ -35,19 +37,103 @@ namespace Grammars /*! Virtual destructor. */ inline virtual ~SchemaBuilder() = default; + /*! Create a database in the schema. */ + virtual QSqlQuery createDatabase(const QString &name) const; + /*! Drop a database from the schema if the database exists. */ + virtual QSqlQuery dropDatabaseIfExists(const QString &name) const; + + /*! Create a new table on the schema. */ + void create(const QString &table, + const std::function &callback) const; + /*! Modify a table on the schema. */ + void table(const QString &table, + const std::function &callback) const; + + /*! Drop a table from the schema. */ + void drop(const QString &table) const; + /*! Drop a table from the schema if it exists. */ + void dropIfExists(const QString &table) const; + + /*! Rename a table on the schema. */ + void rename(const QString &from, const QString &to) const; + + /*! Drop columns from a table schema. */ + void dropColumns(const QString &table, const QVector &columns) const; + /*! Drop columns from a table schema. */ + template + void dropColumns(const QString &table, Args &&...columns); + /*! Drop one column from a table schema. */ + void dropColumn(const QString &table, const QString &column) const; + + /*! Rename the given column on the schema. */ + void renameColumn(const QString &table, const QString &from, const QString &to); + + /*! Drop all tables from the database. */ + virtual void dropAllTables() const; + /*! Drop all views from the database. */ + virtual void dropAllViews() const; + /*! Drop all types from the database. */ + virtual void dropAllTypes() const; + + /*! Get all of the table names for the database. */ + virtual QSqlQuery getAllTables() const; + /*! Get all of the view names for the database. */ + virtual QSqlQuery getAllViews() const; + + /*! Enable foreign key constraints. */ + QSqlQuery enableForeignKeyConstraints() const; + /*! Disable foreign key constraints. */ + QSqlQuery disableForeignKeyConstraints() const; + /*! Get the column listing for a given table. */ virtual QStringList getColumnListing(const QString &table) const; + /*! Determine if the given table exists. */ + virtual bool hasTable(const QString &table) const; + /*! Determine if the given table has a given column. */ + bool hasColumn(const QString &table, const QString &column) const; + /*! Determine if the given table has given columns. */ + bool hasColumns(const QString &table, const QVector &columns) const; + + /* Getters */ + /*! Get the database connection reference. */ + inline DatabaseConnection &getConnection(); + /*! Get the database connection reference, const version. */ + inline const DatabaseConnection &getConnection() const; + protected: using SchemaGrammar = Grammars::SchemaGrammar; + /*! Create a new command set with a Closure. */ + Blueprint + createBlueprint(const QString &table, + const std::function &callback = nullptr) const; + /*! Execute the blueprint to build / modify the table. */ + void build(Blueprint &&blueprint) const; + /*! The database connection instance. */ DatabaseConnection &m_connection; /*! The schema grammar instance. */ const SchemaGrammar &m_grammar; }; -} // namespace Schema + template + void SchemaBuilder::dropColumns(const QString &table, Args &&...columns) + { + dropColumns(table, {std::forward(columns)...}); + } + + DatabaseConnection &SchemaBuilder::getConnection() + { + return m_connection; + } + + const DatabaseConnection &SchemaBuilder::getConnection() const + { + return m_connection; + } + +} // namespace SchemaNs } // namespace Orm TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/schema/schemaconstants.hpp b/include/orm/schema/schemaconstants.hpp new file mode 100644 index 000000000..7287b67e4 --- /dev/null +++ b/include/orm/schema/schemaconstants.hpp @@ -0,0 +1,16 @@ +#pragma once +#ifndef ORM_SCHEMA_SCHEMACONSTANTS_HPP +#define ORM_SCHEMA_SCHEMACONSTANTS_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include "orm/config.hpp" + +#ifdef TINYORM_EXTERN_CONSTANTS +# include "orm/schema/schemaconstants_extern.hpp" +#else +# include "orm/schema/schemaconstants_inline.hpp" +#endif + +#endif // ORM_SCHEMA_SCHEMACONSTANTS_HPP diff --git a/include/orm/schema/schemaconstants_extern.hpp b/include/orm/schema/schemaconstants_extern.hpp new file mode 100644 index 000000000..475893e7e --- /dev/null +++ b/include/orm/schema/schemaconstants_extern.hpp @@ -0,0 +1,60 @@ +#pragma once +#ifndef ORM_SCHEMA_SCHEMACONSTANTS_EXTERN_HPP +#define ORM_SCHEMA_SCHEMACONSTANTS_EXTERN_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include + +#include "orm/macros/commonnamespace.hpp" +#include "orm/macros/export.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +/*! Namespace constains common chars and strings used in the Schema. */ +namespace Orm::SchemaNs +{ +namespace Constants +{ + + // Command names + SHAREDLIB_EXPORT extern const QString Create; + SHAREDLIB_EXPORT extern const QString Add; + SHAREDLIB_EXPORT extern const QString Change; + SHAREDLIB_EXPORT extern const QString Rename; + SHAREDLIB_EXPORT extern const QString Drop; + SHAREDLIB_EXPORT extern const QString DropIfExists; + SHAREDLIB_EXPORT extern const QString DropColumn; + SHAREDLIB_EXPORT extern const QString RenameColumn; + SHAREDLIB_EXPORT extern const QString DropPrimary; + SHAREDLIB_EXPORT extern const QString DropUnique; + SHAREDLIB_EXPORT extern const QString DropIndex; + SHAREDLIB_EXPORT extern const QString DropFullText; + SHAREDLIB_EXPORT extern const QString DropSpatialIndex; + SHAREDLIB_EXPORT extern const QString DropForeign; + SHAREDLIB_EXPORT extern const QString RenameIndex; + + // Indexes + SHAREDLIB_EXPORT extern const QString Primary; + SHAREDLIB_EXPORT extern const QString Unique; + SHAREDLIB_EXPORT extern const QString Index; + SHAREDLIB_EXPORT extern const QString Fulltext; + SHAREDLIB_EXPORT extern const QString SpatialIndex; + SHAREDLIB_EXPORT extern const QString Foreign; + + // Foreign constrains + SHAREDLIB_EXPORT extern const QString Cascade; + SHAREDLIB_EXPORT extern const QString Restrict; + SHAREDLIB_EXPORT extern const QString SetNull; + +} // namespace Constants + +// NOLINTNEXTLINE(google-build-using-namespace) +using namespace Orm::SchemaNs::Constants; + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_SCHEMACONSTANTS_EXTERN_HPP diff --git a/include/orm/schema/schemaconstants_inline.hpp b/include/orm/schema/schemaconstants_inline.hpp new file mode 100644 index 000000000..59b63d63e --- /dev/null +++ b/include/orm/schema/schemaconstants_inline.hpp @@ -0,0 +1,60 @@ +#pragma once +#ifndef ORM_SCHEMA_SCHEMACONSTANTS_INLINE_HPP +#define ORM_SCHEMA_SCHEMACONSTANTS_INLINE_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include + +#include "orm/macros/commonnamespace.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +/*! Namespace constains common chars and strings used in the Schema. */ +namespace Orm::SchemaNs +{ +namespace Constants +{ + + // Command names + inline const QString Create = QStringLiteral("create"); + inline const QString Add = QStringLiteral("add"); + inline const QString Change = QStringLiteral("change"); + inline const QString Drop = QStringLiteral("drop"); + inline const QString DropIfExists = QStringLiteral("dropIfExists"); + inline const QString Rename = QStringLiteral("rename"); + inline const QString DropColumn = QStringLiteral("dropColumn"); + inline const QString RenameColumn = QStringLiteral("renameColumn"); + inline const QString DropPrimary = QStringLiteral("dropPrimary"); + inline const QString DropUnique = QStringLiteral("dropUnique"); + inline const QString DropIndex = QStringLiteral("dropIndex"); + inline const QString DropFullText = QStringLiteral("dropFullText"); + inline const QString DropSpatialIndex = QStringLiteral("dropSpatialIndex"); + inline const QString DropForeign = QStringLiteral("dropForeign"); + inline const QString RenameIndex = QStringLiteral("renameIndex"); + + // Indexes + inline const QString Primary = QStringLiteral("primary"); + inline const QString Unique = QStringLiteral("unique"); + inline const QString Index = QStringLiteral("index"); + // CUR schema, I fucked up case in fulltext and spatialIndex during replacement, have to check all occurences :( silverqx + inline const QString Fulltext = QStringLiteral("fulltext"); + inline const QString SpatialIndex = QStringLiteral("spatialIndex"); + inline const QString Foreign = QStringLiteral("foreign"); + + // Foreign constrains + inline const QString Cascade = QStringLiteral("cascade"); + inline const QString Restrict = QStringLiteral("restrict"); + inline const QString SetNull = QStringLiteral("set null"); + +} // namespace Constants + +// NOLINTNEXTLINE(google-build-using-namespace) +using namespace Orm::SchemaNs::Constants; + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMA_SCHEMACONSTANTS_INLINE_HPP diff --git a/include/orm/schema/schematypes.hpp b/include/orm/schema/schematypes.hpp new file mode 100644 index 000000000..54482fec4 --- /dev/null +++ b/include/orm/schema/schematypes.hpp @@ -0,0 +1,80 @@ +#pragma once +#ifndef ORM_SCHEMATYPES_HPP +#define ORM_SCHEMATYPES_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include + +#include + +#include "orm/macros/commonnamespace.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + + /*! Default string/char length. */ + inline constexpr int DefaultStringLength = 255; + + /*! Column types. */ + enum struct ColumnType + { + Char, + String, + TinyText, + Text, + MediumText, + LongText, + BigInteger, + Integer, + MediumInteger, + TinyInteger, + SmallInteger, + Float, + Double, + Decimal, + Real, // PostgreSQL only + Boolean, + Enum, + Set, // MySQL only + Json, + Jsonb, + Date, + DateTime, + DateTimeTz, + Time, + TimeTz, + Timestamp, + TimestampTz, + Year, + Binary, + Uuid, + IpAddress, + MacAddress, + Geometry, + Point, + LineString, + Polygon, + GeometryCollection, + MultiPoint, + MultiLineString, + MultiPolygon, + MultiPolygonZ, // PostgreSQL only + Computed, // SqlServer only + }; + + /*! Auto increment value for a column. */ + struct AutoIncrementColumnValue + { + QString columnName; + std::optional value; + }; + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_SCHEMATYPES_HPP diff --git a/include/orm/schema/sqliteschemabuilder.hpp b/include/orm/schema/sqliteschemabuilder.hpp index d689ca90f..697f36a0b 100644 --- a/include/orm/schema/sqliteschemabuilder.hpp +++ b/include/orm/schema/sqliteschemabuilder.hpp @@ -9,10 +9,10 @@ TINY_SYSTEM_HEADER TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema +namespace Orm::SchemaNs { - /*! SQLite schema repository class. */ + /*! SQLite schema builder class. */ class SQLiteSchemaBuilder : public SchemaBuilder { Q_DISABLE_COPY(SQLiteSchemaBuilder) @@ -25,7 +25,7 @@ namespace Orm::Schema inline ~SQLiteSchemaBuilder() override = default; }; -} // namespace Orm::Schema +} // namespace Orm::SchemaNs TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/tiny/concerns/queriesrelationships.hpp b/include/orm/tiny/concerns/queriesrelationships.hpp index b90e0f06b..7b6d5fe54 100644 --- a/include/orm/tiny/concerns/queriesrelationships.hpp +++ b/include/orm/tiny/concerns/queriesrelationships.hpp @@ -654,7 +654,7 @@ namespace Private template void QueriesRelationships::createHasNestedStore( const QString &comparison, const qint64 count, - const std::function &)> &callback) const + const std::function &)> &callback) const { // BUG clang doesn't know how to init. aggregate ( by forward declaration inside, using construct in place ) silverqx #ifdef __clang__ diff --git a/include/orm/tiny/model.hpp b/include/orm/tiny/model.hpp index a384160b5..a7c8804a3 100644 --- a/include/orm/tiny/model.hpp +++ b/include/orm/tiny/model.hpp @@ -44,7 +44,8 @@ namespace Orm::Tiny public Tiny::Concerns::GuardsAttributes, public Tiny::Concerns::HasRelationships, public Tiny::Concerns::HasTimestamps, - public ModelProxies + public ModelProxies, + public IsModel { // To access getUserXx() methods friend Concerns::GuardsAttributes; @@ -1320,3 +1321,5 @@ TINYORM_END_COMMON_NAMESPACE // CUR Belongs To Many firstOrNew, firstOrCreate, and updateOrCreate Methods, revisit first and second argument silverqx // CUR add attribute to touch() method silverqx // SEC fix LoadLibrary() before qsql plugin loads? https://www.qt.io/blog/security-advisory-qlockfile-qauthenticator-windows-platform-plugin silverqx +// CUR1 connection, control disable/enable logging at runtime silverqx +// CUR cmake, make TinyUtils_target in TinyTestCommon.cmake optional, not always needed to link to silverqx diff --git a/include/orm/tiny/tinybuilder.hpp b/include/orm/tiny/tinybuilder.hpp index b7f34fd16..c53ccf623 100644 --- a/include/orm/tiny/tinybuilder.hpp +++ b/include/orm/tiny/tinybuilder.hpp @@ -906,7 +906,7 @@ namespace Orm::Tiny // template // Builder & // Builder::callScope( -// const std::function &scope, +// const std::function &scope, // Args &&...parameters) // { // std::invoke(scope, *this, std::forward(parameters)...); diff --git a/include/orm/tiny/tinyconcepts.hpp b/include/orm/tiny/tinyconcepts.hpp index 97b5c1f6e..11be6ee6d 100644 --- a/include/orm/tiny/tinyconcepts.hpp +++ b/include/orm/tiny/tinyconcepts.hpp @@ -19,6 +19,12 @@ namespace Orm::Tiny template concept AllRelationsConcept = (!std::same_as && ...); + class IsModel; + + /*! Concept to check whether a passed type is the model class. */ + template + concept ModelConcept = std::derived_from; + } // namespace Orm::Tiny TINYORM_END_COMMON_NAMESPACE diff --git a/include/orm/tiny/tinytypes.hpp b/include/orm/tiny/tinytypes.hpp index d09658c19..e2a842990 100644 --- a/include/orm/tiny/tinytypes.hpp +++ b/include/orm/tiny/tinytypes.hpp @@ -73,6 +73,10 @@ namespace Tiny bool touch = true; }; + /*! The tag for the model. */ + class IsModel + {}; + namespace Concerns { /*! QueriesRelationships builder type passed to the callback. */ diff --git a/include/orm/utils/container.hpp b/include/orm/utils/container.hpp new file mode 100644 index 000000000..b5d576cb1 --- /dev/null +++ b/include/orm/utils/container.hpp @@ -0,0 +1,59 @@ +#pragma once +#ifndef ORM_UTILS_CONTAINER_HPP +#define ORM_UTILS_CONTAINER_HPP + +#include "orm/macros/systemheader.hpp" +TINY_SYSTEM_HEADER + +#include "orm/constants.hpp" +#include "orm/ormconcepts.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::Utils +{ + + /*! Containers related library class. */ + class Container + { + Q_DISABLE_COPY(Container) + + public: + /*! Deleted default constructor, this is a pure library class. */ + Container() = delete; + /*! Deleted destructor. */ + ~Container() = delete; + + /*! Convert a string container into a (comma) delimited string. */ + template + static QString + join(const T &container, D &&delimiter = Constants::COMMA); + }; + + template + QString Container::join(const T &container, D &&delimiter) + { + QString columnized; + // Estimate a size to avoid resizing, 7 for an item and 2 for the delimiter + columnized.reserve(container.size() * (7 + 2)); + + if (container.isEmpty()) + return columnized; + + const auto end = container.cend() - 1; + auto it = container.begin(); + + for (; it < end; ++it) + columnized += Constants::NOSPACE.arg(*it).arg(std::forward(delimiter)); + + if (it == end) + columnized += *it; + + return columnized; + } + +} // namespace Orm::Utils + +TINYORM_END_COMMON_NAMESPACE + +#endif // ORM_UTILS_CONTAINER_HPP diff --git a/src/orm/basegrammar.cpp b/src/orm/basegrammar.cpp index 466d86386..04e7edadf 100644 --- a/src/orm/basegrammar.cpp +++ b/src/orm/basegrammar.cpp @@ -15,10 +15,10 @@ namespace Orm parametrize uses Container type and Parametrize constraint. columnize() is used for column names containers (constrained by ColumnContainer concept) and it calls wrapArray() internally, columnize uses ColumnContainer - constraint. + constraint, it converts a vector of column names into a wrapped comma delimited string. Values or columns/tables/identifiers can also be the Query::Expression. The Query::Expression is always converted to the QString and appended to the query. - quoteString() can be used to quote string literals, it is not used anywhere for now. + quoteString() can be used to quote string literals. */ const QString &BaseGrammar::getDateFormat() const @@ -57,7 +57,7 @@ QString BaseGrammar::wrap(const Column &value) const // NOLINTNEXTLINE(misc-no-recursion) QString BaseGrammar::wrapTable(const QString &table) const { - return wrap(QStringLiteral("%1%2").arg(m_tablePrefix, table), true); + return wrap(NOSPACE.arg(m_tablePrefix, table), true); } QString BaseGrammar::wrapTable(const FromClause &table) const @@ -106,6 +106,8 @@ QString BaseGrammar::unqualifyColumn(const QString &column) const return column.split(DOT).last().trimmed(); } +/* protected */ + QString BaseGrammar::parameter(const QVariant &value) const { return isExpression(value) ? getValue(value).value() @@ -121,7 +123,7 @@ QString BaseGrammar::wrapAliasedValue(const QString &value, const bool prefixAli as well in order to generate proper syntax. If this is a column of course no prefix is necessary. The condition will be true when from wrapTable. */ if (prefixAlias) - segments[1] = QStringLiteral("%1%2").arg(m_tablePrefix, segments[1]); + segments[1] = NOSPACE.arg(m_tablePrefix, segments[1]); return QStringLiteral("%1 as %2").arg(wrap(segments[0]), wrapValue(segments[1])); } @@ -179,25 +181,6 @@ QString BaseGrammar::getAliasFromFrom(const QString &from) const return segments.last(); } -QString BaseGrammar::columnizeInternal(const QVector &columns) const -{ - QString columnized; - - if (columns.isEmpty()) - return columnized; - - const auto end = columns.cend() - 1; - auto it = columns.begin(); - - for (; it < end; ++it) - columnized += QStringLiteral("%1, ").arg(*it); - - if (it == end) - columnized += *it; - - return columnized; -} - } // namespace Orm TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/connectors/connectionfactory.cpp b/src/orm/connectors/connectionfactory.cpp index e3c0090bc..fc2e26bb8 100644 --- a/src/orm/connectors/connectionfactory.cpp +++ b/src/orm/connectors/connectionfactory.cpp @@ -63,6 +63,11 @@ ConnectionFactory::parseConfig(QVariantHash &config, const QString &name) const if (!config.contains(options_)) config.insert(options_, QVariantHash()); + if (!config.contains(prefix_indexes)) + config.insert(prefix_indexes, false); + + // FUTURE connector, this can be enhanced, eg. add default values per driver, eg. engine_ for mysql is missing, can not be added because is driver specific silverqx + return config; } diff --git a/src/orm/connectors/postgresconnector.cpp b/src/orm/connectors/postgresconnector.cpp index e671a431f..93115b0a5 100644 --- a/src/orm/connectors/postgresconnector.cpp +++ b/src/orm/connectors/postgresconnector.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "orm/constants.hpp" #include "orm/exceptions/queryerror.hpp" #include "orm/utils/type.hpp" @@ -85,11 +87,12 @@ void PostgresConnector::configureTimezone(const QSqlDatabase &connection, QSqlQuery query(connection); - static const QStringList local {"local", "default"}; + static const std::unordered_set local {QStringLiteral("local"), + QStringLiteral("default")}; const auto timezone = config[timezone_].value(); - if (local.contains(timezone, Qt::CaseInsensitive)) { + if (local.contains(timezone.toLower())) { if (query.exec(QStringLiteral("set time zone %1").arg(timezone))) return; } else diff --git a/src/orm/constants_extern.cpp b/src/orm/constants_extern.cpp index 5ca18ba89..fc5d68f0d 100644 --- a/src/orm/constants_extern.cpp +++ b/src/orm/constants_extern.cpp @@ -38,6 +38,8 @@ namespace Orm::Constants const QString UPDATED_AT = QStringLiteral("updated_at"); const QString PARENTH_ONE = QStringLiteral("(%1)"); const QString NEWLINE = QStringLiteral("\n"); + const QString SPACE_IN = QStringLiteral("%1 %2"); + const QString NOSPACE = QStringLiteral("%1%2"); const QString QMYSQL = QStringLiteral("QMYSQL"); const QString QPSQL = QStringLiteral("QPSQL"); @@ -59,10 +61,12 @@ namespace Orm::Constants const QString prefix_ = QStringLiteral("prefix"); const QString options_ = QStringLiteral("options"); const QString strict_ = QStringLiteral("strict"); + const QString engine_ = QStringLiteral("engine"); const QString isolation_level = QStringLiteral("isolation_level"); const QString foreign_key_constraints = QStringLiteral("foreign_key_constraints"); const QString check_database_exists = QStringLiteral("check_database_exists"); + const QString prefix_indexes = QStringLiteral("prefix_indexes"); const QString H127001 = QStringLiteral("127.0.0.1"); const QString LOCALHOST = QStringLiteral("localhost"); @@ -75,6 +79,10 @@ namespace Orm::Constants const QString PUBLIC = QStringLiteral("public"); const QString UTF8 = QStringLiteral("utf8"); const QString UTF8MB4 = QStringLiteral("utf8mb4"); + const QString InnoDB = QStringLiteral("InnoDB"); + const QString MyISAM = QStringLiteral("MyISAM"); + + const QString NotImplemented = QStringLiteral("Not implemented :/."); // Comparison/logical/search operators const QString EQ = QStringLiteral("="); diff --git a/src/orm/databaseconnection.cpp b/src/orm/databaseconnection.cpp index fa34ea1a8..7dea5fec5 100644 --- a/src/orm/databaseconnection.cpp +++ b/src/orm/databaseconnection.cpp @@ -367,6 +367,11 @@ bool DatabaseConnection::pingDatabase() .arg(driverName())); } +QSqlDriver *DatabaseConnection::driver() +{ + return getQtConnection().driver(); +} + void DatabaseConnection::reconnect() const { if (!m_reconnector) diff --git a/src/orm/databasemanager.cpp b/src/orm/databasemanager.cpp index 789547315..ee3e1986f 100644 --- a/src/orm/databasemanager.cpp +++ b/src/orm/databasemanager.cpp @@ -2,6 +2,7 @@ #include "orm/concerns/hasconnectionresolver.hpp" #include "orm/exceptions/invalidargumenterror.hpp" +#include "orm/schema.hpp" TINYORM_BEGIN_COMMON_NAMESPACE @@ -213,6 +214,11 @@ bool DatabaseManager::pingDatabase(const QString &connection) return this->connection(connection).pingDatabase(); } +QSqlDriver *DatabaseManager::driver(const QString &connection) +{ + return this->connection(connection).driver(); +} + namespace { const auto *const InstanceExceptionMessage = @@ -312,10 +318,14 @@ bool DatabaseManager::removeConnection(const QString &name) if ((*m_connections).erase(name_) == 0) return false; + // Remove a cached schema builder for the currently removing connection + if (Schema::m_schemaBuildersCache.contains(name_)) + Schema::m_schemaBuildersCache.erase(name_); + + // Remove TinyORM configuration + (*m_configuration).remove(name_); // Remove Qt's database connection QSqlDatabase::removeDatabase(name_); - // Also remove configuration - (*m_configuration).remove(name_); resetDefaultConnection_(); diff --git a/src/orm/db.cpp b/src/orm/db.cpp index 6a1814fac..329c67e99 100644 --- a/src/orm/db.cpp +++ b/src/orm/db.cpp @@ -247,6 +247,11 @@ bool DB::pingDatabase(const QString &connection) return manager().connection(connection).pingDatabase(); } +QSqlDriver *DB::driver(const QString &connection) +{ + return manager().connection(connection).driver(); +} + bool DB::countingElapsed(const QString &connection) { return manager().connection(connection).countingElapsed(); diff --git a/src/orm/mysqlconnection.cpp b/src/orm/mysqlconnection.cpp index a3885e6ba..f449000d8 100644 --- a/src/orm/mysqlconnection.cpp +++ b/src/orm/mysqlconnection.cpp @@ -50,7 +50,7 @@ std::unique_ptr MySqlConnection::getSchemaBuilder() if (!m_schemaGrammar) useDefaultSchemaGrammar(); - return std::make_unique(*this); + return std::make_unique(*this); } bool MySqlConnection::isMaria() @@ -146,7 +146,7 @@ std::unique_ptr MySqlConnection::getDefaultQueryGrammar() const std::unique_ptr MySqlConnection::getDefaultSchemaGrammar() const { // Ownership of a unique_ptr() - auto grammar = std::make_unique(); + auto grammar = std::make_unique(); withTablePrefix(*grammar); diff --git a/src/orm/postgresconnection.cpp b/src/orm/postgresconnection.cpp index 570752c42..82723c61b 100644 --- a/src/orm/postgresconnection.cpp +++ b/src/orm/postgresconnection.cpp @@ -32,7 +32,7 @@ std::unique_ptr PostgresConnection::getSchemaBuilder() if (!m_schemaGrammar) useDefaultSchemaGrammar(); - return std::make_unique(*this); + return std::make_unique(*this); } std::unique_ptr PostgresConnection::getDefaultQueryGrammar() const @@ -48,7 +48,7 @@ std::unique_ptr PostgresConnection::getDefaultQueryGrammar() const std::unique_ptr PostgresConnection::getDefaultSchemaGrammar() const { // Ownership of a unique_ptr() - auto grammar = std::make_unique(); + auto grammar = std::make_unique(); withTablePrefix(*grammar); diff --git a/src/orm/query/grammars/grammar.cpp b/src/orm/query/grammars/grammar.cpp index ff20f8da8..10701b712 100644 --- a/src/orm/query/grammars/grammar.cpp +++ b/src/orm/query/grammars/grammar.cpp @@ -43,7 +43,7 @@ QString Grammar::compileInsert(const QueryBuilder &query, table, // Columns are obtained only from a first QMap columnize(values.at(0).keys()), - compileInsertToVector(values).join(COMMA)); + columnizeWithoutWrap(compileInsertToVector(values))); } QString Grammar::compileInsertOrIgnore(const QueryBuilder &/*unused*/, @@ -152,8 +152,9 @@ QStringList Grammar::compileComponents(const QueryBuilder &query) const { QStringList sql; - const auto &compileMap = getCompileMap(); - for (const auto &component : compileMap) + for (const auto &compileMap = getCompileMap(); + const auto &component : compileMap + ) if (component.isset) if (component.isset(query)) sql.append(std::invoke(component.compileMethod, query)); @@ -227,9 +228,8 @@ QStringList Grammar::compileWheresToVector(const QueryBuilder &query) const compiledWheres.reserve(wheres.size()); for (const auto &where : wheres) - compiledWheres << QStringLiteral("%1 %2") - .arg(where.condition, - std::invoke(getWhereMethod(where.type), where)); + compiledWheres << SPACE_IN.arg(where.condition, + std::invoke(getWhereMethod(where.type), where)); return compiledWheres; } @@ -242,8 +242,7 @@ QString Grammar::concatenateWhereClauses(const QueryBuilder &query, ? QStringLiteral("where") : QStringLiteral("on"); - return QStringLiteral("%1 %2").arg(conjunction, - removeLeadingBoolean(sql.join(SPACE))); + return SPACE_IN.arg(conjunction, removeLeadingBoolean(sql.join(SPACE))); } QString Grammar::compileJoins(const QueryBuilder &query) const @@ -292,7 +291,7 @@ QString Grammar::compileHaving(const HavingConditionItem &having) const T_UNLIKELY case HavingType::RAW: - return QStringLiteral("%1 %2").arg(having.condition, having.sql); + return SPACE_IN.arg(having.condition, having.sql); T_UNLIKELY default: @@ -312,7 +311,8 @@ QString Grammar::compileOrders(const QueryBuilder &query) const if (query.getOrders().isEmpty()) return QLatin1String(""); - return QStringLiteral("order by %1").arg(compileOrdersToVector(query).join(COMMA)); + return QStringLiteral("order by %1") + .arg(columnizeWithoutWrap(compileOrdersToVector(query))); } QStringList Grammar::compileOrdersToVector(const QueryBuilder &query) const @@ -324,8 +324,8 @@ QStringList Grammar::compileOrdersToVector(const QueryBuilder &query) const for (const auto &order : orders) if (order.sql.isEmpty()) T_LIKELY - compiledOrders << QStringLiteral("%1 %2") - .arg(wrap(order.column), order.direction.toLower()); + compiledOrders << SPACE_IN.arg(wrap(order.column), + order.direction.toLower()); else T_UNLIKELY compiledOrders << order.sql; @@ -450,7 +450,7 @@ Grammar::compileUpdateColumns(const QVector &values) const wrap(assignment.column), parameter(assignment.value)); - return compiledAssignments.join(COMMA); + return columnizeWithoutWrap(compiledAssignments); } QString diff --git a/src/orm/query/grammars/mysqlgrammar.cpp b/src/orm/query/grammars/mysqlgrammar.cpp index 61b45eaa5..6868bc631 100644 --- a/src/orm/query/grammars/mysqlgrammar.cpp +++ b/src/orm/query/grammars/mysqlgrammar.cpp @@ -92,6 +92,7 @@ QString MySqlGrammar::wrapValue(QString value) const const QMap & MySqlGrammar::getCompileMap() const { + // CUR schema, update all this comments silverqx /* Needed, because some compileXx() methods are overloaded, this way I will capture 'this' reference and the compileMethod rvalue reference in the following lambda and simply save std::function<> in the SelectComponentValue's compileMethod data diff --git a/src/orm/query/grammars/postgresgrammar.cpp b/src/orm/query/grammars/postgresgrammar.cpp index 9d0e06949..b59508525 100644 --- a/src/orm/query/grammars/postgresgrammar.cpp +++ b/src/orm/query/grammars/postgresgrammar.cpp @@ -92,7 +92,7 @@ QString PostgresGrammar::compileUpdateColumns(const QVector &values) wrap(unqualifyColumn(assignment.column)), parameter(assignment.value)); - return compiledAssignments.join(COMMA); + return columnizeWithoutWrap(compiledAssignments); } const QMap & diff --git a/src/orm/query/grammars/sqlitegrammar.cpp b/src/orm/query/grammars/sqlitegrammar.cpp index e65079863..7af67710e 100644 --- a/src/orm/query/grammars/sqlitegrammar.cpp +++ b/src/orm/query/grammars/sqlitegrammar.cpp @@ -69,7 +69,7 @@ QString SQLiteGrammar::compileUpdateColumns(const QVector &values) c wrap(unqualifyColumn(assignment.column)), parameter(assignment.value)); - return compiledAssignments.join(COMMA); + return columnizeWithoutWrap(compiledAssignments); } const QMap & diff --git a/src/orm/schema.cpp b/src/orm/schema.cpp new file mode 100644 index 000000000..ec5ae463c --- /dev/null +++ b/src/orm/schema.cpp @@ -0,0 +1,178 @@ +#include "orm/schema.hpp" + +#include "orm/macros/likely.hpp" +#include "orm/schema/schemabuilder.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm +{ + +std::unordered_map> Schema::m_schemaBuildersCache; +std::shared_ptr Schema::m_manager; + +/* public */ + +/* Proxy methods to the SchemaBuilder */ + +QSqlQuery Schema::createDatabase(const QString &name, const QString &connection) +{ + return schemaBuilder(connection).createDatabase(name); +} + +QSqlQuery Schema::dropDatabaseIfExists(const QString &name, const QString &connection) +{ + return schemaBuilder(connection).dropDatabaseIfExists(name); +} + +void Schema::create( + const QString &table, const std::function &callback, + const QString &connection) +{ + schemaBuilder(connection).create(table, callback); +} + +void Schema::table( + const QString &table, const std::function &callback, + const QString &connection) +{ + schemaBuilder(connection).table(table, callback); +} + +void Schema::drop(const QString &table, const QString &connection) +{ + schemaBuilder(connection).drop(table); +} + +void Schema::dropIfExists(const QString &table, const QString &connection) +{ + schemaBuilder(connection).dropIfExists(table); +} + +void Schema::rename(const QString &from, const QString &to, const QString &connection) +{ + schemaBuilder(connection).rename(from, to); +} + +void Schema::dropColumns(const QString &table, const QVector &columns, + const QString &connection) +{ + schemaBuilder(connection).dropColumns(table, columns); +} + +void Schema::dropColumn(const QString &table, const QString &column, + const QString &connection) +{ + schemaBuilder(connection).dropColumns(table, {column}); +} + +void Schema::renameColumn(const QString &table, const QString &from, + const QString &to, const QString &connection) +{ + schemaBuilder(connection).renameColumn(table, from, to); +} + +void Schema::dropAllTables(const QString &connection) +{ + schemaBuilder(connection).dropAllTables(); +} + +void Schema::dropAllViews(const QString &connection) +{ + schemaBuilder(connection).dropAllViews(); +} + +void Schema::dropAllTypes(const QString &connection) +{ + schemaBuilder(connection).dropAllTypes(); +} + +QSqlQuery Schema::getAllTables(const QString &connection) +{ + return schemaBuilder(connection).getAllTables(); +} + +QSqlQuery Schema::getAllViews(const QString &connection) +{ + return schemaBuilder(connection).getAllViews(); +} + +QSqlQuery Schema::enableForeignKeyConstraints(const QString &connection) +{ + return schemaBuilder(connection).enableForeignKeyConstraints(); +} + +QSqlQuery Schema::disableForeignKeyConstraints(const QString &connection) +{ + return schemaBuilder(connection).disableForeignKeyConstraints(); +} + +QStringList Schema::getColumnListing(const QString &table, const QString &connection) +{ + return schemaBuilder(connection).getColumnListing(table); +} + +bool Schema::hasTable(const QString &table, const QString &connection) +{ + return schemaBuilder(connection).hasTable(table); +} + +bool Schema::hasColumn(const QString &table, const QString &column, + const QString &connection) +{ + return schemaBuilder(connection).hasColumn(table, column); +} + +bool Schema::hasColumns(const QString &table, const QVector &columns, + const QString &connection) +{ + return schemaBuilder(connection).hasColumns(table, columns); +} + +/* Schema */ + +SchemaBuilder &Schema::connection(const QString &name) +{ + return schemaBuilder(name); +} + +SchemaBuilder &Schema::on(const QString &name) +{ + return schemaBuilder(name); +} + +/* Others */ + +void Schema::defaultStringLength(const int length) +{ + Blueprint::defaultStringLength(length); +} + +/* private */ + +SchemaBuilder &Schema::schemaBuilder(const QString &connection) +{ + /* Cache obtained schema builders, they should always be the same for the given + connection name, cached value is removed from the cache in + DatabaseManager::removeConnection() */ + if (m_schemaBuildersCache.contains(connection)) + return *m_schemaBuildersCache[connection]; + + // Ownership of a unique_ptr() + return *(m_schemaBuildersCache[connection] = manager().connection(connection) + .getSchemaBuilder()); +} + +DatabaseManager &Schema::manager() +{ + if (m_manager) T_LIKELY + return *m_manager; + + else T_UNLIKELY + return *(m_manager = DatabaseManager::instance()); +} + +} // namespace Orm + +TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/blueprint.cpp b/src/orm/schema/blueprint.cpp new file mode 100644 index 000000000..1c66b5fa3 --- /dev/null +++ b/src/orm/schema/blueprint.cpp @@ -0,0 +1,733 @@ +#include "orm/schema/blueprint.hpp" + +#include +#include +#include + +#include "orm/databaseconnection.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + +/* public */ + +int Blueprint::DefaultStringLength = SchemaNs::DefaultStringLength; + +Blueprint::Blueprint( + QString table, const std::function &callback, + QString &&prefix +) + : m_table(std::move(table)) + , m_prefix(std::move(prefix)) +{ + /* Pretty sure that there will be at least 2 columns and 1-2 commands, so reserve + some memory to avoid resizing. */ + m_columns.reserve(4); + m_commands.reserve(3); + + if (callback) + std::invoke(callback, *this); +} + +void Blueprint::build(DatabaseConnection &connection, const SchemaGrammar &grammar) +{ + // TODO clazy, old clazy check range-loop, remove after ugprade to newer clazy (1.11) silverqx + for (const auto &queryString : toSql(connection, grammar)) // clazy:exclude=range-loop,range-loop-detach + connection.statement(queryString); +} + +QVector Blueprint::toSql(const DatabaseConnection &connection, + const SchemaGrammar &grammar) +{ + addImpliedCommands(grammar); + + // FUTURE schema, sqlite silverqx + /* Each type of command has a corresponding compiler function on the schema + grammar which is used to build the necessary SQL statements to build + the blueprint element, so we'll just call that compilers function. */ +// ensureCommandsAreValid(connection); + + QVector statements; + // Reserve * 2 might be enough, can't be predicted :/ + statements.reserve(m_commands.size() * 2); + + for (const auto &command : std::as_const(m_commands)) + statements += grammar.invokeCompileMethod(command, connection, *this); + + return statements; +} + +ColumnDefinitionReference<> Blueprint::create() +{ + return addCommand(Create); +} + +void Blueprint::after(const QString &column, + const std::function &callback) +{ + m_after = column; + + std::invoke(callback, *this); + + m_after.clear(); +} + +ColumnDefinitionReference<> Blueprint::drop() +{ + return addCommand(Drop); +} + +ColumnDefinitionReference<> Blueprint::dropIfExists() +{ + return addCommand(DropIfExists); +} + +ColumnDefinitionReference<> Blueprint::dropColumns(const QVector &columns) +{ + return addCommand(DropColumn, + {.columns = QVector(columns.cbegin(), columns.cend())}); +} + +ColumnDefinitionReference<> Blueprint::dropColumn(const QString &column) +{ + return addCommand(DropColumn, {.columns = {column}}); +} + +void Blueprint::dropTimestamps() +{ + dropColumns({CREATED_AT, UPDATED_AT}); +} + +ColumnDefinitionReference<> Blueprint::rename(const QString &to) +{ + return addCommand(Rename, {.to = to}); +} + +ColumnDefinitionReference<> +Blueprint::renameColumn(const QString &from, const QString &to) +{ + return addCommand(RenameColumn, {.from_ = from, .to = to}); +} + +ColumnDefinitionReference<> +Blueprint::primary(const QVector &columns, const QString &name, + const QString &algorithm) +{ + return indexCommand(Primary, columns, name, algorithm); +} + +ColumnDefinitionReference<> +Blueprint::unique(const QVector &columns, const QString &name, + const QString &algorithm) +{ + return indexCommand(Unique, columns, name, algorithm); +} + +ColumnDefinitionReference<> +Blueprint::index(const QVector &columns, const QString &name, + const QString &algorithm) +{ + return indexCommand(Index, columns, name, algorithm); // NOLINT(readability-suspicious-call-argument) +} + +ColumnDefinitionReference<> +Blueprint::fullText(const QVector &columns, const QString &name, + const QString &algorithm) +{ + return indexCommand(Fulltext, columns, name, algorithm); +} + +// CUR schema, it looks like spatial index can not be created on multiple columns on mysql, if its true remove QVector overloads, error 1070 Too many key parts specified; max 1 parts allowed silverqx +ColumnDefinitionReference<> +Blueprint::spatialIndex(const QVector &columns, const QString &name) +{ + return indexCommand(SpatialIndex, columns, name); +} + +ColumnDefinitionReference<> +Blueprint::rawIndex(const Expression &expression, const QString &name) +{ + return addCommand(Index, {.index = name, .columns = {expression}}); +} + +ForeignKeyDefinitionReference +Blueprint::foreign(const QVector &columns, const QString &name) +{ + return indexCommand(Foreign, columns, name); +} + +ColumnDefinitionReference<> Blueprint::dropPrimary(const QVector &columns) +{ + return dropIndexCommand(DropPrimary, Primary, columns); +} + +ColumnDefinitionReference<> Blueprint::dropUnique(const QVector &columns) +{ + return dropIndexCommand(DropUnique, Unique, columns); +} + +ColumnDefinitionReference<> Blueprint::dropIndex(const QVector &columns) +{ + return dropIndexCommand(DropIndex, Index, columns); +} + +ColumnDefinitionReference<> Blueprint::dropFullText(const QVector &columns) +{ + return dropIndexCommand(DropFullText, Fulltext, columns); +} + +ColumnDefinitionReference<> Blueprint::dropSpatialIndex(const QVector &columns) +{ + return dropIndexCommand(DropSpatialIndex, SpatialIndex, columns); +} + +ColumnDefinitionReference<> Blueprint::dropForeign(const QVector &columns) +{ + return dropIndexCommand(DropForeign, Foreign, columns); +} + +ColumnDefinitionReference<> Blueprint::dropConstrainedForeignId(const QString &column) +{ + dropForeign(QVector {column}); + + return dropColumn(column); +} + +ColumnDefinitionReference<> Blueprint::renameIndex(const QString &from, const QString &to) +{ + return addCommand(RenameIndex, {.from_ = from, .to = to}); +} + +ForeignIdColumnDefinitionReference Blueprint::foreignId(const QString &column) +{ + return {*this, unsignedBigInteger(column)}; +} + +ForeignIdColumnDefinitionReference Blueprint::foreignUuid(const QString &column) +{ + return {*this, uuid(column)}; +} + +ColumnDefinitionReference<> +Blueprint::integer(const QString &column, const bool autoIncrement, + const bool isUnsigned) +{ + return addColumn(ColumnType::Integer, column, {.autoIncrement = autoIncrement, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> +Blueprint::tinyInteger(const QString &column, const bool autoIncrement, + const bool isUnsigned) +{ + return addColumn(ColumnType::TinyInteger, column, {.autoIncrement = autoIncrement, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> +Blueprint::smallInteger(const QString &column, const bool autoIncrement, + const bool isUnsigned) +{ + return addColumn(ColumnType::SmallInteger, column, {.autoIncrement = autoIncrement, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> +Blueprint::mediumInteger(const QString &column, const bool autoIncrement, + const bool isUnsigned) +{ + return addColumn(ColumnType::MediumInteger, column, {.autoIncrement = autoIncrement, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> +Blueprint::bigInteger(const QString &column, const bool autoIncrement, + const bool isUnsigned) +{ + return addColumn(ColumnType::BigInteger, column, {.autoIncrement = autoIncrement, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> Blueprint::Char(const QString &column, const int length) +{ + return addColumn(ColumnType::Char, column, {.length = length}); +} + +ColumnDefinitionReference<> Blueprint::string(const QString &column, const int length) +{ + return addColumn(ColumnType::String, column, {.length = length}); +} + +ColumnDefinitionReference<> Blueprint::tinyText(const QString &column) +{ + return addColumn(ColumnType::TinyText, column); +} + +ColumnDefinitionReference<> Blueprint::text(const QString &column) +{ + return addColumn(ColumnType::Text, column); +} + +ColumnDefinitionReference<> Blueprint::mediumText(const QString &column) +{ + return addColumn(ColumnType::MediumText, column); +} + +ColumnDefinitionReference<> Blueprint::longText(const QString &column) +{ + return addColumn(ColumnType::LongText, column); +} + +ColumnDefinitionReference<> +Blueprint::Float(const QString &column, const std::optional total, + const std::optional places, const bool isUnsigned) +{ + return addColumn(ColumnType::Float, column, {.places = places, .total = total, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> +Blueprint::Double(const QString &column, const std::optional total, + const std::optional places, const bool isUnsigned) +{ + return addColumn(ColumnType::Double, column, {.places = places, .total = total, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> +Blueprint::decimal(const QString &column, const std::optional total, + const std::optional places, const bool isUnsigned) +{ + return addColumn(ColumnType::Decimal, column, {.places = places, .total = total, + .isUnsigned = isUnsigned}); +} + +ColumnDefinitionReference<> Blueprint::boolean(const QString &column) +{ + return addColumn(ColumnType::Boolean, column); +} + +ColumnDefinitionReference<> +Blueprint::Enum(const QString &column, const QVector &allowed) +{ + return addColumn(ColumnType::Enum, column, {.allowed = allowed}); +} + +ColumnDefinitionReference<> +Blueprint::set(const QString &column, const QVector &allowed) +{ + return addColumn(ColumnType::Set, column, {.allowed = allowed}); +} + +ColumnDefinitionReference<> Blueprint::json(const QString &column) +{ + return addColumn(ColumnType::Json, column); +} + +ColumnDefinitionReference<> Blueprint::jsonb(const QString &column) +{ + return addColumn(ColumnType::Jsonb, column); +} + +ColumnDefinitionReference<> Blueprint::date(const QString &column) +{ + return addColumn(ColumnType::Date, column); +} + +ColumnDefinitionReference<> +Blueprint::dateTime(const QString &column, const int precision) +{ + return addColumn(ColumnType::DateTime, column, {.precision = precision}); +} + +ColumnDefinitionReference<> +Blueprint::dateTimeTz(const QString &column, const int precision) +{ + return addColumn(ColumnType::DateTimeTz, column, {.precision = precision}); +} + +ColumnDefinitionReference<> Blueprint::time(const QString &column, const int precision) +{ + return addColumn(ColumnType::Time, column, {.precision = precision}); +} + +ColumnDefinitionReference<> Blueprint::timeTz(const QString &column, const int precision) +{ + return addColumn(ColumnType::TimeTz, column, {.precision = precision}); +} + +ColumnDefinitionReference<> +Blueprint::timestamp(const QString &column, const int precision) +{ + return addColumn(ColumnType::Timestamp, column, {.precision = precision}); +} + +ColumnDefinitionReference<> +Blueprint::timestampTz(const QString &column, const int precision) +{ + return addColumn(ColumnType::TimestampTz, column, {.precision = precision}); +} + +void Blueprint::timestamps(const int precision) +{ + timestamp(CREATED_AT, precision).nullable(); + + timestamp(UPDATED_AT, precision).nullable(); +} + +void Blueprint::timestampsTz(const int precision) +{ + timestampTz(CREATED_AT, precision).nullable(); + + timestampTz(UPDATED_AT, precision).nullable(); +} + +ColumnDefinitionReference<> Blueprint::year(const QString &column) +{ + return addColumn(ColumnType::Year, column); +} + +ColumnDefinitionReference<> Blueprint::binary(const QString &column) +{ + return addColumn(ColumnType::Binary, column); +} + +ColumnDefinitionReference<> Blueprint::uuid(const QString &column) +{ + return addColumn(ColumnType::Uuid, column); +} + +ColumnDefinitionReference<> Blueprint::ipAddress(const QString &column) +{ + return addColumn(ColumnType::IpAddress, column); +} + +ColumnDefinitionReference<> Blueprint::macAddress(const QString &column) +{ + return addColumn(ColumnType::MacAddress, column); +} + +ColumnDefinitionReference<> Blueprint::geometry(const QString &column) +{ + return addColumn(ColumnType::Geometry, column); +} + +ColumnDefinitionReference<> +Blueprint::point(const QString &column, const std::optional srid) +{ + return addColumn(ColumnType::Point, column, {.srid = srid}); +} + +ColumnDefinitionReference<> Blueprint::lineString(const QString &column) +{ + return addColumn(ColumnType::LineString, column); +} + +ColumnDefinitionReference<> Blueprint::polygon(const QString &column) +{ + return addColumn(ColumnType::Polygon, column); +} + +ColumnDefinitionReference<> Blueprint::geometryCollection(const QString &column) +{ + return addColumn(ColumnType::GeometryCollection, column); +} + +ColumnDefinitionReference<> Blueprint::multiPoint(const QString &column) +{ + return addColumn(ColumnType::MultiPoint, column); +} + +ColumnDefinitionReference<> Blueprint::multiLineString(const QString &column) +{ + return addColumn(ColumnType::MultiLineString, column); +} + +ColumnDefinitionReference<> Blueprint::multiPolygon(const QString &column) +{ + return addColumn(ColumnType::MultiPolygon, column); +} + +ColumnDefinitionReference<> Blueprint::multiPolygonZ(const QString &column) +{ + return addColumn(ColumnType::MultiPolygonZ, column); +} + +// CUR schema, test computed column silverqx +ColumnDefinitionReference<> +Blueprint::computed(const QString &column, const QString &expression) +{ + return addColumn(ColumnType::Computed, column, {.expression = expression}); +} + +ColumnDefinitionReference<> Blueprint::rememberToken() +{ + return string(QStringLiteral("remember_token"), 100).nullable(); +} + +ColumnDefinitionReference<> +Blueprint::addColumn(const ColumnType type, const QString &name, + ColumnDefinition &¶meters) +{ + // To be more clear + auto &columnDefinition = parameters; + + columnDefinition.type = type; + columnDefinition.name = name; + + return addColumnDefinition(std::move(columnDefinition)); +} + +QVector Blueprint::getAddedColumns() const +{ + QVector added; + added.reserve(m_columns.size()); + + std::ranges::copy_if(m_columns, std::back_inserter(added), + [](const auto &column) + { + return !column.change; + }); + + return added; +} + +QVector Blueprint::getChangedColumns() const +{ + QVector added; + added.reserve(m_columns.size()); + + std::ranges::copy_if(m_columns, std::back_inserter(added), + [](const auto &column) + { + return column.change; + }); + + return added; +} + +bool Blueprint::hasAutoIncrementColumn() const +{ + return ranges::contains(getAddedColumns(), true, + [](const ColumnDefinition &column) + { + return column.autoIncrement; + }); +} + +QVector Blueprint::autoIncrementStartingValues() const +{ + if (!hasAutoIncrementColumn()) + return {}; + + auto addedColumns = getAddedColumns(); + + // CUR1 ranges, check all pred and proj, now I understand where I have to use auto & or auto &&, note in bash_or_cmd c++ sheet silverqx + return addedColumns + | ranges::views::filter([](const auto addedColumn) + { + return addedColumn.autoIncrement; + }) + | ranges::views::transform([](auto &addedColumn) + -> AutoIncrementColumnValue + { + /* When value was not defined then init. it to std::nullopt and filter out + in the next operator| chain. */ + return {std::move(addedColumn.name), + addedColumn.startingValue ? *addedColumn.startingValue + /* Default value if a startingValue was not + defined, can also be the std::nullopt. */ + : addedColumn.from}; + }) + // Filter out items with std::nullopt values to avoid alter table autoIncrement 0 + | ranges::views::filter([](auto &&autoIncrementColumnValue) + { + return autoIncrementColumnValue.value; + }) + | ranges::to>(); +} + +bool Blueprint::creating() const +{ + return ranges::contains(m_commands, Create, [](const auto &command) + { + return command.name; + }); +} + +Blueprint &Blueprint::removeColumn(const QString &name) +{ + m_columns |= ranges::actions::remove_if([&name](const auto &column) + { + return column.name == name; + }); + + return *this; +} + +void Blueprint::defaultStringLength(const int length) noexcept +{ + DefaultStringLength = length; +} + +/* protected */ + +ColumnDefinitionReference<> Blueprint::addColumnDefinition(ColumnDefinition &&definition) +{ + m_columns.append(std::move(definition)); + + auto &definitionRef = m_columns.last(); // clazy:exclude=detaching-member + + if (!m_after.isEmpty()) { + definitionRef.after.swap(m_after); + + m_after = definitionRef.name; + } + + return definitionRef; +} + +ColumnDefinitionReference<> +Blueprint::addCommand(const QString &name, ColumnDefinition &¶meters) +{ + auto command = createCommand(name, std::move(parameters)); + + m_commands.append(std::move(command)); + + return m_commands.last(); // clazy:exclude=detaching-member +} + +ColumnDefinition +Blueprint::createCommand(const QString &name, ColumnDefinition &¶meters) +{ + // To be more clear + auto &commandDefinition = parameters; + + commandDefinition.name = name; + + // CUR schema, move on reference to rvalue param. silverqx + return std::move(commandDefinition); +} + +void Blueprint::addImpliedCommands(const SchemaGrammar &/*unused*/) +{ + if (!getAddedColumns().isEmpty() && !creating()) + m_commands.prepend(createCommand(Add)); + +// if (!getChangedColumns().isEmpty() && !creating()) +// m_commands.prepend(createCommand(Change)); + + addFluentIndexes(); + +// addFluentCommands(grammar); +} + +void Blueprint::addFluentIndexes() +{ + /*! Index item for fluent indexes. */ + struct FluentIndexItem + { + /*! Index name for the database. */ + std::reference_wrapper> name; + /*! Index type in the string format. */ + QString type; + }; + + for (auto &column : m_columns) + /* Loop over all column's index data members to check if any index has been + defined. */ + for (std::array indexes { + std::to_array({ + {std::ref(column.index_), Index}, + {std::ref(column.primary), Primary}, + {std::ref(column.fulltext), Fulltext}, + {std::ref(column.spatialIndex), SpatialIndex}, + {std::ref(column.unique), Unique} + }) + }; + auto &&indexItem : indexes + ) { + auto &index = indexItem.name.get(); + + if (std::holds_alternative(index)) + continue; + + /* If the index has been specified on the given column, but is simply equal + to "true" (boolean), no name has been specified for this index so the + index method can be called without a name and it will generate one. */ + if (std::holds_alternative(index) && std::get(index)) { + indexCommand(indexItem.type, {column.name}, ""); + + index = false; + + break; + } + + /* If the index has been specified on the given column, and it has a string + value, we'll go ahead and call the index method and pass the name for + the index since the developer specified the explicit name for this. */ + else if(const auto indexName = std::get(index); // NOLINT(readability-else-after-return) + std::holds_alternative(index) && + !indexName.isEmpty() + ) { + indexCommand(indexItem.type, {column.name}, indexName); + + index = false; + + break; + } + } +} + +ColumnDefinitionReference<> +Blueprint::indexCommand(const QString &type, const QVector &columns, + const QString &indexName, const QString &algorithm) +{ + /* If no name was specified for this index, we will create one using a basic + convention of the table name, followed by the columns, followed by an + index type, such as primary or index, which makes the index unique, + eg. posts_user_id_foreign or users_name_unique. */ + return addCommand(type, {.index = indexName.isEmpty() ? createIndexName(type, columns) + : indexName, + .columns = QVector(columns.cbegin(), columns.cend()), + .algorithm = algorithm}); +} + +ColumnDefinitionReference<> +Blueprint::dropIndexCommand(const QString &command, const QString &type, + const QVector &columns) +{ + // Used by dropPrimary("") + if (columns.isEmpty()) + return indexCommand(command, {}, {}); + + /* If the given "index" is actually the vector of columns, the developer means + to drop an index merely by specifying the columns involved without the + conventional name, so we will build the index name from the columns. */ + return dropIndexCommand(command, createIndexName(type, columns)); +} + +// NOTE api different, Eloquent doesn't have overload like this silverqx +ColumnDefinitionReference<> +Blueprint::dropIndexCommand(const QString &command, const QString &index) +{ + return indexCommand(command, {}, index); +} + +QString +Blueprint::createIndexName(const QString &type, const QVector &columns) const +{ + auto index = QStringLiteral("%1_%2_%3") + .arg(NOSPACE.arg(m_prefix, m_table), + Utils::Container::join(columns, UNDERSCORE), + type) + .toLower(); + + return index.replace(DASH, UNDERSCORE).replace(DOT, UNDERSCORE); +} + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/foreignidcolumndefinitionreference.cpp b/src/orm/schema/foreignidcolumndefinitionreference.cpp new file mode 100644 index 000000000..e63f5561c --- /dev/null +++ b/src/orm/schema/foreignidcolumndefinitionreference.cpp @@ -0,0 +1,43 @@ +#include "orm/schema/foreignidcolumndefinitionreference.hpp" + +#include "orm/schema/blueprint.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + +ForeignIdColumnDefinitionReference::ForeignIdColumnDefinitionReference( + Blueprint &blueprint, + const ColumnDefinitionReference<> columnDefinitionReference +) + : ColumnDefinitionReference(columnDefinitionReference.m_columnDefinition) + , m_blueprint(blueprint) + , m_columnDefinition(columnDefinitionReference.m_columnDefinition) +{} + +ForeignKeyDefinitionReference +ForeignIdColumnDefinitionReference::constrained(const QString &table, + const QString &column) +{ + // Guess table name, cut off _column from the foreign index name and make it plural + const auto &foreignName = m_columnDefinition.get().name; + + const auto guessTable = QStringLiteral("%1s") + .arg(foreignName.left( + foreignName.lastIndexOf( + QStringLiteral("_%1").arg(column)))); + + return references(QVector {column}) + .on(table.isEmpty() ? guessTable : table); +} + +ForeignKeyDefinitionReference +ForeignIdColumnDefinitionReference::references(const QVector &columns) +{ + return m_blueprint.foreign(m_columnDefinition.get().name).references(columns); +} + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/foreignkeydefinitionreference.cpp b/src/orm/schema/foreignkeydefinitionreference.cpp new file mode 100644 index 000000000..5e78f221f --- /dev/null +++ b/src/orm/schema/foreignkeydefinitionreference.cpp @@ -0,0 +1,82 @@ +#include "orm/schema/foreignkeydefinitionreference.hpp" + +#include "orm/schema/foreignidcolumndefinitionreference.hpp" +#include "orm/schema/schemaconstants.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs +{ + +ForeignKeyDefinitionReference::ForeignKeyDefinitionReference( + const ColumnDefinitionReference<> columnDefinitionReference +) + : m_columnDefinition(columnDefinitionReference.m_columnDefinition) +{} + +ForeignKeyDefinitionReference::ForeignKeyDefinitionReference( + ForeignIdColumnDefinitionReference foreignIdColumnReference +) + : m_columnDefinition(foreignIdColumnReference.m_columnDefinition) +{} + +ForeignKeyDefinitionReference & +ForeignKeyDefinitionReference::references(const QVector &columns) +{ + m_columnDefinition.get().references = columns; + + return *this; +} + +ForeignKeyDefinitionReference & +ForeignKeyDefinitionReference::on(const QString &table) +{ + m_columnDefinition.get().on = table; + + return *this; +} + +ForeignKeyDefinitionReference & +ForeignKeyDefinitionReference::onDelete(const QString &action) +{ + m_columnDefinition.get().onDelete = action; + + return *this; +} + +ForeignKeyDefinitionReference & +ForeignKeyDefinitionReference::onUpdate(const QString &action) +{ + m_columnDefinition.get().onUpdate = action; + + return *this; +} + +ForeignKeyDefinitionReference &ForeignKeyDefinitionReference::cascadeOnUpdate() +{ + return onUpdate(Cascade); +} + +ForeignKeyDefinitionReference &ForeignKeyDefinitionReference::restrictOnUpdate() +{ + return onUpdate(Restrict); +} + +ForeignKeyDefinitionReference &ForeignKeyDefinitionReference::cascadeOnDelete() +{ + return onDelete(Cascade); +} + +ForeignKeyDefinitionReference &ForeignKeyDefinitionReference::restrictOnDelete() +{ + return onDelete(Restrict); +} + +ForeignKeyDefinitionReference &ForeignKeyDefinitionReference::nullOnDelete() +{ + return onDelete(SetNull); +} + +} // namespace Orm::SchemaNs + +TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/grammars/mysqlschemagrammar.cpp b/src/orm/schema/grammars/mysqlschemagrammar.cpp index 177b3bda9..b5fd73e42 100644 --- a/src/orm/schema/grammars/mysqlschemagrammar.cpp +++ b/src/orm/schema/grammars/mysqlschemagrammar.cpp @@ -1,10 +1,77 @@ #include "orm/schema/grammars/mysqlschemagrammar.hpp" +#include + +#include "orm/databaseconnection.hpp" +#include "orm/macros/threadlocal.hpp" +#include "orm/utils/type.hpp" + TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema::Grammars +using Orm::Constants::charset_; +using Orm::Constants::collation_; +using Orm::Constants::engine_; + +namespace Orm::SchemaNs::Grammars { +/* Compile methods for the SchemaBuilder */ + +/* public */ + +QString MySqlSchemaGrammar::compileCreateDatabase( + const QString &name, DatabaseConnection &connection) const +{ + return QStringLiteral("create database %1 " + "default character set %2 default collate %3") + .arg(wrapValue(name), + wrapValue(connection.getConfig(charset_).value()), + wrapValue(connection.getConfig(collation_).value())); +} + +QString MySqlSchemaGrammar::compileDropDatabaseIfExists(const QString &name) const +{ + return QStringLiteral("drop database if exists %1").arg(wrapValue(name)); +} + +QString MySqlSchemaGrammar::compileDropAllTables(const QVector &tables) const +{ + return QStringLiteral("drop table %1").arg(columnize(tables)); +} + +QString MySqlSchemaGrammar::compileDropAllViews(const QVector &views) const +{ + return QStringLiteral("drop view %1").arg(columnize(views)); +} + +QString MySqlSchemaGrammar::compileGetAllTables() const +{ + return QStringLiteral("SHOW FULL TABLES WHERE table_type = 'BASE TABLE';"); +} + +QString MySqlSchemaGrammar::compileGetAllViews() const +{ + return QStringLiteral("SHOW FULL TABLES WHERE table_type = 'VIEW';"); +} + +QString MySqlSchemaGrammar::compileEnableForeignKeyConstraints() const +{ + return QStringLiteral("SET FOREIGN_KEY_CHECKS=1;"); +} + +QString MySqlSchemaGrammar::compileDisableForeignKeyConstraints() const +{ + return QStringLiteral("SET FOREIGN_KEY_CHECKS=0;"); +} + +QString MySqlSchemaGrammar::compileTableExists() const +{ + return QStringLiteral("select * " + "from `information_schema`.`tables` " + "where `table_schema` = ? and `table_name` = ? and " + "`table_type` = 'BASE TABLE'"); +} + QString MySqlSchemaGrammar::compileColumnListing(const QString &/*unused*/) const { return QStringLiteral("select `column_name` as `column_name` " @@ -12,6 +79,854 @@ QString MySqlSchemaGrammar::compileColumnListing(const QString &/*unused*/) cons "where `table_schema` = ? and `table_name` = ?"); } -} // namespace Orm::Schema::Grammars +/* Compile methods for commands */ + +/* public */ + +QVector +MySqlSchemaGrammar::compileCreate( + const Blueprint &blueprint, const ColumnDefinition &command, + const DatabaseConnection &connection) const +{ + // Primary SQL query for create table + auto sqlCreateTable = compileCreateTable(blueprint, command, connection); + + // Add the encoding option to the SQL for the table + compileCreateEncoding(sqlCreateTable, connection, blueprint); + + // Add storage engine declaration to the SQL query if has been supplied + compileCreateEngine(sqlCreateTable, connection, blueprint); + + // Add autoIncrement starting values to the SQL query if have been supplied + auto autoIncrementStartingValues = compileAutoIncrementStartingValues(blueprint); + + /* Prepare container with all sql queries, autoIncrement for every column uses + alter table, so separate SQL queries are provided for every column. */ + QVector sql {sqlCreateTable}; + if (!autoIncrementStartingValues.isEmpty()) + sql << std::move(autoIncrementStartingValues); + + return sql; +} + +QVector MySqlSchemaGrammar::compileDrop( + const Blueprint &blueprint, const ColumnDefinition &/*unused*/) const +{ + return {QStringLiteral("drop table %1").arg(wrapTable(blueprint))}; +} + +QVector MySqlSchemaGrammar::compileDropIfExists( + const Blueprint &blueprint, const ColumnDefinition &/*unused*/) const +{ + return {QStringLiteral("drop table if exists %1").arg(wrapTable(blueprint))}; +} + +QVector MySqlSchemaGrammar::compileRename( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {QStringLiteral("rename table %1 to %2") + .arg(wrapTable(blueprint), BaseGrammar::wrap(command.to))}; +} + +QVector MySqlSchemaGrammar::compileAdd(const Blueprint &blueprint, + const ColumnDefinition &/*unused*/) const +{ + auto columns = prefixArray(Add, getColumns(blueprint)); + + // Add autoIncrement starting values to the SQL query if have been supplied + auto autoIncrementStartingValues = compileAutoIncrementStartingValues(blueprint); + + auto sqlAlterTable = QStringLiteral("alter table %1 %2") + .arg(wrapTable(blueprint), columnizeWithoutWrap(columns)); + + /* Prepare container with all sql queries, autoIncrement for every column uses + alter table, so separate SQL queries are provided for every column. */ + QVector sql {sqlAlterTable}; + if (!autoIncrementStartingValues.isEmpty()) + sql << std::move(autoIncrementStartingValues); + + return sql; +} + +QVector MySqlSchemaGrammar::compileDropColumn( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {QStringLiteral("alter table %1 %2") + .arg(wrapTable(blueprint), + columnizeWithoutWrap(prefixArray(Drop, + wrapArray(command.columns))))}; +} + +QVector MySqlSchemaGrammar::compileRenameColumn( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {QStringLiteral("alter table %1 rename column %2 to %3") + .arg(wrapTable(blueprint), BaseGrammar::wrap(command.from_), + BaseGrammar::wrap(command.to))}; +} + +QVector MySqlSchemaGrammar::compilePrimary( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + // CUR schema, check this, why it is doing, I have to make command non-const silverqx +// command.name.clear(); + + return {compileKey(blueprint, command, QStringLiteral("primary key"))}; +} + +QVector MySqlSchemaGrammar::compileUnique( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {compileKey(blueprint, command, Unique)}; +} + +QVector MySqlSchemaGrammar::compileIndex( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {compileKey(blueprint, command, Index)}; +} + +QVector MySqlSchemaGrammar::compileFullText( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {compileKey(blueprint, command, Fulltext)}; +} + +QVector MySqlSchemaGrammar::compileSpatialIndex( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {compileKey(blueprint, command, QStringLiteral("spatial index"))}; +} + +QVector MySqlSchemaGrammar::compileDropPrimary( + const Blueprint &blueprint, const ColumnDefinition &/*unused*/) const +{ + return {QStringLiteral("alter table %1 drop primary key") + .arg(wrapTable(blueprint))}; +} + +QVector MySqlSchemaGrammar::compileDropIndex( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {QStringLiteral("alter table %1 drop index %2") + .arg(wrapTable(blueprint), BaseGrammar::wrap(command.index))}; +} + +QVector MySqlSchemaGrammar::compileDropForeign( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {QStringLiteral("alter table %1 drop foreign key %2") + .arg(wrapTable(blueprint), BaseGrammar::wrap(command.index))}; +} + +QVector MySqlSchemaGrammar::compileRenameIndex( + const Blueprint &blueprint, const ColumnDefinition &command) const +{ + return {QStringLiteral("alter table %1 rename index %2 to %3") + .arg(wrapTable(blueprint), BaseGrammar::wrap(command.from_), + BaseGrammar::wrap(command.to))}; +} + +QVector +MySqlSchemaGrammar::invokeCompileMethod(const ColumnDefinition &command, + const DatabaseConnection &connection, + const Blueprint &blueprint) const +{ + /*! Type for the compileXx() methods. */ + using CompileMemFn = + std::function( + const MySqlSchemaGrammar &, const Blueprint &, + const ColumnDefinition &)>; + + /* Helps to avoid declare all compileXx() methods with a DatabaseConenction & + parameter, only the compileCreate() needs connection argument. */ + const auto bindCreate = [&connection](auto &&compileMethod) + { + return [&connection, + compileMethod = std::forward(compileMethod)] + (const MySqlSchemaGrammar &grammar, const Blueprint &blueprint, + const ColumnDefinition &command) + { + return std::invoke(compileMethod, grammar, blueprint, command, connection); + }; + }; + + // CUR schema, use enum, indexCommand() calls createIndexName() that needs also command.name silverqx + /* Pointers to a command's compile member methods by a command name, yes yes c++ 😂. + I have to map by QString instead of enum struct because a command.name is used + to look up, I could use enum struct but I had to map + QString(command.name) -> enum. */ + T_THREAD_LOCAL + static const std::unordered_map cached { + {Create, bindCreate(&MySqlSchemaGrammar::compileCreate)}, + + {Add, &MySqlSchemaGrammar::compileAdd}, + {Rename, &MySqlSchemaGrammar::compileRename}, + {Drop, &MySqlSchemaGrammar::compileDrop}, + {DropIfExists, &MySqlSchemaGrammar::compileDropIfExists}, + {DropColumn, &MySqlSchemaGrammar::compileDropColumn}, + {RenameColumn, &MySqlSchemaGrammar::compileRenameColumn}, + {Primary, &MySqlSchemaGrammar::compilePrimary}, + {Unique, &MySqlSchemaGrammar::compileUnique}, + {Index, &MySqlSchemaGrammar::compileIndex}, + {Fulltext, &MySqlSchemaGrammar::compileFullText}, + {SpatialIndex, &MySqlSchemaGrammar::compileSpatialIndex}, + {Foreign, &MySqlSchemaGrammar::compileForeign}, + {DropPrimary, &MySqlSchemaGrammar::compileDropPrimary}, + {DropUnique, &MySqlSchemaGrammar::compileDropUnique}, + {DropIndex, &MySqlSchemaGrammar::compileDropIndex}, + {DropFullText, &MySqlSchemaGrammar::compileDropFullText}, + {DropSpatialIndex, &MySqlSchemaGrammar::compileDropSpatialIndex}, + {DropForeign, &MySqlSchemaGrammar::compileDropForeign}, + {RenameIndex, &MySqlSchemaGrammar::compileRenameIndex}, + }; + + Q_ASSERT_X(cached.contains(command.name), + qUtf8Printable(__tiny_func__), + QStringLiteral("Compile methods map doesn't contain the '%1' key.") + .arg(command.name) + .toUtf8().constData()); + + return std::invoke(cached.at(command.name), *this, blueprint, command); +} + +/* protected */ + +QString +MySqlSchemaGrammar::compileCreateTable( + const Blueprint &blueprint, const ColumnDefinition &/*unused*/, + const DatabaseConnection &/*unused*/) const +{ + return QStringLiteral("%1 table %2 (%3)") + .arg(blueprint.isTemporary() ? QStringLiteral("create temporary") + : Create, + wrapTable(blueprint), + columnizeWithoutWrap(getColumns(blueprint))) + .trimmed(); +} + +QString MySqlSchemaGrammar::addModifiers(QString &&sql, + const ColumnDefinition &column) const +{ + // CUR schema, should be T_THREAD_LOCAL? silverqx + constexpr static std::array modifierMethods { + &MySqlSchemaGrammar::modifyUnsigned, &MySqlSchemaGrammar::modifyCharset, + &MySqlSchemaGrammar::modifyCollate, &MySqlSchemaGrammar::modifyVirtualAs, + &MySqlSchemaGrammar::modifyStoredAs, &MySqlSchemaGrammar::modifyNullable, + &MySqlSchemaGrammar::modifyInvisible, &MySqlSchemaGrammar::modifySrid, + &MySqlSchemaGrammar::modifyDefault, &MySqlSchemaGrammar::modifyIncrement, + &MySqlSchemaGrammar::modifyComment, &MySqlSchemaGrammar::modifyAfter, + &MySqlSchemaGrammar::modifyFirst, + }; + + for (const auto method : modifierMethods) + sql.append(std::invoke(method, this, column)); + + return std::move(sql); +} + +void MySqlSchemaGrammar::compileCreateEncoding( + QString &sql, const DatabaseConnection &connection, + const Blueprint &blueprint) const +{ + static const auto charsetTmpl = QStringLiteral(" default character set %1"); + static const auto collateTmpl = QStringLiteral(" collate %1"); + + /* First we will set the character set if one has been set on either the create + blueprint itself or on the root configuration for the connection that the + table is being created on. We will add these to the create table query. */ + if (!blueprint.charset.isEmpty()) + sql.append(charsetTmpl.arg(blueprint.charset)); + + else if (const auto charset = connection.getConfig(charset_).value(); + !charset.isEmpty() + ) + sql.append(charsetTmpl.arg(charset)); + + /* Next we will add the collation to the create table statement if one has been + added to either this create table blueprint or the configuration for this + connection that the query is targeting. We'll add it to this SQL query. */ + if (!blueprint.collation.isEmpty()) + sql.append(collateTmpl.arg(quoteString(blueprint.collation))); + else if (const auto collation = connection.getConfig(collation_).value(); + !collation.isEmpty() + ) + sql.append(collateTmpl.arg(quoteString(collation))); +} + +void MySqlSchemaGrammar::compileCreateEngine( + QString &sql, const DatabaseConnection &connection, + const Blueprint &blueprint) const +{ + static const auto engineTmpl = QStringLiteral(" engine = %1"); + + if (!blueprint.engine.isEmpty()) + sql.append(engineTmpl.arg(blueprint.engine)); + + else if (const auto engine = connection.getConfig(engine_).value(); + !engine.isEmpty() + ) + sql.append(engineTmpl.arg(engine)); +} + +QVector +MySqlSchemaGrammar::compileAutoIncrementStartingValues(const Blueprint &blueprint) const +{ + const auto autoIncrementStartingValues = blueprint.autoIncrementStartingValues(); + + // Nothing to compile + if (autoIncrementStartingValues.isEmpty()) + return {}; + + return autoIncrementStartingValues + | ranges::views::transform([this, &blueprint](const auto &startingValue) + -> QString + { + Q_ASSERT(startingValue.value); + + return QStringLiteral("alter table %1 auto_increment = %2") + .arg(wrapTable(blueprint)) + .arg(*startingValue.value); + }) + | ranges::to>(); +} + +QString MySqlSchemaGrammar::compileKey( + const Blueprint &blueprint, const ColumnDefinition &command, + const QString &type) const +{ + static const auto usingTmpl = QStringLiteral(" using %1"); + + return QStringLiteral("alter table %1 add %2 %3%4(%5)") + .arg(wrapTable(blueprint), type, BaseGrammar::wrap(command.index), + command.algorithm.isEmpty() ? "" + : usingTmpl.arg(command.algorithm), + columnize(command.columns)); +} + +// CUR duplicate in MysqlGrammar silverqx +QString MySqlSchemaGrammar::wrapValue(QString value) const +{ + if (value == ASTERISK_C) + return value; + + return QStringLiteral("`%1`").arg(value.replace(QStringLiteral("`"), + QStringLiteral("``"))); +} + +QString MySqlSchemaGrammar::addSlashes(QString value) const +{ + return value + .replace(QChar(0x001a), "^Z") + .replace(QChar('\\'), "\\\\") + .replace(QChar(QChar::Null), "\\0") + .replace(QChar(QChar::LineFeed), "\\n") + .replace(QChar(QChar::Tabulation), "\\t") + .replace(QChar(0x0008), "\\b") + .replace(QChar(0x000d), "\\r") + .replace(QChar('"'), "\\\"") + .replace(QChar(0x0027), "\\'"); +} + +QString MySqlSchemaGrammar::getType(const ColumnDefinition &column) const +{ + switch (column.type) { + case ColumnType::Char: + return typeChar(column); + + case ColumnType::String: + return typeString(column); + + case ColumnType::Text: + return typeText(column); + + case ColumnType::TinyText: + return typeTinyText(column); + + case ColumnType::MediumText: + return typeMediumText(column); + + case ColumnType::LongText: + return typeLongText(column); + + case ColumnType::Integer: + return typeInteger(column); + + case ColumnType::TinyInteger: + return typeTinyInteger(column); + + case ColumnType::SmallInteger: + return typeSmallInteger(column); + + case ColumnType::MediumInteger: + return typeMediumInteger(column); + + case ColumnType::BigInteger: + return typeBigInteger(column); + + case ColumnType::Float: + return typeFloat(column); + + case ColumnType::Double: + return typeDouble(column); + + case ColumnType::Decimal: + return typeDecimal(column); + + // PostgreSQL only +// case ColumnType::Real: +// return typeReal(column); + + case ColumnType::Boolean: + return typeBoolean(column); + + case ColumnType::Enum: + return typeEnum(column); + + // MySQL only + case ColumnType::Set: + return typeSet(column); + + case ColumnType::Json: + return typeJson(column); + + case ColumnType::Jsonb: + return typeJsonb(column); + + case ColumnType::Date: + return typeDate(column); + + case ColumnType::DateTime: + return typeDateTime(column); + + case ColumnType::DateTimeTz: + return typeDateTimeTz(column); + + case ColumnType::Time: + return typeTime(column); + + case ColumnType::TimeTz: + return typeTimeTz(column); + + case ColumnType::Timestamp: + return typeTimestamp(column); + + case ColumnType::TimestampTz: + return typeTimestampTz(column); + + case ColumnType::Year: + return typeYear(column); + + case ColumnType::Binary: + return typeBinary(column); + + case ColumnType::Uuid: + return typeUuid(column); + + case ColumnType::IpAddress: + return typeIpAddress(column); + + case ColumnType::MacAddress: + return typeMacAddress(column); + + case ColumnType::Geometry: + return typeGeometry(column); + + case ColumnType::Point: + return typePoint(column); + + case ColumnType::LineString: + return typeLineString(column); + + case ColumnType::Polygon: + return typePolygon(column); + + case ColumnType::GeometryCollection: + return typeGeometryCollection(column); + + case ColumnType::MultiPoint: + return typeMultiPoint(column); + + case ColumnType::MultiLineString: + return typeMultiLineString(column); + + case ColumnType::MultiPolygon: + return typeMultiPolygon(column); + + // PostgreSQL only +// case ColumnType::MultiPolygonZ: +// return typeMultiPolygonZ(column); + + default: + break; + } + + throw Exceptions::RuntimeError( + QStringLiteral("Unsupported column type in %1().").arg(__tiny_func__)); +} + +QString MySqlSchemaGrammar::typeChar(const ColumnDefinition &column) const +{ + return QStringLiteral("char(%1)").arg(column.length); +} + +QString MySqlSchemaGrammar::typeString(const ColumnDefinition &column) const +{ + return QStringLiteral("varchar(%1)").arg(column.length); +} + +QString MySqlSchemaGrammar::typeTinyText(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("tinytext"); +} + +QString MySqlSchemaGrammar::typeText(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("text"); +} + +QString MySqlSchemaGrammar::typeMediumText(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("mediumtext"); +} + +QString MySqlSchemaGrammar::typeLongText(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("longtext"); +} + +QString MySqlSchemaGrammar::typeBigInteger(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("bigint"); +} + +QString MySqlSchemaGrammar::typeInteger(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("int"); +} + +QString MySqlSchemaGrammar::typeMediumInteger(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("mediumint"); +} + +QString MySqlSchemaGrammar::typeTinyInteger(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("tinyint"); +} + +QString MySqlSchemaGrammar::typeSmallInteger(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("smallint"); +} + +QString MySqlSchemaGrammar::typeFloat(const ColumnDefinition &column) const +{ + return typeDouble(column); +} + +QString MySqlSchemaGrammar::typeDouble(const ColumnDefinition &column) const +{ + if (column.total && column.places) + return QStringLiteral("double(%1, %2)").arg(*column.total).arg(*column.places); + + return QStringLiteral("double"); +} + +QString MySqlSchemaGrammar::typeDecimal(const ColumnDefinition &column) const +{ + Q_ASSERT(column.total && column.places); + + return QStringLiteral("decimal(%1, %2)").arg(*column.total).arg(*column.places); +} + +QString MySqlSchemaGrammar::typeBoolean(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("tinyint(1)"); +} + +QString MySqlSchemaGrammar::typeEnum(const ColumnDefinition &column) const +{ + return QStringLiteral("enum(%1)").arg(quoteString(column.allowed)); +} + +QString MySqlSchemaGrammar::typeSet(const ColumnDefinition &column) const +{ + return QStringLiteral("set(%1)").arg(quoteString(column.allowed)); +} + +QString MySqlSchemaGrammar::typeJson(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("json"); +} + +QString MySqlSchemaGrammar::typeJsonb(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("json"); +} + +QString MySqlSchemaGrammar::typeDate(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("date"); +} + +QString MySqlSchemaGrammar::typeDateTime(const ColumnDefinition &column) const +{ + auto columnType = column.precision > 0 + ? QStringLiteral("datetime(%1)").arg(column.precision) + : QStringLiteral("datetime"); + + const auto current = column.precision > 0 + ? QStringLiteral("CURRENT_TIMESTAMP(%1)").arg(column.precision) + : QStringLiteral("CURRENT_TIMESTAMP"); + + columnType = column.useCurrent + ? QStringLiteral("%1 default %2").arg(columnType, current) + : columnType; + + return column.useCurrentOnUpdate + ? QStringLiteral("%1 on update %2").arg(columnType, current) + : columnType; +} + +QString MySqlSchemaGrammar::typeDateTimeTz(const ColumnDefinition &column) const +{ + return typeDateTime(column); +} + +QString MySqlSchemaGrammar::typeTime(const ColumnDefinition &column) const +{ + return column.precision > 0 ? QStringLiteral("time(%1)").arg(column.precision) + : QStringLiteral("time"); +} + +QString MySqlSchemaGrammar::typeTimeTz(const ColumnDefinition &column) const +{ + return typeTime(column); +} + +QString MySqlSchemaGrammar::typeTimestamp(const ColumnDefinition &column) const +{ + auto columnType = column.precision > 0 + ? QStringLiteral("timestamp(%1)").arg(column.precision) + : QStringLiteral("timestamp"); + + const auto current = column.precision > 0 + ? QStringLiteral("CURRENT_TIMESTAMP(%1)").arg(column.precision) + : QStringLiteral("CURRENT_TIMESTAMP"); + + columnType = column.useCurrent + ? QStringLiteral("%1 default %2").arg(columnType, current) + : columnType; + + return column.useCurrentOnUpdate + ? QStringLiteral("%1 on update %2").arg(columnType, current) + : columnType; +} + +QString MySqlSchemaGrammar::typeTimestampTz(const ColumnDefinition &column) const +{ + return typeTimestamp(column); +} + +QString MySqlSchemaGrammar::typeYear(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("year"); +} + +QString MySqlSchemaGrammar::typeBinary(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("blob"); +} + +QString MySqlSchemaGrammar::typeUuid(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("char(36)"); +} + +QString MySqlSchemaGrammar::typeIpAddress(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("varchar(45)"); +} + +QString MySqlSchemaGrammar::typeMacAddress(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("varchar(17)"); +} + +QString MySqlSchemaGrammar::typeGeometry(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("geometry"); +} + +QString MySqlSchemaGrammar::typePoint(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("point"); +} + +QString MySqlSchemaGrammar::typeLineString(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("linestring"); +} + +QString MySqlSchemaGrammar::typePolygon(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("polygon"); +} + +QString +MySqlSchemaGrammar::typeGeometryCollection(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("geometrycollection"); +} + +QString MySqlSchemaGrammar::typeMultiPoint(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("multipoint"); +} + +QString MySqlSchemaGrammar::typeMultiLineString(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("multilinestring"); +} + +QString MySqlSchemaGrammar::typeMultiPolygon(const ColumnDefinition &/*unused*/) const +{ + return QStringLiteral("multipolygon"); +} + +QString MySqlSchemaGrammar::typeComputed(const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError( + "This database driver requires a type, see the virtualAs / storedAs " + "modifiers."); +} + +QString MySqlSchemaGrammar::modifyVirtualAs(const ColumnDefinition &column) const +{ + if (column.virtualAs.isEmpty()) + return {}; + + return QStringLiteral(" as (%1)").arg(column.virtualAs); +} + +QString MySqlSchemaGrammar::modifyStoredAs(const ColumnDefinition &column) const +{ + if (column.storedAs.isEmpty()) + return {}; + + return QStringLiteral(" as (%1) stored").arg(column.storedAs); +} + +QString MySqlSchemaGrammar::modifyUnsigned(const ColumnDefinition &column) const +{ + if (!column.isUnsigned) + return {}; + + return QStringLiteral(" unsigned"); +} + +QString MySqlSchemaGrammar::modifyCharset(const ColumnDefinition &column) const +{ + if (column.charset.isEmpty()) + return {}; + + // CUR check quote, origin doesn't have silverqx + return QStringLiteral(" character set %1").arg(quoteString(column.charset)); +} + +QString MySqlSchemaGrammar::modifyCollate(const ColumnDefinition &column) const +{ + if (column.collation.isEmpty()) + return {}; + + return QStringLiteral(" collate %1").arg(quoteString(column.collation)); +} + +QString MySqlSchemaGrammar::modifyNullable(const ColumnDefinition &column) const +{ + static const auto notNull = QStringLiteral(" not null"); + + if (column.virtualAs.isEmpty() && column.storedAs.isEmpty()) + return column.nullable ? QStringLiteral(" null") : notNull; + + // CUR schema, find out why set 'not null' only for virtual/stored silverqx + // Don't set null for virtual/stored columns, set 'not null' only + if (column.nullable) + return {}; + + return notNull; +} + +QString MySqlSchemaGrammar::modifyInvisible(const ColumnDefinition &column) const +{ + if (!column.invisible) + return {}; + + return QStringLiteral(" invisible"); +} + +QString MySqlSchemaGrammar::modifyDefault(const ColumnDefinition &column) const +{ + const auto &defaultValue = column.defaultValue; + + if (!defaultValue.isValid() || defaultValue.isNull()) + return {}; + + // CUR schema, note about security in docs, unprepared and unescaped silverqx + return QStringLiteral(" default %1").arg(getDefaultValue(defaultValue)); +} + +QString MySqlSchemaGrammar::modifyIncrement(const ColumnDefinition &column) const +{ + static const std::unordered_set serials { + ColumnType::BigInteger, ColumnType::Integer, ColumnType::MediumInteger, + ColumnType::SmallInteger, ColumnType::TinyInteger + }; + + // This reverse OR algebra is sick 😮🙃 + if (!column.autoIncrement || !serials.contains(column.type)) + return {}; + + return QStringLiteral(" auto_increment primary key"); +} + +QString MySqlSchemaGrammar::modifyFirst(const ColumnDefinition &column) const +{ + if (!column.first) + return {}; + + return QStringLiteral(" first"); +} + +QString MySqlSchemaGrammar::modifyAfter(const ColumnDefinition &column) const +{ + if (column.after.isEmpty()) + return {}; + + return QStringLiteral(" after %1").arg(BaseGrammar::wrap(column.after)); +} + +QString MySqlSchemaGrammar::modifyComment(const ColumnDefinition &column) const +{ + if (column.comment.isEmpty()) + return {}; + + // CUR schema docs, note about escaping silverqx + // All escaped special characters will be correctly saved in the comment + return QStringLiteral(" comment %1").arg(quoteString(addSlashes(column.comment))); +} + +QString MySqlSchemaGrammar::modifySrid(const ColumnDefinition &column) const +{ + if (const auto &srid = column.srid; + srid && *srid > 0 + ) + return QStringLiteral(" srid %1").arg(*srid); + + return {}; +} + +} // namespace Orm::SchemaNs::Grammars TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/grammars/postgresschemagrammar.cpp b/src/orm/schema/grammars/postgresschemagrammar.cpp index b1426af30..7e6df60e5 100644 --- a/src/orm/schema/grammars/postgresschemagrammar.cpp +++ b/src/orm/schema/grammars/postgresschemagrammar.cpp @@ -1,10 +1,24 @@ #include "orm/schema/grammars/postgresschemagrammar.hpp" +#include "orm/exceptions/runtimeerror.hpp" + TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema::Grammars +namespace Orm::SchemaNs::Grammars { +/* Compile methods for the SchemaBuilder */ + +QString PostgresSchemaGrammar::compileEnableForeignKeyConstraints() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString PostgresSchemaGrammar::compileDisableForeignKeyConstraints() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + QString PostgresSchemaGrammar::compileColumnListing(const QString &/*unused*/) const { return QStringLiteral("select column_name " @@ -12,6 +26,34 @@ QString PostgresSchemaGrammar::compileColumnListing(const QString &/*unused*/) c "where table_schema = ? and table_name = ?"); } -} // namespace Orm::Schema::Grammars +/* Compile methods for commands */ + +QVector +PostgresSchemaGrammar::compileForeign(const Blueprint &/*unused*/, + const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QVector +PostgresSchemaGrammar::invokeCompileMethod(const ColumnDefinition &/*unused*/, + const DatabaseConnection &/*unused*/, + const Blueprint &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString PostgresSchemaGrammar::addModifiers(QString &&/*unused*/, + const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString PostgresSchemaGrammar::getType(const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +} // namespace Orm::SchemaNs::Grammars TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/grammars/schemagrammar.cpp b/src/orm/schema/grammars/schemagrammar.cpp new file mode 100644 index 000000000..66809b710 --- /dev/null +++ b/src/orm/schema/grammars/schemagrammar.cpp @@ -0,0 +1,156 @@ +#include "orm/schema/grammars/schemagrammar.hpp" + +#include "orm/databaseconnection.hpp" +#include "orm/exceptions/invalidargumenterror.hpp" +#include "orm/schema/blueprint.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs::Grammars +{ + +QString SchemaGrammar::compileCreateDatabase(const QString &/*unused*/, + DatabaseConnection &connection) const +{ + throw Exceptions::LogicError( + QStringLiteral("%1 database driver does not support creating databases.") + .arg(connection.driverName())); +} + +QString SchemaGrammar::compileDropDatabaseIfExists(const QString &/*unused*/) const +{ + throw Exceptions::LogicError( + "This database driver does not support dropping databases."); +} + +QString SchemaGrammar::compileDropAllTables(const QVector &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString SchemaGrammar::compileDropAllViews(const QVector &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString SchemaGrammar::compileGetAllTables() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString SchemaGrammar::compileGetAllViews() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString SchemaGrammar::compileTableExists() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QVector +SchemaGrammar::compileFullText(const Blueprint &/*unused*/, + const ColumnDefinition &/*unused*/) const +{ + // CUR schema, check runtime vs logic exception silverqx + // CUR schema, add driver and maybe connection names to this type of exceptions silverqx + // CUR schema, extract this type of exceptions silverqx + throw Exceptions::RuntimeError( + "This database driver does not support dropping databases."); +} + +QVector +SchemaGrammar::compileForeign(const Blueprint &blueprint, + const ColumnDefinition &command) const +{ + /* We need to prepare several of the elements of the foreign key definition + before we can create the SQL, such as wrapping the tables and convert + an array of columns to comma-delimited strings for the SQL queries. */ + auto sql = QStringLiteral("alter table %1 add constraint %2 ") + .arg(wrapTable(blueprint), BaseGrammar::wrap(command.index)); + + /* Once we have the initial portion of the SQL statement we will add on the + key name, table name, and referenced columns. These will complete the + main portion of the SQL statement and this SQL will almost be done. */ + sql += QStringLiteral("foreign key (%1) references %2 (%3)") + .arg(columnize(command.columns), + BaseGrammar::wrapTable(command.on), + columnize(command.references)); + + /* Once we have the basic foreign key creation statement constructed we can + build out the syntax for what should happen on an update or delete of + the affected columns, which will get something like "cascade", etc. */ + if (!command.onDelete.isEmpty()) + sql += QStringLiteral(" on delete %1").arg(command.onDelete); + + if (!command.onUpdate.isEmpty()) + sql += QStringLiteral(" on update %1").arg(command.onUpdate); + + return {sql}; +} + +QVector +SchemaGrammar::compileDropFullText(const Blueprint &/*unused*/, + const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError( + "This database driver does not support dropping databases."); +} + +QString SchemaGrammar::wrap(const ColumnDefinition &column, bool prefixAlias) const +{ + return BaseGrammar::wrap(column.name, prefixAlias); +} + +QString SchemaGrammar::wrapTable(const Blueprint &blueprint) const +{ + return BaseGrammar::wrapTable(blueprint.getTable()); +} + +QStringList SchemaGrammar::getColumns(const Blueprint &blueprint) const +{ + auto addedColumns = blueprint.getAddedColumns(); + + QStringList columns; + columns.reserve(addedColumns.size()); + + for (auto &&column : addedColumns) { + /* Each of the column types have their own compiler functions which are tasked + with turning the column definition into its SQL format for this platform + used by the connection. The column's modifiers are compiled and added. */ + auto sql = SPACE_IN.arg(wrap(column), getType(column)); + + columns << addModifiers(std::move(sql), column); + } + + return columns; +} + +QString SchemaGrammar::getDefaultValue(const QVariant &value) const +{ + if (isExpression(value)) + return getValue(value).value(); + + if (!value.canConvert()) + throw Exceptions::InvalidArgumentError( + "The default value has to be convertible to the QString."); + + // Send boolean values as '0'/'1' +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return value.typeId() == QMetaType::Bool +#else + return value.userType() == QMetaType::Bool +#endif + ? quoteString(QString::number(value.value())) + : quoteString(value.value()); +} + +QString SchemaGrammar::typeComputed(const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError( + "This database driver does not support the computed type."); +} + +} // namespace Orm::SchemaNs::Grammars + +TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/grammars/sqliteschemagrammar.cpp b/src/orm/schema/grammars/sqliteschemagrammar.cpp index de1924beb..8731ab9f5 100644 --- a/src/orm/schema/grammars/sqliteschemagrammar.cpp +++ b/src/orm/schema/grammars/sqliteschemagrammar.cpp @@ -1,10 +1,24 @@ #include "orm/schema/grammars/sqliteschemagrammar.hpp" +#include "orm/exceptions/runtimeerror.hpp" + TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema::Grammars +namespace Orm::SchemaNs::Grammars { +/* Compile methods for the SchemaBuilder */ + +QString SQLiteSchemaGrammar::compileEnableForeignKeyConstraints() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString SQLiteSchemaGrammar::compileDisableForeignKeyConstraints() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + QString SQLiteSchemaGrammar::compileColumnListing(const QString &table) const { auto table_ = table; @@ -12,9 +26,37 @@ QString SQLiteSchemaGrammar::compileColumnListing(const QString &table) const // TODO study, wtf is this 🤔 silverqx table_.replace(DOT, "__"); - return QStringLiteral("pragma table_info(%1)").arg(wrap(table_)); + return QStringLiteral("pragma table_info(%1)").arg(BaseGrammar::wrap(table_)); } -} // namespace Orm::Schema::Grammars +/* Compile methods for commands */ + +QVector +SQLiteSchemaGrammar::compileForeign(const Blueprint &/*unused*/, + const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QVector +SQLiteSchemaGrammar::invokeCompileMethod(const ColumnDefinition &/*unused*/, + const DatabaseConnection &/*unused*/, + const Blueprint &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString SQLiteSchemaGrammar::addModifiers(QString &&/*unused*/, + const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QString SQLiteSchemaGrammar::getType(const ColumnDefinition &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +} // namespace Orm::SchemaNs::Grammars TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/mysqlschemabuilder.cpp b/src/orm/schema/mysqlschemabuilder.cpp index 95ec0adde..1b8b6744b 100644 --- a/src/orm/schema/mysqlschemabuilder.cpp +++ b/src/orm/schema/mysqlschemabuilder.cpp @@ -1,16 +1,89 @@ #include "orm/schema/mysqlschemabuilder.hpp" +#include +#include + #include "orm/databaseconnection.hpp" TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema +namespace Orm::SchemaNs { +QSqlQuery MySqlSchemaBuilder::createDatabase(const QString &name) const +{ + return m_connection.statement( + m_grammar.compileCreateDatabase(name, m_connection)); +} + +QSqlQuery MySqlSchemaBuilder::dropDatabaseIfExists(const QString &name) const +{ + return m_connection.statement( + m_grammar.compileDropDatabaseIfExists(name)); +} + +// CUR schema, test in functional tests silverqx +void MySqlSchemaBuilder::dropAllTables() const +{ + auto query = getAllTables(); + + // No fields in the record + if (query.record().isEmpty()) + return; + + QVector tables; + if (const auto size = query.size(); size > 0) + tables.reserve(size); + + while (query.next()) + tables << query.value(0).value(); + + if (tables.isEmpty()) + return; + + disableForeignKeyConstraints(); + + m_connection.statement(m_grammar.compileDropAllTables(tables)); + + enableForeignKeyConstraints(); +} + +// CUR schema, test in functional tests silverqx +void MySqlSchemaBuilder::dropAllViews() const +{ + auto query = getAllViews(); + + // No fields in the record + if (query.record().isEmpty()) + return; + + QVector views; + if (const auto size = query.size(); size > 0) + views.reserve(size); + + while (query.next()) + views << query.value(0).value(); + + if (views.isEmpty()) + return; + + m_connection.statement(m_grammar.compileDropAllViews(views)); +} + +QSqlQuery MySqlSchemaBuilder::getAllTables() const +{ + // CUR schema, use postproccessor processColumnListing() silverqx + return m_connection.select(m_grammar.compileGetAllTables()); +} + +QSqlQuery MySqlSchemaBuilder::getAllViews() const +{ + return m_connection.select(m_grammar.compileGetAllViews()); +} + QStringList MySqlSchemaBuilder::getColumnListing(const QString &table) const { - const QString table_ = QStringLiteral("%1%2").arg(m_connection.getTablePrefix(), - table); + const auto table_ = NOSPACE.arg(m_connection.getTablePrefix(), table); auto query = m_connection.select(m_grammar.compileColumnListing(), { m_connection.getDatabaseName(), table_ @@ -19,6 +92,17 @@ QStringList MySqlSchemaBuilder::getColumnListing(const QString &table) const return m_connection.getPostProcessor().processColumnListing(query); } -} // namespace Orm::Schema +bool MySqlSchemaBuilder::hasTable(const QString &table) const +{ + const auto table_ = NOSPACE.arg(m_connection.getTablePrefix(), table); + + Q_ASSERT(m_connection.driver()->hasFeature(QSqlDriver::QuerySize)); + + return m_connection.select(m_grammar.compileTableExists(), + {m_connection.getDatabaseName(), table_}) + .size() > 0; +} + +} // namespace Orm::SchemaNs TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/postgresschemabuilder.cpp b/src/orm/schema/postgresschemabuilder.cpp index 0e783462b..21b8da993 100644 --- a/src/orm/schema/postgresschemabuilder.cpp +++ b/src/orm/schema/postgresschemabuilder.cpp @@ -4,14 +4,14 @@ TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema +namespace Orm::SchemaNs { QStringList PostgresSchemaBuilder::getColumnListing(const QString &table) const { auto [schema, table_] = parseSchemaAndTable(table); - table_ = QStringLiteral("%1%2").arg(m_connection.getTablePrefix(), table); + table_ = NOSPACE.arg(m_connection.getTablePrefix(), table); auto query = m_connection.select(m_grammar.compileColumnListing(), { schema, table_ @@ -45,6 +45,6 @@ PostgresSchemaBuilder::parseSchemaAndTable(const QString &table) const return {std::move(schema), table}; } -} // namespace Orm::Schema +} // namespace Orm::SchemaNs TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/schemabuilder.cpp b/src/orm/schema/schemabuilder.cpp index f5a222514..cf6cd9d1a 100644 --- a/src/orm/schema/schemabuilder.cpp +++ b/src/orm/schema/schemabuilder.cpp @@ -1,10 +1,14 @@ #include "orm/schema/schemabuilder.hpp" +#include +#include + #include "orm/databaseconnection.hpp" +#include "orm/exceptions/logicerror.hpp" TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Schema +namespace Orm::SchemaNs { SchemaBuilder::SchemaBuilder(DatabaseConnection &connection) @@ -12,6 +16,138 @@ SchemaBuilder::SchemaBuilder(DatabaseConnection &connection) , m_grammar(connection.getSchemaGrammar()) {} +QSqlQuery SchemaBuilder::createDatabase(const QString &/*unused*/) const +{ + throw Exceptions::LogicError( + QStringLiteral("%1 database driver does not support creating databases.") + .arg(m_connection.driverName())); +} + +QSqlQuery SchemaBuilder::dropDatabaseIfExists(const QString &/*unused*/) const +{ + throw Exceptions::LogicError( + "This database driver does not support dropping databases."); +} + +void SchemaBuilder::create(const QString &table, + const std::function &callback) const +{ + auto blueprint = createBlueprint(table); + + blueprint.create(); + + std::invoke(callback, blueprint); + + build(std::move(blueprint)); +} + +void SchemaBuilder::table(const QString &table, + const std::function &callback) const +{ + build(createBlueprint(table, callback)); +} + +void SchemaBuilder::drop(const QString &table) const +{ + // CUR schema, create tap helper silverqx + auto blueprint = createBlueprint(table); + + blueprint.drop(); + + build(std::move(blueprint)); +} + +void SchemaBuilder::dropIfExists(const QString &table) const +{ + auto blueprint = createBlueprint(table); + + blueprint.dropIfExists(); + + build(std::move(blueprint)); +} + +void SchemaBuilder::rename(const QString &from, const QString &to) const +{ + auto blueprint = createBlueprint(from); + + blueprint.rename(to); + + build(std::move(blueprint)); +} + +void SchemaBuilder::dropColumns(const QString &table, + const QVector &columns) const +{ + auto blueprint = createBlueprint(table); + + blueprint.dropColumns(columns); + + build(std::move(blueprint)); +} + +void SchemaBuilder::dropColumn(const QString &table, const QString &column) const +{ + auto blueprint = createBlueprint(table); + + blueprint.dropColumns({column}); + + build(std::move(blueprint)); +} + +void SchemaBuilder::renameColumn(const QString &table, const QString &from, + const QString &to) +{ + auto blueprint = createBlueprint(table); + + blueprint.renameColumn(from, to); + + build(std::move(blueprint)); +} + +void SchemaBuilder::dropAllTables() const +{ + // CUR schema, solve this logic vs runtime exception silverqx + throw Exceptions::LogicError( + QStringLiteral("%1 database driver does not support dropping all tables.") + .arg(m_connection.driverName())); +} + +void SchemaBuilder::dropAllViews() const +{ + throw Exceptions::LogicError( + QStringLiteral("%1 database driver does not support dropping all views.") + .arg(m_connection.driverName())); +} + +void SchemaBuilder::dropAllTypes() const +{ + throw Exceptions::LogicError( + QStringLiteral("%1 database driver does not support dropping all types.") + .arg(m_connection.driverName())); +} + +QSqlQuery SchemaBuilder::getAllTables() const +{ + throw Exceptions::LogicError( + QStringLiteral("%1 database driver does not support getting all tables.") + .arg(m_connection.driverName())); +} + +QSqlQuery SchemaBuilder::getAllViews() const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +QSqlQuery SchemaBuilder::enableForeignKeyConstraints() const +{ + return m_connection.statement(m_grammar.compileEnableForeignKeyConstraints()); +} + +QSqlQuery SchemaBuilder::disableForeignKeyConstraints() const +{ + return m_connection.statement(m_grammar.compileDisableForeignKeyConstraints()); +} + QStringList SchemaBuilder::getColumnListing(const QString &table) const { auto query = m_connection.selectFromWriteConnection( @@ -21,6 +157,52 @@ QStringList SchemaBuilder::getColumnListing(const QString &table) const return m_connection.getPostProcessor().processColumnListing(query); } -} // namespace Orm::Schema +bool SchemaBuilder::hasTable(const QString &/*unused*/) const +{ + throw Exceptions::RuntimeError(NotImplemented); +} + +// CUR schema, test in functional tests silverqx +bool SchemaBuilder::hasColumn(const QString &table, const QString &column) const +{ + return ranges::contains(getColumnListing(table), column.toLower(), + [](auto &&columnFromListing) + { + return columnFromListing.toLower(); + }); +} + +bool SchemaBuilder::hasColumns(const QString &table, + const QVector &columns) const +{ + auto columnsFromListing = getColumnListing(table); + + columnsFromListing |= ranges::actions::transform([](const auto &column) + { + return column.toLower(); + }); + + return std::ranges::all_of(columns, [&columnsFromListing](const auto &column) + { + return columnsFromListing.contains(column.toLower()); + }); +} + +Blueprint SchemaBuilder::createBlueprint( + const QString &table, const std::function &callback) const +{ + auto prefix = m_connection.getConfig(prefix_indexes).value() + ? m_connection.getConfig(prefix_).value() + : ""; + + return Blueprint(table, callback, std::move(prefix)); +} + +void SchemaBuilder::build(Blueprint &&blueprint) const +{ + blueprint.build(m_connection, m_grammar); +} + +} // namespace Orm::SchemaNs TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/schema/schemaconstants_extern.cpp b/src/orm/schema/schemaconstants_extern.cpp new file mode 100644 index 000000000..9165890e5 --- /dev/null +++ b/src/orm/schema/schemaconstants_extern.cpp @@ -0,0 +1,40 @@ +#include "orm/schema/schemaconstants_extern.hpp" + +TINYORM_BEGIN_COMMON_NAMESPACE + +namespace Orm::SchemaNs::Constants +{ + + // Command names + const QString Create = QStringLiteral("create"); + const QString Add = QStringLiteral("add"); + const QString Change = QStringLiteral("change"); + const QString Drop = QStringLiteral("drop"); + const QString DropIfExists = QStringLiteral("dropIfExists"); + const QString Rename = QStringLiteral("rename"); + const QString DropColumn = QStringLiteral("dropColumn"); + const QString RenameColumn = QStringLiteral("renameColumn"); + const QString DropPrimary = QStringLiteral("dropPrimary"); + const QString DropUnique = QStringLiteral("dropUnique"); + const QString DropIndex = QStringLiteral("dropIndex"); + const QString DropFullText = QStringLiteral("dropFullText"); + const QString DropSpatialIndex = QStringLiteral("dropSpatialIndex"); + const QString DropForeign = QStringLiteral("dropForeign"); + const QString RenameIndex = QStringLiteral("renameIndex"); + + // Indexes + const QString Primary = QStringLiteral("primary"); + const QString Unique = QStringLiteral("unique"); + const QString Index = QStringLiteral("index"); + const QString Fulltext = QStringLiteral("fulltext"); + const QString SpatialIndex = QStringLiteral("spatialIndex"); + const QString Foreign = QStringLiteral("foreign"); + + // Foreign constrains + const QString Cascade = QStringLiteral("cascade"); + const QString Restrict = QStringLiteral("restrict"); + const QString SetNull = QStringLiteral("set null"); + +} // namespace Orm::SchemaNs::Constants + +TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/sqliteconnection.cpp b/src/orm/sqliteconnection.cpp index f7b82ac38..18fa7f260 100644 --- a/src/orm/sqliteconnection.cpp +++ b/src/orm/sqliteconnection.cpp @@ -30,7 +30,7 @@ std::unique_ptr SQLiteConnection::getSchemaBuilder() if (!m_schemaGrammar) useDefaultSchemaGrammar(); - return std::make_unique(*this); + return std::make_unique(*this); } std::unique_ptr SQLiteConnection::getDefaultQueryGrammar() const @@ -46,7 +46,7 @@ std::unique_ptr SQLiteConnection::getDefaultQueryGrammar() const std::unique_ptr SQLiteConnection::getDefaultSchemaGrammar() const { // Ownership of a unique_ptr() - auto grammar = std::make_unique(); + auto grammar = std::make_unique(); withTablePrefix(*grammar); diff --git a/src/src.pri b/src/src.pri index d80ab1bb7..d67abe304 100644 --- a/src/src.pri +++ b/src/src.pri @@ -1,5 +1,7 @@ extern_constants: \ - sourcesList += $$PWD/orm/constants_extern.cpp + sourcesList += \ + $$PWD/orm/constants_extern.cpp \ + $$PWD/orm/schema/schemaconstants_extern.cpp sourcesList += \ $$PWD/orm/basegrammar.cpp \ @@ -33,8 +35,13 @@ sourcesList += \ $$PWD/orm/query/processors/processor.cpp \ $$PWD/orm/query/processors/sqliteprocessor.cpp \ $$PWD/orm/query/querybuilder.cpp \ + $$PWD/orm/schema.cpp \ + $$PWD/orm/schema/blueprint.cpp \ + $$PWD/orm/schema/foreignidcolumndefinitionreference.cpp \ + $$PWD/orm/schema/foreignkeydefinitionreference.cpp \ $$PWD/orm/schema/grammars/mysqlschemagrammar.cpp \ $$PWD/orm/schema/grammars/postgresschemagrammar.cpp \ + $$PWD/orm/schema/grammars/schemagrammar.cpp \ $$PWD/orm/schema/grammars/sqliteschemagrammar.cpp \ $$PWD/orm/schema/mysqlschemabuilder.cpp \ $$PWD/orm/schema/postgresschemabuilder.cpp \ diff --git a/tests/TinyUtils/src/databases.cpp b/tests/TinyUtils/src/databases.cpp index 34cc8a43e..f0aad9c40 100644 --- a/tests/TinyUtils/src/databases.cpp +++ b/tests/TinyUtils/src/databases.cpp @@ -9,9 +9,11 @@ using Orm::Constants::driver_; using Orm::Constants::charset_; using Orm::Constants::check_database_exists; using Orm::Constants::collation_; +using Orm::Constants::engine_; using Orm::Constants::foreign_key_constraints; using Orm::Constants::H127001; using Orm::Constants::host_; +using Orm::Constants::InnoDB; using Orm::Constants::isolation_level; using Orm::Constants::options_; using Orm::Constants::P3306; @@ -19,6 +21,7 @@ using Orm::Constants::P5432; using Orm::Constants::password_; using Orm::Constants::port_; using Orm::Constants::prefix_; +using Orm::Constants::prefix_indexes; using Orm::Constants::PUBLIC; using Orm::Constants::QMYSQL; using Orm::Constants::QPSQL; @@ -138,8 +141,10 @@ Databases::mysqlConfiguration() // Very important for tests {timezone_, "+00:00"}, {prefix_, ""}, + {prefix_indexes, true}, {strict_, true}, {isolation_level, "REPEATABLE READ"}, + {engine_, InnoDB}, {options_, QVariantHash()}, // FUTURE remove, when unit tested silverqx // Example @@ -171,6 +176,8 @@ Databases::sqliteConfiguration() {foreign_key_constraints, qEnvironmentVariable("DB_SQLITE_FOREIGN_KEYS", QStringLiteral("true"))}, {check_database_exists, true}, + {prefix_, ""}, + // FUTURE schema sqlite, prefix_indexes and sqlite, works it? test silverqx }; // Environment variables were undefined @@ -199,9 +206,10 @@ Databases::postgresConfiguration() {password_, qEnvironmentVariable("DB_PGSQL_PASSWORD", "")}, {charset_, qEnvironmentVariable("DB_PGSQL_CHARSET", UTF8)}, // I don't use timezone types in postgres anyway - {timezone_, UTC}, - {prefix_, ""}, - {options_, QVariantHash()}, + {timezone_, UTC}, + {prefix_, ""}, + {prefix_indexes, true}, + {options_, QVariantHash()}, }; // Environment variables were undefined diff --git a/tests/auto/unit/orm/CMakeLists.txt b/tests/auto/unit/orm/CMakeLists.txt index c22bc5bef..9583067a0 100644 --- a/tests/auto/unit/orm/CMakeLists.txt +++ b/tests/auto/unit/orm/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(databaseconnection) add_subdirectory(query) +add_subdirectory(schema) if(ORM) add_subdirectory(tiny) diff --git a/tests/auto/unit/orm/orm.pro b/tests/auto/unit/orm/orm.pro index df9dabe17..c2bc72d5b 100644 --- a/tests/auto/unit/orm/orm.pro +++ b/tests/auto/unit/orm/orm.pro @@ -3,6 +3,7 @@ TEMPLATE = subdirs subdirsList = \ databaseconnection \ query \ + schema \ version \ !disable_orm: \ diff --git a/tests/auto/unit/orm/query/mysql_querybuilder/tst_mysql_querybuilder.cpp b/tests/auto/unit/orm/query/mysql_querybuilder/tst_mysql_querybuilder.cpp index 4846dfa84..1b0b78e4f 100644 --- a/tests/auto/unit/orm/query/mysql_querybuilder/tst_mysql_querybuilder.cpp +++ b/tests/auto/unit/orm/query/mysql_querybuilder/tst_mysql_querybuilder.cpp @@ -177,6 +177,7 @@ void tst_MySql_QueryBuilder::get() const connection.query()->from("torrents").get({ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -191,6 +192,7 @@ void tst_MySql_QueryBuilder::get() const connection.query()->from("torrents").get(); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -207,6 +209,7 @@ void tst_MySql_QueryBuilder::get_ColumnExpression() const connection.query()->from("torrents").get({Raw(ID), NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -222,6 +225,7 @@ void tst_MySql_QueryBuilder::find() const connection.query()->from("torrents").find(3, {ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -239,6 +243,7 @@ void tst_MySql_QueryBuilder::find_ColumnAndValueExpression() const connection.query()->from("torrents").find(3, {ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -254,6 +259,7 @@ void tst_MySql_QueryBuilder::find_ColumnAndValueExpression() const connection.query()->from("torrents").find(Raw("1 + 3"), {ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -270,6 +276,7 @@ void tst_MySql_QueryBuilder::first() const connection.query()->from("torrents").first({ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -285,6 +292,7 @@ void tst_MySql_QueryBuilder::first_ColumnExpression() const connection.query()->from("torrents").first({ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -300,6 +308,7 @@ void tst_MySql_QueryBuilder::value() const connection.query()->from("torrents").value(NAME); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -315,6 +324,7 @@ void tst_MySql_QueryBuilder::value_ColumnExpression() const connection.query()->from("torrents").value(Raw(NAME)); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -330,6 +340,7 @@ void tst_MySql_QueryBuilder::count() const connection.query()->from("torrents").count(); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -346,6 +357,7 @@ void tst_MySql_QueryBuilder::count_Distinct() const connection.query()->from("torrents").distinct().count(SIZE); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -361,6 +373,7 @@ void tst_MySql_QueryBuilder::count_Distinct() const connection.query()->from("torrents").distinct().count({SIZE, "note"}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -376,6 +389,7 @@ void tst_MySql_QueryBuilder::count_Distinct() const connection.query()->from("torrents").distinct({SIZE, "note"}).count(); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -392,6 +406,7 @@ void tst_MySql_QueryBuilder::min_Aggregate() const connection.query()->from("torrents").min(SIZE); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -407,6 +422,7 @@ void tst_MySql_QueryBuilder::min_Aggregate_ColumnExpression() const connection.query()->from("torrents").min(Raw(SIZE)); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -422,6 +438,7 @@ void tst_MySql_QueryBuilder::max_Aggregate() const connection.query()->from("torrents").max(SIZE); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -437,6 +454,7 @@ void tst_MySql_QueryBuilder::sum_Aggregate() const connection.query()->from("torrents").sum(SIZE); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -452,6 +470,7 @@ void tst_MySql_QueryBuilder::average_Aggregate() const connection.query()->from("torrents").avg(SIZE); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1937,6 +1956,7 @@ void tst_MySql_QueryBuilder::insert() const connection.query()->from("torrents").insert({{NAME, "xyz"}, {SIZE, 6}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1955,6 +1975,7 @@ void tst_MySql_QueryBuilder::insert_WithExpression() const {"progress", DB::raw(2)}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1973,6 +1994,7 @@ void tst_MySql_QueryBuilder::update() const .update({{NAME, "xyz"}, {SIZE, 6}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1992,6 +2014,7 @@ void tst_MySql_QueryBuilder::update_WithExpression() const {"progress", DB::raw(2)}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -2009,6 +2032,7 @@ void tst_MySql_QueryBuilder::remove() const connection.query()->from("torrents").remove(2222); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -2025,6 +2049,7 @@ void tst_MySql_QueryBuilder::remove_WithExpression() const connection.query()->from("torrents").remove(DB::raw(2223)); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); diff --git a/tests/auto/unit/orm/query/postgresql_querybuilder/tst_postgresql_querybuilder.cpp b/tests/auto/unit/orm/query/postgresql_querybuilder/tst_postgresql_querybuilder.cpp index 53bcbcb81..daeb6c9bf 100644 --- a/tests/auto/unit/orm/query/postgresql_querybuilder/tst_postgresql_querybuilder.cpp +++ b/tests/auto/unit/orm/query/postgresql_querybuilder/tst_postgresql_querybuilder.cpp @@ -140,6 +140,7 @@ void tst_PostgreSQL_QueryBuilder::get() const connection.query()->from("torrents").get({ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -154,6 +155,7 @@ void tst_PostgreSQL_QueryBuilder::get() const connection.query()->from("torrents").get(); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -170,6 +172,7 @@ void tst_PostgreSQL_QueryBuilder::get_ColumnExpression() const connection.query()->from("torrents").get({Raw(ID), NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -185,6 +188,7 @@ void tst_PostgreSQL_QueryBuilder::find() const connection.query()->from("torrents").find(3, {ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -202,6 +206,7 @@ void tst_PostgreSQL_QueryBuilder::find_ColumnAndValueExpression() const connection.query()->from("torrents").find(3, {ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -217,6 +222,7 @@ void tst_PostgreSQL_QueryBuilder::find_ColumnAndValueExpression() const connection.query()->from("torrents").find(Raw("1 + 3"), {ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -233,6 +239,7 @@ void tst_PostgreSQL_QueryBuilder::first() const connection.query()->from("torrents").first({ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -248,6 +255,7 @@ void tst_PostgreSQL_QueryBuilder::first_ColumnExpression() const connection.query()->from("torrents").first({ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -263,6 +271,7 @@ void tst_PostgreSQL_QueryBuilder::value() const connection.query()->from("torrents").value(NAME); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -278,6 +287,7 @@ void tst_PostgreSQL_QueryBuilder::value_ColumnExpression() const connection.query()->from("torrents").value(Raw(NAME)); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1535,6 +1545,7 @@ void tst_PostgreSQL_QueryBuilder::insert() const connection.query()->from("torrents").insert({{NAME, "xyz"}, {SIZE, 6}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1553,6 +1564,7 @@ void tst_PostgreSQL_QueryBuilder::insert_WithExpression() const {"progress", DB::raw(2)}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1572,6 +1584,7 @@ void tst_PostgreSQL_QueryBuilder::update() const .update({{NAME, "xyz"}, {SIZE, 6}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1591,6 +1604,7 @@ void tst_PostgreSQL_QueryBuilder::update_WithExpression() const {"progress", DB::raw(2)}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1608,6 +1622,7 @@ void tst_PostgreSQL_QueryBuilder::remove() const connection.query()->from("torrents").remove(2222); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1624,6 +1639,7 @@ void tst_PostgreSQL_QueryBuilder::remove_WithExpression() const connection.query()->from("torrents").remove(DB::raw(2223)); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); diff --git a/tests/auto/unit/orm/query/sqlite_querybuilder/tst_sqlite_querybuilder.cpp b/tests/auto/unit/orm/query/sqlite_querybuilder/tst_sqlite_querybuilder.cpp index 8b14c3c78..22ccdffa3 100644 --- a/tests/auto/unit/orm/query/sqlite_querybuilder/tst_sqlite_querybuilder.cpp +++ b/tests/auto/unit/orm/query/sqlite_querybuilder/tst_sqlite_querybuilder.cpp @@ -138,6 +138,7 @@ void tst_SQLite_QueryBuilder::get() const connection.query()->from("torrents").get({ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -152,6 +153,7 @@ void tst_SQLite_QueryBuilder::get() const connection.query()->from("torrents").get(); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -168,6 +170,7 @@ void tst_SQLite_QueryBuilder::get_ColumnExpression() const connection.query()->from("torrents").get({Raw(ID), NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -183,6 +186,7 @@ void tst_SQLite_QueryBuilder::find() const connection.query()->from("torrents").find(3, {ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -200,6 +204,7 @@ void tst_SQLite_QueryBuilder::find_ColumnAndValueExpression() const connection.query()->from("torrents").find(3, {ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -215,6 +220,7 @@ void tst_SQLite_QueryBuilder::find_ColumnAndValueExpression() const connection.query()->from("torrents").find(Raw("1 + 3"), {ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -231,6 +237,7 @@ void tst_SQLite_QueryBuilder::first() const connection.query()->from("torrents").first({ID, NAME}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -246,6 +253,7 @@ void tst_SQLite_QueryBuilder::first_ColumnExpression() const connection.query()->from("torrents").first({ID, Raw(NAME)}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -261,6 +269,7 @@ void tst_SQLite_QueryBuilder::value() const connection.query()->from("torrents").value(NAME); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -276,6 +285,7 @@ void tst_SQLite_QueryBuilder::value_ColumnExpression() const connection.query()->from("torrents").value(Raw(NAME)); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1488,6 +1498,7 @@ void tst_SQLite_QueryBuilder::insert() const connection.query()->from("torrents").insert({{NAME, "xyz"}, {SIZE, 6}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1506,6 +1517,7 @@ void tst_SQLite_QueryBuilder::insert_WithExpression() const {"progress", DB::raw(2)}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1525,6 +1537,7 @@ void tst_SQLite_QueryBuilder::update() const .update({{NAME, "xyz"}, {SIZE, 6}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1544,6 +1557,7 @@ void tst_SQLite_QueryBuilder::update_WithExpression() const {"progress", DB::raw(2)}}); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1561,6 +1575,7 @@ void tst_SQLite_QueryBuilder::remove() const connection.query()->from("torrents").remove(2222); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); @@ -1577,6 +1592,7 @@ void tst_SQLite_QueryBuilder::remove_WithExpression() const connection.query()->from("torrents").remove(DB::raw(2223)); }); + QVERIFY(!log.isEmpty()); const auto &firstLog = log.first(); QCOMPARE(log.size(), 1); diff --git a/tests/auto/unit/orm/schema/CMakeLists.txt b/tests/auto/unit/orm/schema/CMakeLists.txt new file mode 100644 index 000000000..250964c9b --- /dev/null +++ b/tests/auto/unit/orm/schema/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(blueprint) +add_subdirectory(mysql_schemabuilder) diff --git a/tests/auto/unit/orm/schema/blueprint/CMakeLists.txt b/tests/auto/unit/orm/schema/blueprint/CMakeLists.txt new file mode 100644 index 000000000..983ddc0ac --- /dev/null +++ b/tests/auto/unit/orm/schema/blueprint/CMakeLists.txt @@ -0,0 +1,12 @@ +project(blueprint + LANGUAGES CXX +) + +add_executable(blueprint + tst_blueprint.cpp +) + +add_test(NAME blueprint COMMAND blueprint) + +include(TinyTestCommon) +tiny_configure_test(blueprint) diff --git a/tests/auto/unit/orm/schema/blueprint/blueprint.pro b/tests/auto/unit/orm/schema/blueprint/blueprint.pro new file mode 100644 index 000000000..b5b2199de --- /dev/null +++ b/tests/auto/unit/orm/schema/blueprint/blueprint.pro @@ -0,0 +1,4 @@ +include($$TINYORM_SOURCE_TREE/tests/qmake/common.pri) +include($$TINYORM_SOURCE_TREE/tests/qmake/TinyUtils.pri) + +SOURCES = tst_blueprint.cpp diff --git a/tests/auto/unit/orm/schema/blueprint/tst_blueprint.cpp b/tests/auto/unit/orm/schema/blueprint/tst_blueprint.cpp new file mode 100644 index 000000000..0361f9962 --- /dev/null +++ b/tests/auto/unit/orm/schema/blueprint/tst_blueprint.cpp @@ -0,0 +1,259 @@ +#include +#include + +#include "orm/db.hpp" +#include "orm/schema/grammars/mysqlschemagrammar.hpp" + +#include "databases.hpp" + +using Orm::Constants::NAME; +using Orm::Constants::SIZE; + +using Orm::DB; + +using Orm::SchemaNs::Blueprint; +using Orm::SchemaNs::Grammars::MySqlSchemaGrammar; + +using TestUtils::Databases; + +class tst_Blueprint : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void index_DefaultNames() const; + void index_DefaultNames_WithPrefix() const; + + void dropIndex_ByColumns_DefaultNames() const; + void dropIndex_ByColumns_DefaultNames_WithPrefix() const; + + void dropIndex_ByIndexName() const; + + void unsignedDecimal_WithTotalAndPlaces() const; + + void removeColumn() const; + +// NOLINTNEXTLINE(readability-redundant-access-specifiers) +private: + /*! Connection name used in this test case. */ + QString m_connection {}; +}; + +void tst_Blueprint::initTestCase() +{ + m_connection = Databases::createConnection(Databases::MYSQL); + + if (m_connection.isEmpty()) + QSKIP(QStringLiteral("%1 autotest skipped, environment variables " + "for '%2' connection have not been defined.") + .arg("tst_Blueprint", Databases::MYSQL).toUtf8().constData(), ); +} + +void tst_Blueprint::index_DefaultNames() const +{ + // Unique + { + Blueprint blueprint("torrents"); + blueprint.unique({NAME, "foo"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_name_foo_unique"); + } + // Index + { + Blueprint blueprint("torrents"); + blueprint.index(SIZE); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_size_index"); + } + // SpatialIndex + { + Blueprint blueprint("torrents"); + blueprint.spatialIndex({"coordinates"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_coordinates_spatialindex"); + } + // FullText + { + Blueprint blueprint("torrents"); + blueprint.fullText("note"); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_note_fulltext"); + } +} + +void tst_Blueprint::index_DefaultNames_WithPrefix() const +{ + // Unique + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.unique({NAME, "foo"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_name_foo_unique"); + } + // Index + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.index(SIZE); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_size_index"); + } + // SpatialIndex + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.spatialIndex({"coordinates"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_coordinates_spatialindex"); + } + // FullText + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.fullText("note"); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_note_fulltext"); + } +} + +void tst_Blueprint::dropIndex_ByColumns_DefaultNames() const +{ + { + Blueprint blueprint("torrents"); + blueprint.dropUnique({NAME, "foo"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_name_foo_unique"); + } + // Index + { + Blueprint blueprint("torrents"); + blueprint.dropIndex(QVector {SIZE}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_size_index"); + } + // SpatialIndex + { + Blueprint blueprint("torrents"); + blueprint.dropSpatialIndex({"coordinates"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_coordinates_spatialindex"); + } + // FullText + { + Blueprint blueprint("torrents"); + blueprint.dropFullText({"note"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "torrents_note_fulltext"); + } +} + +void tst_Blueprint::dropIndex_ByColumns_DefaultNames_WithPrefix() const +{ + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.dropUnique({NAME, "foo"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_name_foo_unique"); + } + // Index + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.dropIndex(QVector {SIZE}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_size_index"); + } + // SpatialIndex + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.dropSpatialIndex({"coordinates"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_coordinates_spatialindex"); + } + // FullText + { + Blueprint blueprint("torrents", nullptr, "prefix_"); + blueprint.dropFullText({"note"}); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "prefix_torrents_note_fulltext"); + } +} + +void tst_Blueprint::dropIndex_ByIndexName() const +{ + { + Blueprint blueprint("torrents"); + blueprint.dropUnique(NAME); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, NAME); + } + // Index + { + Blueprint blueprint("torrents"); + blueprint.dropIndex(SIZE); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, SIZE); + } + // SpatialIndex + { + Blueprint blueprint("torrents"); + blueprint.dropSpatialIndex("coordinates"); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "coordinates"); + } + // FullText + { + Blueprint blueprint("torrents"); + blueprint.dropFullText("note"); + const auto &commands = blueprint.getCommands(); + + QCOMPARE(commands.first().index, "note"); + } +} + +void tst_Blueprint::unsignedDecimal_WithTotalAndPlaces() const +{ + Blueprint blueprint("cars", [](Blueprint &table) + { + table.unsignedDecimal("money", 10, 2); + }); + + QCOMPARE(blueprint.toSql(DB::connection(m_connection), MySqlSchemaGrammar()), + QVector {"alter table `cars` " + "add `money` decimal(10, 2) unsigned not null"}); +} + +void tst_Blueprint::removeColumn() const +{ + Blueprint blueprint("torrents", [](Blueprint &table) + { + table.string(NAME); + table.integer(SIZE); + table.removeColumn(SIZE); + }); + + QCOMPARE(blueprint.toSql(DB::connection(m_connection), MySqlSchemaGrammar()), + QVector {"alter table `torrents` " + "add `name` varchar(255) not null"}); +} + +QTEST_MAIN(tst_Blueprint) + +#include "tst_blueprint.moc" diff --git a/tests/auto/unit/orm/schema/mysql_schemabuilder/CMakeLists.txt b/tests/auto/unit/orm/schema/mysql_schemabuilder/CMakeLists.txt new file mode 100644 index 000000000..53a332340 --- /dev/null +++ b/tests/auto/unit/orm/schema/mysql_schemabuilder/CMakeLists.txt @@ -0,0 +1,12 @@ +project(mysql_schemabuilder + LANGUAGES CXX +) + +add_executable(mysql_schemabuilder + tst_mysql_schemabuilder.cpp +) + +add_test(NAME mysql_schemabuilder COMMAND mysql_schemabuilder) + +include(TinyTestCommon) +tiny_configure_test(mysql_schemabuilder INCLUDE_MODELS) diff --git a/tests/auto/unit/orm/schema/mysql_schemabuilder/mysql_schemabuilder.pro b/tests/auto/unit/orm/schema/mysql_schemabuilder/mysql_schemabuilder.pro new file mode 100644 index 000000000..a3fcf7f2c --- /dev/null +++ b/tests/auto/unit/orm/schema/mysql_schemabuilder/mysql_schemabuilder.pro @@ -0,0 +1,5 @@ +include($$TINYORM_SOURCE_TREE/tests/qmake/common.pri) +include($$TINYORM_SOURCE_TREE/tests/qmake/TinyUtils.pri) +include($$TINYORM_SOURCE_TREE/tests/models/models.pri) + +SOURCES = tst_mysql_schemabuilder.cpp diff --git a/tests/auto/unit/orm/schema/mysql_schemabuilder/tst_mysql_schemabuilder.cpp b/tests/auto/unit/orm/schema/mysql_schemabuilder/tst_mysql_schemabuilder.cpp new file mode 100644 index 000000000..056db9278 --- /dev/null +++ b/tests/auto/unit/orm/schema/mysql_schemabuilder/tst_mysql_schemabuilder.cpp @@ -0,0 +1,1622 @@ +#include +#include + +#include "orm/db.hpp" +#include "orm/exceptions/logicerror.hpp" +#include "orm/schema.hpp" + +#ifndef TINYORM_DISABLE_ORM +# include "models/user.hpp" +#endif + +#include "databases.hpp" + +using Orm::Constants::charset_; +using Orm::Constants::driver_; +using Orm::Constants::collation_; +using Orm::Constants::ID; +using Orm::Constants::MyISAM; +using Orm::Constants::NAME; +using Orm::Constants::QMYSQL; +using Orm::Constants::SIZE; +using Orm::Constants::UTF8; + +using Orm::DB; +using Orm::Exceptions::LogicError; +using Orm::Schema; +using Orm::SchemaNs::Blueprint; +using Orm::SchemaNs::Constants::Cascade; +using Orm::SchemaNs::Constants::Restrict; + +using TestUtils::Databases; + +class tst_Mysql_SchemaBuilder : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + + void createDatabase() const; + void createDatabase_Charset_Collation() const; + void dropDatabaseIfExists() const; + + void createTable() const; + void createTable_Temporary() const; + void createTable_Charset_Collation_Engine() const; + + void timestamps_rememberToken_CreateAndDrop() const; + + void modifyTable() const; + + void dropTable() const; + void dropTableIfExists() const; + + void rename() const; + + void dropColumns() const; + void renameColumn() const; + + void dropAllTypes() const; + + void getAllTables() const; + void getAllViews() const; + + void enableForeignKeyConstraints() const; + void disableForeignKeyConstraints() const; + + void getColumnListing() const; + + void hasTable() const; + + void defaultStringLength_Set() const; + + void modifiers() const; + void modifier_defaultValue_WithExpression() const; + void modifier_defaultValue_WithBoolean() const; + + void useCurrent() const; + void useCurrentOnUpdate() const; + + void indexes_Fluent() const; + void indexes_Blueprint() const; + + void renameIndex() const; + + void dropIndex_ByIndexName() const; + void dropIndex_ByColumn() const; + void dropIndex_ByMultipleColumns() const; + + void foreignKey() const; + void foreignKey_TerserSyntax() const; +#ifndef TINYORM_DISABLE_ORM + void foreignKey_WithModel() const; +#endif + + void dropForeign() const; + +// NOLINTNEXTLINE(readability-redundant-access-specifiers) +private: + /*! Table or database name used in tests. */ + inline static const auto Firewalls = QStringLiteral("firewalls"); + + /*! Connection name used in this test case. */ + QString m_connection {}; +}; + +void tst_Mysql_SchemaBuilder::initTestCase() +{ + m_connection = Databases::createConnection(Databases::MYSQL); + + if (m_connection.isEmpty()) + QSKIP(QStringLiteral("%1 autotest skipped, environment variables " + "for '%2' connection have not been defined.") + .arg("tst_Mysql_SchemaBuilder", Databases::MYSQL).toUtf8().constData(), ); +} + +void tst_Mysql_SchemaBuilder::createDatabase() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).createDatabase(Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create database `firewalls` " + "default character set `utf8mb4` default collate `utf8mb4_0900_ai_ci`"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::createDatabase_Charset_Collation() const +{ + static const auto mysqlCreateDb = QStringLiteral("tinyorm_mysql_tests_create_db"); + + // Create a new connection with different charset and collation + DB::addConnection({ + {driver_, QMYSQL}, + {charset_, UTF8}, + {collation_, QStringLiteral("utf8_general_ci")}, + }, mysqlCreateDb); + + auto log = DB::connection(mysqlCreateDb).pretend([](auto &connection) + { + Schema::on(connection.getName()).createDatabase(Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create database `firewalls` " + "default character set `utf8` default collate `utf8_general_ci`"); + QVERIFY(firstLog.boundValues.isEmpty()); + + // Restore + DB::removeConnection(mysqlCreateDb); +} + +void tst_Mysql_SchemaBuilder::dropDatabaseIfExists() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).dropDatabaseIfExists(Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "drop database if exists `firewalls`"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::createTable() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + table.Char("char"); + table.Char("char_10", 10); + table.string("string"); + table.string("string_22", 22); + table.tinyText("tiny_text"); + table.text("text"); + table.mediumText("medium_text"); + table.longText("long_text"); + + table.integer("integer"); + table.tinyInteger("tinyInteger"); + table.smallInteger("smallInteger"); + table.mediumInteger("mediumInteger"); + table.bigInteger("bigInteger"); + + table.unsignedInteger("unsignedInteger"); + table.unsignedTinyInteger("unsignedTinyInteger"); + table.unsignedSmallInteger("unsignedSmallInteger"); + table.unsignedMediumInteger("unsignedMediumInteger"); + table.unsignedBigInteger("unsignedBigInteger"); + + table.uuid(); + table.ipAddress(); + table.macAddress(); + }); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`char` char(255) not null, " + "`char_10` char(10) not null, " + "`string` varchar(255) not null, " + "`string_22` varchar(22) not null, " + "`tiny_text` tinytext not null, " + "`text` text not null, " + "`medium_text` mediumtext not null, " + "`long_text` longtext not null, " + "`integer` int not null, " + "`tinyInteger` tinyint not null, " + "`smallInteger` smallint not null, " + "`mediumInteger` mediumint not null, " + "`bigInteger` bigint not null, " + "`unsignedInteger` int unsigned not null, " + "`unsignedTinyInteger` tinyint unsigned not null, " + "`unsignedSmallInteger` smallint unsigned not null, " + "`unsignedMediumInteger` mediumint unsigned not null, " + "`unsignedBigInteger` bigint unsigned not null, " + "`uuid` char(36) not null, " + "`ip_address` varchar(45) not null, " + "`mac_address` varchar(17) not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::createTable_Temporary() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.temporary(); + + table.id(); + table.string(NAME); + }); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create temporary table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`name` varchar(255) not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::createTable_Charset_Collation_Engine() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.charset = UTF8; + table.collation = "utf8_general_ci"; + table.engine = MyISAM; + + table.id(); + table.string(NAME); + }); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`name` varchar(255) not null) " + "default character set utf8 collate 'utf8_general_ci' " + "engine = MyISAM"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::timestamps_rememberToken_CreateAndDrop() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.timestamps(); + table.rememberToken(); + }); + + Schema::on(connection.getName()) + .table(Firewalls, [](Blueprint &table) + { + table.dropTimestamps(); + table.dropRememberToken(); + }); + + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.timestamps(3); + }); + }); + + QCOMPARE(log.size(), 4); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`created_at` timestamp null, " + "`updated_at` timestamp null, " + "`remember_token` varchar(100) null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` drop `created_at`, drop `updated_at`"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` drop `remember_token`"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`created_at` timestamp(3) null, " + "`updated_at` timestamp(3) null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log3.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::modifyTable() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .table(Firewalls, [](Blueprint &table) + { + table.Char("char"); + table.Char("char_10", 10); + table.string("string"); + table.string("string_22", 22); + table.tinyText("tiny_text"); + table.text("text"); + table.mediumText("medium_text"); + table.longText("long_text"); + + table.integer("integer").nullable(); + table.tinyInteger("tinyInteger"); + table.smallInteger("smallInteger"); + table.mediumInteger("mediumInteger"); + + table.dropColumn("long_text"); + table.dropColumns({"medium_text", "text"}); + table.dropColumns("smallInteger", "mediumInteger"); + + table.renameColumn("integer", "integer_renamed"); + table.renameColumn("string_22", "string_22_renamed"); + }); + }); + + QCOMPARE(log.size(), 6); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "alter table `firewalls` " + "add `char` char(255) not null, " + "add `char_10` char(10) not null, " + "add `string` varchar(255) not null, " + "add `string_22` varchar(22) not null, " + "add `tiny_text` tinytext not null, " + "add `text` text not null, " + "add `medium_text` mediumtext not null, " + "add `long_text` longtext not null, " + "add `integer` int null, " + "add `tinyInteger` tinyint not null, " + "add `smallInteger` smallint not null, " + "add `mediumInteger` mediumint not null"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` drop `long_text`"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` drop `medium_text`, drop `text`"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` drop `smallInteger`, drop `mediumInteger`"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log4 = log.at(4); + QCOMPARE(log4.query, + "alter table `firewalls` rename column `integer` to `integer_renamed`"); + QVERIFY(log4.boundValues.isEmpty()); + + const auto &log5 = log.at(5); + QCOMPARE(log5.query, + "alter table `firewalls` rename column `string_22` to `string_22_renamed`"); + QVERIFY(log5.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::dropTable() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).drop(Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "drop table `firewalls`"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::dropTableIfExists() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).dropIfExists(Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "drop table if exists `firewalls`"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::rename() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).rename("secured", Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "rename table `secured` to `firewalls`"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::dropColumns() const +{ + { + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).dropColumn(Firewalls, NAME); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "alter table `firewalls` drop `name`"); + QVERIFY(firstLog.boundValues.isEmpty()); + } + { + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).dropColumns(Firewalls, {NAME, SIZE}); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "alter table `firewalls` drop `name`, drop `size`"); + QVERIFY(firstLog.boundValues.isEmpty()); + } + { + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).dropColumns(Firewalls, NAME, SIZE); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "alter table `firewalls` drop `name`, drop `size`"); + QVERIFY(firstLog.boundValues.isEmpty()); + } +} + +void tst_Mysql_SchemaBuilder::renameColumn() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).renameColumn(Firewalls, NAME, "first_name"); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "alter table `firewalls` rename column `name` to `first_name`"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::dropAllTypes() const +{ + QVERIFY_EXCEPTION_THROWN(Schema::on(m_connection).dropAllTypes(), LogicError); +} + +void tst_Mysql_SchemaBuilder::getAllTables() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).getAllTables(); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "SHOW FULL TABLES WHERE table_type = 'BASE TABLE';"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::getAllViews() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).getAllViews(); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "SHOW FULL TABLES WHERE table_type = 'VIEW';"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::enableForeignKeyConstraints() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).enableForeignKeyConstraints(); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "SET FOREIGN_KEY_CHECKS=1;"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::disableForeignKeyConstraints() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()).disableForeignKeyConstraints(); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, "SET FOREIGN_KEY_CHECKS=0;"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::getColumnListing() const +{ + auto &connection = DB::connection(m_connection); + + auto log = connection.pretend([](auto &connection) + { + Schema::on(connection.getName()).getColumnListing(Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "select `column_name` as `column_name` " + "from `information_schema`.`columns` " + "where `table_schema` = ? and `table_name` = ?"); + QCOMPARE(firstLog.boundValues, + QVector({QVariant(connection.getDatabaseName()), + QVariant(Firewalls)})); +} + +void tst_Mysql_SchemaBuilder::hasTable() const +{ + auto &connection = DB::connection(m_connection); + + auto log = connection.pretend([](auto &connection) + { + Schema::on(connection.getName()).hasTable(Firewalls); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "select * " + "from `information_schema`.`tables` " + "where `table_schema` = ? and `table_name` = ? and " + "`table_type` = 'BASE TABLE'"); + QCOMPARE(firstLog.boundValues, + QVector({QVariant(connection.getDatabaseName()), + QVariant(Firewalls)})); +} + +void tst_Mysql_SchemaBuilder::defaultStringLength_Set() const +{ + QVERIFY(Blueprint::DefaultStringLength == Orm::SchemaNs::DefaultStringLength); + + Schema::defaultStringLength(191); + QVERIFY(Blueprint::DefaultStringLength == 191); + + // Restore + Schema::defaultStringLength(Orm::SchemaNs::DefaultStringLength); + QVERIFY(Blueprint::DefaultStringLength == Orm::SchemaNs::DefaultStringLength); +} + +void tst_Mysql_SchemaBuilder::modifiers() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.bigInteger(ID).autoIncrement().isUnsigned().startingValue(5); + table.string(NAME).defaultValue("guest"); + table.string("name1").nullable(); + table.string("name2").comment("name2 note"); + table.string("name3"); + table.string("name4").invisible(); + table.string("name5").charset("utf8"); + table.string("name6").collation("utf8mb4_unicode_ci"); + table.string("name7").charset("utf8").collation("utf8_unicode_ci"); + table.bigInteger("big_int").isUnsigned(); + table.bigInteger("big_int1"); + }); + // Tests from and also integerIncrements + Schema::on(connection.getName()) + .table(Firewalls, [](Blueprint &table) + { + table.string(NAME).after("big_int"); + table.integerIncrements(ID).autoIncrement().from(15).first(); + }); + }); + + QCOMPARE(log.size(), 4); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`name` varchar(255) not null default 'guest', " + "`name1` varchar(255) null, " + "`name2` varchar(255) not null comment 'name2 note', " + "`name3` varchar(255) not null, " + "`name4` varchar(255) not null invisible, " + "`name5` varchar(255) character set 'utf8' not null, " + "`name6` varchar(255) collate 'utf8mb4_unicode_ci' not null, " + "`name7` varchar(255) character set 'utf8' collate 'utf8_unicode_ci' " + "not null, " + "`big_int` bigint unsigned not null, " + "`big_int1` bigint not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` auto_increment = 5"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` " + "add `name` varchar(255) not null after `big_int`, " + "add `id` int unsigned not null auto_increment primary key first"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` auto_increment = 15"); + QVERIFY(log3.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::modifier_defaultValue_WithExpression() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.string(NAME).defaultValue("guest"); + table.string("name_raw").defaultValue(DB::raw("'guest_raw'")); + }); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create table `firewalls` (" + "`name` varchar(255) not null default 'guest', " + "`name_raw` varchar(255) not null default 'guest_raw') " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::modifier_defaultValue_WithBoolean() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.boolean("boolean"); + table.boolean("boolean_false").defaultValue(false); + table.boolean("boolean_true").defaultValue(true); + table.boolean("boolean_0").defaultValue(0); + table.boolean("boolean_1").defaultValue(1); + }); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create table `firewalls` (" + "`boolean` tinyint(1) not null, " + "`boolean_false` tinyint(1) not null default '0', " + "`boolean_true` tinyint(1) not null default '1', " + "`boolean_0` tinyint(1) not null default '0', " + "`boolean_1` tinyint(1) not null default '1') " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::useCurrent() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.dateTime("created"); + table.dateTime("created_current").useCurrent(); + + table.timestamp("created_t"); + table.timestamp("created_t_current").useCurrent(); + }); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create table `firewalls` (" + "`created` datetime not null, " + "`created_current` datetime default CURRENT_TIMESTAMP not null, " + "`created_t` timestamp not null, " + "`created_t_current` timestamp default CURRENT_TIMESTAMP not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::useCurrentOnUpdate() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.dateTime("updated"); + table.dateTime("updated_current").useCurrentOnUpdate(); + + table.timestamp("updated_t"); + table.timestamp("updated_t_current").useCurrentOnUpdate(); + }); + }); + + QVERIFY(!log.isEmpty()); + const auto &firstLog = log.first(); + + QCOMPARE(log.size(), 1); + QCOMPARE(firstLog.query, + "create table `firewalls` (" + "`updated` datetime not null, " + "`updated_current` datetime on update CURRENT_TIMESTAMP not null, " + "`updated_t` timestamp not null, " + "`updated_t_current` timestamp on update CURRENT_TIMESTAMP not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(firstLog.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::indexes_Fluent() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + // Fluent indexes + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.string("name_u").unique(); + + table.string("name_i").index(); + table.string("name_i_cn").index("name_i_cn_index"); + + table.string("name_f").fulltext(); + table.string("name_f_cn").fulltext("name_f_cn_fulltext"); + + table.geometry("coordinates_s").spatialIndex(); + table.geometry("coordinates_s_cn").spatialIndex("coordinates_s_cn_spatial"); + }); + }); + + QCOMPARE(log.size(), 8); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`name_u` varchar(255) not null, " + "`name_i` varchar(255) not null, " + "`name_i_cn` varchar(255) not null, " + "`name_f` varchar(255) not null, " + "`name_f_cn` varchar(255) not null, " + "`coordinates_s` geometry not null, " + "`coordinates_s_cn` geometry not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` add unique `firewalls_name_u_unique`(`name_u`)"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` add index `firewalls_name_i_index`(`name_i`)"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` add index `name_i_cn_index`(`name_i_cn`)"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log4 = log.at(4); + QCOMPARE(log4.query, + "alter table `firewalls` " + "add fulltext `firewalls_name_f_fulltext`(`name_f`)"); + QVERIFY(log4.boundValues.isEmpty()); + + const auto &log5 = log.at(5); + QCOMPARE(log5.query, + "alter table `firewalls` add fulltext `name_f_cn_fulltext`(`name_f_cn`)"); + QVERIFY(log5.boundValues.isEmpty()); + + const auto &log6 = log.at(6); + QCOMPARE(log6.query, + "alter table `firewalls` " + "add spatial index `firewalls_coordinates_s_spatialindex`(`coordinates_s`)"); + QVERIFY(log6.boundValues.isEmpty()); + + const auto &log7 = log.at(7); + QCOMPARE(log7.query, + "alter table `firewalls` " + "add spatial index `coordinates_s_cn_spatial`(`coordinates_s_cn`)"); + QVERIFY(log7.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::indexes_Blueprint() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + // Blueprint indexes + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.string("name_u"); + table.unique({"name_u"}, "name_u_unique"); + + table.string("name_i"); + table.index({"name_i"}); + + table.string("name_i_cn"); + table.index("name_i_cn", "name_i_cn_index"); + + table.string("name_r"); + table.string("name_r1"); + table.rawIndex(DB::raw("`name_r`, name_r1"), "name_r_raw"); + + table.string("name_f"); + table.fullText({"name_f"}); + + table.string("name_f_cn"); + table.fullText("name_f_cn", "name_f_cn_fulltext"); + + table.geometry("coordinates_s"); + table.spatialIndex("coordinates_s"); + + table.geometry("coordinates_s_cn"); + table.spatialIndex("coordinates_s_cn", "coordinates_s_cn_spatial"); + }); + }); + + QCOMPARE(log.size(), 9); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`name_u` varchar(255) not null, " + "`name_i` varchar(255) not null, " + "`name_i_cn` varchar(255) not null, " + "`name_r` varchar(255) not null, " + "`name_r1` varchar(255) not null, " + "`name_f` varchar(255) not null, " + "`name_f_cn` varchar(255) not null, " + "`coordinates_s` geometry not null, " + "`coordinates_s_cn` geometry not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` add unique `name_u_unique`(`name_u`)"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` add index `firewalls_name_i_index`(`name_i`)"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` add index `name_i_cn_index`(`name_i_cn`)"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log4 = log.at(4); + QCOMPARE(log4.query, + "alter table `firewalls` add index `name_r_raw`(`name_r`, name_r1)"); + QVERIFY(log4.boundValues.isEmpty()); + + const auto &log5 = log.at(5); + QCOMPARE(log5.query, + "alter table `firewalls` " + "add fulltext `firewalls_name_f_fulltext`(`name_f`)"); + QVERIFY(log5.boundValues.isEmpty()); + + const auto &log6 = log.at(6); + QCOMPARE(log6.query, + "alter table `firewalls` add fulltext `name_f_cn_fulltext`(`name_f_cn`)"); + QVERIFY(log6.boundValues.isEmpty()); + + const auto &log7 = log.at(7); + QCOMPARE(log7.query, + "alter table `firewalls` " + "add spatial index `firewalls_coordinates_s_spatialindex`(`coordinates_s`)"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log8 = log.at(8); + QCOMPARE(log8.query, + "alter table `firewalls` " + "add spatial index `coordinates_s_cn_spatial`(`coordinates_s_cn`)"); + QVERIFY(log8.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::renameIndex() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.string(NAME).unique(); + }); + + Schema::on(connection.getName()) + .table(Firewalls, [](Blueprint &table) + { + table.renameIndex("firewalls_name_unique", "firewalls_name_unique_renamed"); + }); + }); + + QCOMPARE(log.size(), 3); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`name` varchar(255) not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` add unique `firewalls_name_unique`(`name`)"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` " + "rename index `firewalls_name_unique` to `firewalls_name_unique_renamed`"); + QVERIFY(log2.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::dropIndex_ByIndexName() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.unsignedInteger(ID); + table.primary(ID); + + table.string("name_u").unique(); + table.string("name_i").index(); + table.string("name_f").fulltext(); + table.geometry("coordinates_s").spatialIndex(); + }); + + Schema::on(connection.getName()) + .table(Firewalls, [](Blueprint &table) + { + table.dropPrimary(); + table.dropUnique("firewalls_name_u_unique"); + table.dropIndex("firewalls_name_i_index"); + table.dropFullText("firewalls_name_f_fulltext"); + table.dropSpatialIndex("firewalls_coordinates_s_spatialindex"); + }); + }); + + QCOMPARE(log.size(), 11); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` int unsigned not null, " + "`name_u` varchar(255) not null, " + "`name_i` varchar(255) not null, " + "`name_f` varchar(255) not null, " + "`coordinates_s` geometry not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` add primary key `firewalls_id_primary`(`id`)"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` add unique `firewalls_name_u_unique`(`name_u`)"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` add index `firewalls_name_i_index`(`name_i`)"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log4 = log.at(4); + QCOMPARE(log4.query, + "alter table `firewalls` " + "add fulltext `firewalls_name_f_fulltext`(`name_f`)"); + QVERIFY(log4.boundValues.isEmpty()); + + const auto &log5 = log.at(5); + QCOMPARE(log5.query, + "alter table `firewalls` " + "add spatial index " + "`firewalls_coordinates_s_spatialindex`(`coordinates_s`)"); + QVERIFY(log5.boundValues.isEmpty()); + + const auto &log6 = log.at(6); + QCOMPARE(log6.query, + "alter table `firewalls` drop primary key"); + QVERIFY(log6.boundValues.isEmpty()); + + const auto &log7 = log.at(7); + QCOMPARE(log7.query, + "alter table `firewalls` drop index `firewalls_name_u_unique`"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log8 = log.at(8); + QCOMPARE(log8.query, + "alter table `firewalls` drop index `firewalls_name_i_index`"); + QVERIFY(log8.boundValues.isEmpty()); + + const auto &log9 = log.at(9); + QCOMPARE(log9.query, + "alter table `firewalls` drop index `firewalls_name_f_fulltext`"); + QVERIFY(log9.boundValues.isEmpty()); + + const auto &log10 = log.at(10); + QCOMPARE(log10.query, + "alter table `firewalls` " + "drop index `firewalls_coordinates_s_spatialindex`"); + QVERIFY(log10.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::dropIndex_ByColumn() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.unsignedInteger(ID); + table.primary(ID); + + table.string("name_u").unique(); + table.string("name_i").index(); + table.string("name_f").fulltext(); + table.geometry("coordinates_s").spatialIndex(); + }); + + Schema::on(connection.getName()) + .table(Firewalls, [](Blueprint &table) + { + table.dropPrimary(); + table.dropUnique({"name_u"}); + table.dropIndex({"name_i"}); + table.dropFullText({"name_f"}); + table.dropSpatialIndex({"coordinates_s"}); + }); + }); + + QCOMPARE(log.size(), 11); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` int unsigned not null, " + "`name_u` varchar(255) not null, " + "`name_i` varchar(255) not null, " + "`name_f` varchar(255) not null, " + "`coordinates_s` geometry not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` add primary key `firewalls_id_primary`(`id`)"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` add unique `firewalls_name_u_unique`(`name_u`)"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` add index `firewalls_name_i_index`(`name_i`)"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log4 = log.at(4); + QCOMPARE(log4.query, + "alter table `firewalls` " + "add fulltext `firewalls_name_f_fulltext`(`name_f`)"); + QVERIFY(log4.boundValues.isEmpty()); + + const auto &log5 = log.at(5); + QCOMPARE(log5.query, + "alter table `firewalls` " + "add spatial index " + "`firewalls_coordinates_s_spatialindex`(`coordinates_s`)"); + QVERIFY(log5.boundValues.isEmpty()); + + const auto &log6 = log.at(6); + QCOMPARE(log6.query, + "alter table `firewalls` drop primary key"); + QVERIFY(log6.boundValues.isEmpty()); + + const auto &log7 = log.at(7); + QCOMPARE(log7.query, + "alter table `firewalls` drop index `firewalls_name_u_unique`"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log8 = log.at(8); + QCOMPARE(log8.query, + "alter table `firewalls` drop index `firewalls_name_i_index`"); + QVERIFY(log8.boundValues.isEmpty()); + + const auto &log9 = log.at(9); + QCOMPARE(log9.query, + "alter table `firewalls` drop index `firewalls_name_f_fulltext`"); + QVERIFY(log9.boundValues.isEmpty()); + + const auto &log10 = log.at(10); + QCOMPARE(log10.query, + "alter table `firewalls` " + "drop index `firewalls_coordinates_s_spatialindex`"); + QVERIFY(log10.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::dropIndex_ByMultipleColumns() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.unsignedInteger(ID); + table.unsignedInteger("id1"); + table.primary({ID, "id1"}); + + table.string("name_u"); + table.string("name_u1"); + table.unique({"name_u", "name_u1"}); + + table.string("name_i"); + table.string("name_i1"); + table.index({"name_i", "name_i1"}); + + table.string("name_f"); + table.string("name_f1"); + table.fullText({"name_f", "name_f1"}); + }); + + Schema::on(connection.getName()) + .table(Firewalls, [](Blueprint &table) + { + table.dropPrimary({ID, "id1"}); + table.dropUnique({"name_u", "name_u1"}); + table.dropIndex({"name_i", "name_i1"}); + table.dropFullText({"name_f", "name_f1"}); + }); + }); + + QCOMPARE(log.size(), 9); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` int unsigned not null, " + "`id1` int unsigned not null, " + "`name_u` varchar(255) not null, " + "`name_u1` varchar(255) not null, " + "`name_i` varchar(255) not null, " + "`name_i1` varchar(255) not null, " + "`name_f` varchar(255) not null, " + "`name_f1` varchar(255) not null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` " + "add primary key `firewalls_id_id1_primary`(`id`, `id1`)"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` " + "add unique `firewalls_name_u_name_u1_unique`(`name_u`, `name_u1`)"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` " + "add index `firewalls_name_i_name_i1_index`(`name_i`, `name_i1`)"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log4 = log.at(4); + QCOMPARE(log4.query, + "alter table `firewalls` " + "add fulltext `firewalls_name_f_name_f1_fulltext`(`name_f`, `name_f1`)"); + QVERIFY(log4.boundValues.isEmpty()); + + const auto &log5 = log.at(5); + QCOMPARE(log5.query, + "alter table `firewalls` drop primary key"); + QVERIFY(log5.boundValues.isEmpty()); + + const auto &log6 = log.at(6); + QCOMPARE(log6.query, + "alter table `firewalls` drop index `firewalls_name_u_name_u1_unique`"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log7 = log.at(7); + QCOMPARE(log7.query, + "alter table `firewalls` drop index `firewalls_name_i_name_i1_index`"); + QVERIFY(log7.boundValues.isEmpty()); + + const auto &log8 = log.at(8); + QCOMPARE(log8.query, + "alter table `firewalls` drop index `firewalls_name_f_name_f1_fulltext`"); + QVERIFY(log8.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::foreignKey() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.unsignedBigInteger("user_id"); + table.unsignedBigInteger("torrent_id"); + table.unsignedBigInteger("role_id").nullable(); + + table.foreign("user_id").references(ID).on("users") + .onDelete(Cascade).onUpdate(Restrict); + table.foreign("torrent_id").references(ID).on("torrents") + .restrictOnDelete().restrictOnUpdate(); + table.foreign("role_id").references(ID).on("roles") + .nullOnDelete().cascadeOnUpdate(); + }); + }); + + QCOMPARE(log.size(), 4); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`user_id` bigint unsigned not null, " + "`torrent_id` bigint unsigned not null, " + "`role_id` bigint unsigned null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` " + "add constraint `firewalls_user_id_foreign` " + "foreign key (`user_id`) " + "references `users` (`id`) " + "on delete cascade on update restrict"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` " + "add constraint `firewalls_torrent_id_foreign` " + "foreign key (`torrent_id`) " + "references `torrents` (`id`) " + "on delete restrict on update restrict"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` " + "add constraint `firewalls_role_id_foreign` " + "foreign key (`role_id`) " + "references `roles` (`id`) " + "on delete set null on update cascade"); + QVERIFY(log3.boundValues.isEmpty()); +} + +void tst_Mysql_SchemaBuilder::foreignKey_TerserSyntax() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.foreignId("user_id").constrained() + .onDelete(Cascade).onUpdate(Restrict); + table.foreignId("torrent_id").constrained() + .restrictOnDelete().restrictOnUpdate(); + table.foreignId("role_id").nullable().constrained() + .nullOnDelete().cascadeOnUpdate(); + }); + }); + + QCOMPARE(log.size(), 4); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`user_id` bigint unsigned not null, " + "`torrent_id` bigint unsigned not null, " + "`role_id` bigint unsigned null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` " + "add constraint `firewalls_user_id_foreign` " + "foreign key (`user_id`) " + "references `users` (`id`) " + "on delete cascade on update restrict"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` " + "add constraint `firewalls_torrent_id_foreign` " + "foreign key (`torrent_id`) " + "references `torrents` (`id`) " + "on delete restrict on update restrict"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` " + "add constraint `firewalls_role_id_foreign` " + "foreign key (`role_id`) " + "references `roles` (`id`) " + "on delete set null on update cascade"); + QVERIFY(log3.boundValues.isEmpty()); +} + +#ifndef TINYORM_DISABLE_ORM +void tst_Mysql_SchemaBuilder::foreignKey_WithModel() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Models::Torrent torrent; + Models::User user; + + Schema::on(connection.getName()) + .create(Firewalls, [&torrent, &user](Blueprint &table) + { + table.id(); + + table.foreignIdFor(torrent).constrained() + .onDelete(Cascade).onUpdate(Restrict); + table.foreignIdFor(user).nullable().constrained() + .nullOnDelete().cascadeOnUpdate(); + }); + }); + + QCOMPARE(log.size(), 3); + + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`torrent_id` bigint unsigned not null, " + "`user_id` bigint unsigned null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` " + "add constraint `firewalls_torrent_id_foreign` " + "foreign key (`torrent_id`) " + "references `torrents` (`id`) " + "on delete cascade on update restrict"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` " + "add constraint `firewalls_user_id_foreign` " + "foreign key (`user_id`) " + "references `users` (`id`) " + "on delete set null on update cascade"); + QVERIFY(log2.boundValues.isEmpty()); +} +#endif + +void tst_Mysql_SchemaBuilder::dropForeign() const +{ + auto log = DB::connection(m_connection).pretend([](auto &connection) + { + Schema::on(connection.getName()) + .create(Firewalls, [](Blueprint &table) + { + table.id(); + + table.foreignId("user_id").constrained() + .onDelete(Cascade).onUpdate(Restrict); + table.foreignId("torrent_id").constrained() + .restrictOnDelete().restrictOnUpdate(); + table.foreignId("role_id").nullable().constrained() + .nullOnDelete().cascadeOnUpdate(); + + // By column name + table.dropForeign({"user_id"}); + // By index name + table.dropForeign("firewalls_torrent_id_foreign"); + // Drop index and also a column + table.dropConstrainedForeignId("role_id"); + }); + }); + + QCOMPARE(log.size(), 8); + + // I leave these comparisons here, even if they are doubled from the previous test + const auto &log0 = log.at(0); + QCOMPARE(log0.query, + "create table `firewalls` (" + "`id` bigint unsigned not null auto_increment primary key, " + "`user_id` bigint unsigned not null, " + "`torrent_id` bigint unsigned not null, " + "`role_id` bigint unsigned null) " + "default character set utf8mb4 collate 'utf8mb4_0900_ai_ci' " + "engine = InnoDB"); + QVERIFY(log0.boundValues.isEmpty()); + + const auto &log1 = log.at(1); + QCOMPARE(log1.query, + "alter table `firewalls` " + "add constraint `firewalls_user_id_foreign` " + "foreign key (`user_id`) " + "references `users` (`id`) " + "on delete cascade on update restrict"); + QVERIFY(log1.boundValues.isEmpty()); + + const auto &log2 = log.at(2); + QCOMPARE(log2.query, + "alter table `firewalls` " + "add constraint `firewalls_torrent_id_foreign` " + "foreign key (`torrent_id`) " + "references `torrents` (`id`) " + "on delete restrict on update restrict"); + QVERIFY(log2.boundValues.isEmpty()); + + const auto &log3 = log.at(3); + QCOMPARE(log3.query, + "alter table `firewalls` " + "add constraint `firewalls_role_id_foreign` " + "foreign key (`role_id`) " + "references `roles` (`id`) " + "on delete set null on update cascade"); + QVERIFY(log3.boundValues.isEmpty()); + + const auto &log4 = log.at(4); + QCOMPARE(log4.query, + "alter table `firewalls` drop foreign key `firewalls_user_id_foreign`"); + QVERIFY(log4.boundValues.isEmpty()); + + const auto &log5 = log.at(5); + QCOMPARE(log5.query, + "alter table `firewalls` drop foreign key `firewalls_torrent_id_foreign`"); + QVERIFY(log5.boundValues.isEmpty()); + + const auto &log6 = log.at(6); + QCOMPARE(log6.query, + "alter table `firewalls` drop foreign key `firewalls_role_id_foreign`"); + QVERIFY(log6.boundValues.isEmpty()); + + const auto &log7 = log.at(7); + QCOMPARE(log7.query, + "alter table `firewalls` drop `role_id`"); + QVERIFY(log7.boundValues.isEmpty()); +} + +QTEST_MAIN(tst_Mysql_SchemaBuilder) + +#include "tst_mysql_schemabuilder.moc" diff --git a/tests/auto/unit/orm/schema/schema.pro b/tests/auto/unit/orm/schema/schema.pro new file mode 100644 index 000000000..1edf4a899 --- /dev/null +++ b/tests/auto/unit/orm/schema/schema.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + blueprint \ + mysql_schemabuilder \