diff --git a/cmake/Modules/TinySources.cmake b/cmake/Modules/TinySources.cmake
index b8aaad03f..1b5cbabb5 100644
--- a/cmake/Modules/TinySources.cmake
+++ b/cmake/Modules/TinySources.cmake
@@ -78,6 +78,7 @@ function(tinyorm_sources out_headers out_sources)
schema/grammars/postgresschemagrammar.hpp
schema/grammars/schemagrammar.hpp
schema/grammars/sqliteschemagrammar.hpp
+ schema/indexdefinitionreference.hpp
schema/mysqlschemabuilder.hpp
schema/postgresschemabuilder.hpp
schema/schemabuilder.hpp
@@ -193,6 +194,7 @@ function(tinyorm_sources out_headers out_sources)
schema/grammars/postgresschemagrammar.cpp
schema/grammars/schemagrammar.cpp
schema/grammars/sqliteschemagrammar.cpp
+ schema/indexdefinitionreference.cpp
schema/mysqlschemabuilder.cpp
schema/postgresschemabuilder.cpp
schema/schemabuilder.cpp
diff --git a/docs/database/migrations.mdx b/docs/database/migrations.mdx
index fa02081c8..b9c8f18df 100644
--- a/docs/database/migrations.mdx
+++ b/docs/database/migrations.mdx
@@ -38,7 +38,7 @@ The TinyORM `Schema` facade provides database agnostic support for creating and
`Tom` migrations is a small console application that depends on the `TinyORM` library. All migrations logic is compiled so recompilation is needed after adding a new migration class.
:::caution
-Currently, TinyORM's schema builder provides first-party support for the MySQL database only. Support for the PostgreSQL and SQLite databases is not implemented 😢.
+Currently, TinyORM's schema builder provides first-party support for the MySQL and PostgreSQL database. Support for the SQLite databases is not implemented 😢.
:::
## Generating Migrations
@@ -253,7 +253,7 @@ You may check for the existence of a table or column using the `hasTable` and `h
If you want to perform a schema operation on a database connection that is not your application's default connection, use the `connection` method or `on` alias:
- Schema::connection("sqlite").create("users", [](Blueprint &table)
+ Schema::connection("postgres").create("users", [](Blueprint &table)
{
table.id();
});
diff --git a/docs/database/seeding.mdx b/docs/database/seeding.mdx
index 00ce9ffe0..98c3355f7 100644
--- a/docs/database/seeding.mdx
+++ b/docs/database/seeding.mdx
@@ -19,6 +19,10 @@ TinyORM includes the ability to seed your database with data using seed classes.
[Mass assignment protection](tinyorm/getting-started.mdx#mass-assignment) is automatically disabled during database seeding.
:::
+:::note
+Because seeding is independent of the schema builder, it supports all [supported databases](database/getting-started.mdx#introduction) out of the box.
+:::
+
## Writing Seeders
To generate a seeder, execute the `make:seeder` `tom` command. A new seeder will be placed in the `database/seeders` directory relative to the current pwd:
diff --git a/docs/supported-compilers.mdx b/docs/supported-compilers.mdx
index 92049af2d..97944c214 100644
--- a/docs/supported-compilers.mdx
+++ b/docs/supported-compilers.mdx
@@ -7,7 +7,7 @@ description: Platform requirements and supported compilers for TinyORM c++ libra
# Supported Compilers
-Following compilers are backed up by the GitHub Action [workflows](https://github.com/silverqx/TinyORM/tree/main/.github/workflows) (CI pipelines), these workflows also include more then 920 unit tests.
+Following compilers are backed up by the GitHub Action [workflows](https://github.com/silverqx/TinyORM/tree/main/.github/workflows) (CI pipelines), these workflows also include more then 973 unit tests 😮.
Windows >=10:
diff --git a/include/include.pri b/include/include.pri
index 22c6e3409..bb0dcaf3a 100644
--- a/include/include.pri
+++ b/include/include.pri
@@ -73,6 +73,7 @@ headersList += \
$$PWD/orm/schema/grammars/postgresschemagrammar.hpp \
$$PWD/orm/schema/grammars/schemagrammar.hpp \
$$PWD/orm/schema/grammars/sqliteschemagrammar.hpp \
+ $$PWD/orm/schema/indexdefinitionreference.hpp \
$$PWD/orm/schema/mysqlschemabuilder.hpp \
$$PWD/orm/schema/postgresschemabuilder.hpp \
$$PWD/orm/schema/schemabuilder.hpp \
diff --git a/include/orm/constants_extern.hpp b/include/orm/constants_extern.hpp
index 1f0f73a1c..2d6d87bb8 100644
--- a/include/orm/constants_extern.hpp
+++ b/include/orm/constants_extern.hpp
@@ -52,6 +52,7 @@ namespace Orm::Constants
SHAREDLIB_EXPORT extern const QString SPACE_IN;
SHAREDLIB_EXPORT extern const QString NOSPACE;
SHAREDLIB_EXPORT extern const QString EMPTY;
+ SHAREDLIB_EXPORT extern const QString text_;
SHAREDLIB_EXPORT extern const QString QMYSQL;
SHAREDLIB_EXPORT extern const QString QPSQL;
@@ -74,6 +75,7 @@ namespace Orm::Constants
SHAREDLIB_EXPORT extern const QString options_;
SHAREDLIB_EXPORT extern const QString strict_;
SHAREDLIB_EXPORT extern const QString engine_;
+ SHAREDLIB_EXPORT extern const QString dont_drop;
SHAREDLIB_EXPORT extern const QString isolation_level;
SHAREDLIB_EXPORT extern const QString foreign_key_constraints;
@@ -94,8 +96,10 @@ namespace Orm::Constants
SHAREDLIB_EXPORT extern const QString UTF8MB4;
SHAREDLIB_EXPORT extern const QString InnoDB;
SHAREDLIB_EXPORT extern const QString MyISAM;
+ SHAREDLIB_EXPORT extern const QString postgres_;
SHAREDLIB_EXPORT extern const QString UTF8MB40900aici;
+ SHAREDLIB_EXPORT extern const QString UcsBasic;
SHAREDLIB_EXPORT extern const QString NotImplemented;
// Comparison/logical/search operators
diff --git a/include/orm/constants_inline.hpp b/include/orm/constants_inline.hpp
index 77271ae16..437e7156a 100644
--- a/include/orm/constants_inline.hpp
+++ b/include/orm/constants_inline.hpp
@@ -52,6 +52,7 @@ namespace Orm::Constants
inline const QString SPACE_IN = QStringLiteral("%1 %2");
inline const QString NOSPACE = QStringLiteral("%1%2");
inline const QString EMPTY = QLatin1String("");
+ inline const QString text_ = QStringLiteral("text");
inline const QString QMYSQL = QStringLiteral("QMYSQL");
inline const QString QPSQL = QStringLiteral("QPSQL");
@@ -74,6 +75,7 @@ namespace Orm::Constants
inline const QString options_ = QStringLiteral("options");
inline const QString strict_ = QStringLiteral("strict");
inline const QString engine_ = QStringLiteral("engine");
+ inline const QString dont_drop = QStringLiteral("dont_drop");
inline const QString
isolation_level = QStringLiteral("isolation_level");
@@ -98,8 +100,10 @@ namespace Orm::Constants
inline const QString UTF8MB4 = QStringLiteral("utf8mb4");
inline const QString InnoDB = QStringLiteral("InnoDB");
inline const QString MyISAM = QStringLiteral("MyISAM");
+ inline const QString postgres_ = QStringLiteral("postgres");
inline const QString UTF8MB40900aici = QStringLiteral("utf8mb4_0900_ai_ci");
+ inline const QString UcsBasic = QStringLiteral("ucs_basic");
inline const QString NotImplemented = QStringLiteral("Not implemented :/.");
// Comparison/logical/search operators
diff --git a/include/orm/schema/blueprint.hpp b/include/orm/schema/blueprint.hpp
index de9962f27..6c9682e91 100644
--- a/include/orm/schema/blueprint.hpp
+++ b/include/orm/schema/blueprint.hpp
@@ -11,6 +11,7 @@ TINY_SYSTEM_HEADER
#include "orm/ormconcepts.hpp"
#include "orm/schema/foreignidcolumndefinitionreference.hpp"
+#include "orm/schema/indexdefinitionreference.hpp"
#include "orm/schema/schemaconstants.hpp"
#ifndef TINYORM_DISABLE_ORM
# include "orm/tiny/tinytypes.hpp"
@@ -89,50 +90,50 @@ namespace Grammars
const RenameCommand &renameColumn(const QString &from, const QString &to);
/*! Specify the primary key(s) for the table. */
- const IndexCommand &
+ IndexDefinitionReference
primary(const QVector &columns, const QString &indexName = "",
const QString &algorithm = "");
/*! Specify the primary key(s) for the table. */
template
- const IndexCommand &
+ IndexDefinitionReference
primary(const QString &column, const QString &indexName = "",
const QString &algorithm = "");
/*! Specify a unique index for the table. */
- const IndexCommand &
+ IndexDefinitionReference
unique(const QVector &columns, const QString &indexName = "",
const QString &algorithm = "");
/*! Specify a unique index for the table. */
template
- const IndexCommand &
+ IndexDefinitionReference
unique(const QString &column, const QString &indexName = "",
const QString &algorithm = "");
/*! Specify an index for the table. */
- const IndexCommand &
+ IndexDefinitionReference
index(const QVector &columns, const QString &indexName = "",
const QString &algorithm = "");
/*! Specify an index for the table. */
template
- const IndexCommand &
+ IndexDefinitionReference
index(const QString &column, const QString &indexName = "",
const QString &algorithm = "");
/*! Specify an fulltext for the table. */
- const IndexCommand &
+ IndexDefinitionReference
fullText(const QVector &columns, const QString &indexName = "",
- const QString &algorithm = "");
+ const QString &algorithm = "", const QString &language = "");
/*! Specify an fulltext for the table. */
template
- const IndexCommand &
+ IndexDefinitionReference
fullText(const QString &column, const QString &indexName = "",
- const QString &algorithm = "");
+ const QString &algorithm = "", const QString &language = "");
/*! Specify a spatial index for the table. */
- const IndexCommand &
+ IndexDefinitionReference
spatialIndex(const QVector &columns, const QString &indexName = "");
/*! Specify a spatial index for the table. */
template
- const IndexCommand &
+ IndexDefinitionReference
spatialIndex(const QString &column, const QString &indexName = "");
/*! Specify a raw index for the table. */
- const IndexCommand &
+ IndexDefinitionReference
rawIndex(const Expression &expression, const QString &indexName);
/*! Specify a foreign key for the table. */
ForeignKeyDefinitionReference
@@ -433,11 +434,14 @@ namespace Grammars
void addImpliedCommands(const SchemaGrammar &grammar);
/*! Add the index commands fluently specified on columns. */
void addFluentIndexes();
+ /*! Add the fluent commands specified on any columns. */
+ void addFluentCommands(const SchemaGrammar &grammar);
/*! Add a new index command to the blueprint. */
- const IndexCommand &
+ IndexDefinitionReference
indexCommand(const QString &type, const QVector &columns,
- const QString &indexName, const QString &algorithm = "");
+ const QString &indexName, const QString &algorithm = "",
+ const QString &language = "");
/*! Create a new drop index command on the blueprint. */
const IndexCommand &
dropIndexCommand(const QString &command, const QString &type,
@@ -500,7 +504,7 @@ namespace Grammars
}
template
- const IndexCommand &
+ IndexDefinitionReference
Blueprint::primary(const QString &column, const QString &indexName,
const QString &algorithm)
{
@@ -508,7 +512,7 @@ namespace Grammars
}
template
- const IndexCommand &
+ IndexDefinitionReference
Blueprint::unique(const QString &column, const QString &indexName,
const QString &algorithm)
{
@@ -516,7 +520,7 @@ namespace Grammars
}
template
- const IndexCommand &
+ IndexDefinitionReference
Blueprint::index(const QString &column, const QString &indexName,
const QString &algorithm)
{
@@ -524,15 +528,15 @@ namespace Grammars
}
template
- const IndexCommand &
+ IndexDefinitionReference
Blueprint::fullText(const QString &column, const QString &indexName,
- const QString &algorithm)
+ const QString &algorithm, const QString &language)
{
- return fullText(QVector {column}, indexName, algorithm);
+ return fullText(QVector {column}, indexName, algorithm, language);
}
template
- const IndexCommand &
+ IndexDefinitionReference
Blueprint::spatialIndex(const QString &column, const QString &indexName)
{
return spatialIndex(QVector {column}, indexName);
diff --git a/include/orm/schema/columndefinition.hpp b/include/orm/schema/columndefinition.hpp
index 2c7c34ee3..b3377145e 100644
--- a/include/orm/schema/columndefinition.hpp
+++ b/include/orm/schema/columndefinition.hpp
@@ -71,6 +71,8 @@ namespace Orm::SchemaNs
QVector columns;
/*! Algorithm to use during index creation. */
QString algorithm {};
+ /*! Dictionary for the to_tsvector function for fulltext search (PostgreSQL). */
+ QString language {};
};
/*! Foreign key constraints command. */
@@ -96,6 +98,26 @@ namespace Orm::SchemaNs
/*! Specifies ON UPDATE action (cascade/restrict/set null/no action/
set default). */
QString onUpdate {};
+
+ /*! Set the foreign key as deferrable (PostgreSQL). */
+ std::optional deferrable = std::nullopt;
+ /*! Set the default time to check the constraint (PostgreSQL). */
+ std::optional initiallyImmediate = std::nullopt;
+ /*! Set the skip check that all existing rows in the table satisfy the new
+ constraint, skip this check on true (PostgreSQL). */
+ std::optional notValid = std::nullopt;
+ };
+
+ /*! Column comment command for the PostgreSQL. */
+ class CommentCommand : public CommandDefinition
+ {
+ public:
+ /*! Command name. */
+ QString name {};
+ /*! Column name. */
+ QString column;
+ /*! Column comment value. */
+ QString comment;
};
/*! Database column definition. */
@@ -126,7 +148,7 @@ namespace Orm::SchemaNs
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. */
+ in which the geometry is defined (MySQL/PostgreSQL). */
std::optional srid = std::nullopt;
/*! Number of digits before the decimal point for floating-point types. */
std::optional total = std::nullopt;
@@ -154,22 +176,30 @@ namespace Orm::SchemaNs
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 = std::nullopt;
- /*! Set the starting value of an auto-incrementing field (MySQL / PostgreSQL). */
- std::optional startingValue = std::nullopt;
+ /*! Create a SQL compliant identity column (PostgreSQL). */
+ QString generatedAs {};
/*! Create a stored generated column (MySQL/PostgreSQL/SQLite). */
- QString storedAs {};
+ QString storedAs {};
/*! Create a virtual generated column (MySQL/PostgreSQL/SQLite). */
- QString virtualAs {};
+ QString virtualAs {};
+
+ /*! Set the starting value of an auto-incrementing field (MySQL/PostgreSQL). */
+ std::optional from = std::nullopt;
+ /*! Set the starting value of an auto-incrementing field (MySQL/PostgreSQL). */
+ std::optional startingValue = std::nullopt;
// Place boolean data members at the end to avoid excessive padding
+ /*! Used as a modifier for generatedAs() (PostgreSQL). */
+ bool always = false;
/*! 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 to use the geography (default, false) or
+ geometry type (PostgreSQL). */
+ bool isGeometry = false;
/*! Determine whether the INTEGER column is UNSIGNED (MySQL). */
bool isUnsigned = false;
/*! Allow NULL values to be inserted into the column. */
diff --git a/include/orm/schema/columndefinitionreference.hpp b/include/orm/schema/columndefinitionreference.hpp
index d2cd57d88..ffec6f987 100644
--- a/include/orm/schema/columndefinitionreference.hpp
+++ b/include/orm/schema/columndefinitionreference.hpp
@@ -54,6 +54,8 @@ namespace Orm::SchemaNs
/*! Place the column "after" another column (MySQL). */
ColumnReferenceType &after(const QString &column);
+ /*! Used as a modifier for generatedAs() (PostgreSQL). */
+ ColumnReferenceType &always();
/*! Set INTEGER column as auto-increment (primary key). */
ColumnReferenceType &autoIncrement();
/*! Specify a character set for the column (MySQL). */
@@ -68,12 +70,20 @@ namespace Orm::SchemaNs
ColumnReferenceType &first();
/*! Set the starting value of an auto-incrementing field (MySQL / PostgreSQL). */
ColumnReferenceType &from(int startingValue);
+ /*! Create a SQL compliant identity column (PostgreSQL). */
+ ColumnReferenceType &generatedAs(const QString &expression);
/*! Specify that the column should be invisible to "SELECT *" (MySQL). */
ColumnReferenceType &invisible();
+ /*! Determine whether to use the geography (default, false) or
+ geometry type (PostgreSQL). */
+ ColumnReferenceType &isGeometry();
/*! Set the INTEGER column as UNSIGNED (MySQL). */
ColumnReferenceType &isUnsigned();
/*! Allow NULL values to be inserted into the column. */
ColumnReferenceType &nullable(bool value = true);
+ /*! The spatial reference identifier (SRID) of a geometry identifies the SRS
+ in which the geometry is defined (MySQL/PostgreSQL). */
+ ColumnReferenceType &srid(quint32 value);
/*! Set the starting value of an auto-incrementing field (MySQL/PostgreSQL). */
ColumnReferenceType &startingValue(int startingValue);
/*! Create a stored generated column (MySQL/PostgreSQL/SQLite). */
@@ -137,6 +147,15 @@ namespace Orm::SchemaNs
return columnReference();
}
+ template
+ typename ColumnDefinitionReference::ColumnReferenceType &
+ ColumnDefinitionReference::always()
+ {
+ m_columnDefinition.get().always = true;
+
+ return columnReference();
+ }
+
template
typename ColumnDefinitionReference::ColumnReferenceType &
ColumnDefinitionReference::autoIncrement()
@@ -200,6 +219,15 @@ namespace Orm::SchemaNs
return columnReference();
}
+ template
+ typename ColumnDefinitionReference::ColumnReferenceType &
+ ColumnDefinitionReference::generatedAs(const QString &expression)
+ {
+ m_columnDefinition.get().generatedAs = expression;
+
+ return columnReference();
+ }
+
template
typename ColumnDefinitionReference::ColumnReferenceType &
ColumnDefinitionReference::invisible()
@@ -209,6 +237,15 @@ namespace Orm::SchemaNs
return columnReference();
}
+ template
+ typename ColumnDefinitionReference::ColumnReferenceType &
+ ColumnDefinitionReference::isGeometry()
+ {
+ m_columnDefinition.get().isGeometry = true;
+
+ return columnReference();
+ }
+
template
typename ColumnDefinitionReference::ColumnReferenceType &
ColumnDefinitionReference::isUnsigned()
@@ -236,6 +273,15 @@ namespace Orm::SchemaNs
return columnReference();
}
+ template
+ typename ColumnDefinitionReference::ColumnReferenceType &
+ ColumnDefinitionReference::srid(const quint32 value)
+ {
+ m_columnDefinition.get().srid = value;
+
+ return columnReference();
+ }
+
template
typename ColumnDefinitionReference::ColumnReferenceType &
ColumnDefinitionReference::storedAs(const QString &expression)
diff --git a/include/orm/schema/foreignkeydefinitionreference.hpp b/include/orm/schema/foreignkeydefinitionreference.hpp
index 881a2dcb1..ee476d06f 100644
--- a/include/orm/schema/foreignkeydefinitionreference.hpp
+++ b/include/orm/schema/foreignkeydefinitionreference.hpp
@@ -41,6 +41,15 @@ namespace Orm::SchemaNs
/*! Add an ON UPDATE action. */
ForeignKeyDefinitionReference &onUpdate(const QString &action);
+ /*! Set the foreign key as deferrable (PostgreSQL). */
+ ForeignKeyDefinitionReference &deferrable(bool value = true);
+ /*! Set the default time to check the constraint (PostgreSQL). */
+ ForeignKeyDefinitionReference &initiallyImmediate(bool value = true);
+ /*! Set skip check that all existing rows in the table satisfy the new
+ constraint (PostgreSQL). */
+ ForeignKeyDefinitionReference ¬Valid(bool value = true);
+
+ /* Shortcuts */
/*! Indicate that updates should cascade. */
ForeignKeyDefinitionReference &cascadeOnUpdate();
/*! Indicate that updates should be restricted. */
diff --git a/include/orm/schema/grammars/mysqlschemagrammar.hpp b/include/orm/schema/grammars/mysqlschemagrammar.hpp
index c96f29d5e..0594c165f 100644
--- a/include/orm/schema/grammars/mysqlschemagrammar.hpp
+++ b/include/orm/schema/grammars/mysqlschemagrammar.hpp
@@ -45,9 +45,11 @@ namespace Grammars
QString compileDropAllViews(const QVector &views) const override;
/*! Compile the SQL needed to retrieve all table names. */
- QString compileGetAllTables() const override;
+ QString compileGetAllTables(
+ const QVector &databases = {}) const override;
/*! Compile the SQL needed to retrieve all view names. */
- QString compileGetAllViews() const override;
+ QString compileGetAllViews(
+ const QVector &databases = {}) const override;
/*! Compile the command to enable foreign key constraints. */
QString compileEnableForeignKeyConstraints() const override;
diff --git a/include/orm/schema/grammars/postgresschemagrammar.hpp b/include/orm/schema/grammars/postgresschemagrammar.hpp
index c47b9d78c..8401e161c 100644
--- a/include/orm/schema/grammars/postgresschemagrammar.hpp
+++ b/include/orm/schema/grammars/postgresschemagrammar.hpp
@@ -9,7 +9,14 @@ TINY_SYSTEM_HEADER
TINYORM_BEGIN_COMMON_NAMESPACE
-namespace Orm::SchemaNs::Grammars
+namespace Orm::SchemaNs
+{
+ class BasicCommand;
+ class CommentCommand;
+ class DropColumnsCommand;
+ class RenameCommand;
+
+namespace Grammars
{
/*! PostgreSql schemma grammar. */
@@ -27,20 +34,110 @@ namespace Orm::SchemaNs::Grammars
inline bool supportsSchemaTransactions() const noexcept override;
/* 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 QVector &databases = {}) const override;
+ /*! Compile the SQL needed to retrieve all view names. */
+ QString compileGetAllViews(
+ const QVector &databases = {}) 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;
+
+ /*! Compile a drop table command. */
+ QVector compileDrop(const Blueprint &blueprint,
+ const BasicCommand &command) const;
+ /*! Compile a drop table (if exists) command. */
+ QVector compileDropIfExists(const Blueprint &blueprint,
+ const BasicCommand &command) const;
+
+ /*! Compile a rename table command. */
+ QVector compileRename(const Blueprint &blueprint,
+ const RenameCommand &command) const;
+
+ /*! Compile an add column command. */
+ QVector compileAdd(const Blueprint &blueprint,
+ const BasicCommand &command) const;
+ /*! Compile a drop column command. */
+ QVector compileDropColumn(const Blueprint &blueprint,
+ const DropColumnsCommand &command) const;
+ /*! Compile a rename column command. */
+ QVector compileRenameColumn(const Blueprint &blueprint,
+ const RenameCommand &command) const;
+
+ /*! Compile a primary key command. */
+ QVector compilePrimary(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+ /*! Compile a unique key command. */
+ QVector compileUnique(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+ /*! Compile a plain index key command. */
+ QVector compileIndex(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+ /*! Compile a fulltext index key command. */
+ QVector compileFullText(const Blueprint &blueprint,
+ const IndexCommand &command) const override;
+ /*! Compile a spatial index key command. */
+ QVector compileSpatialIndex(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+
/*! Compile a foreign key command. */
QVector
compileForeign(const Blueprint &blueprint,
const ForeignKeyCommand &command) const override;
+ /*! Compile a drop primary key command. */
+ QVector compileDropPrimary(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+ /*! Compile a drop unique key command. */
+ inline QVector compileDropUnique(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+ /*! Compile a drop index command. */
+ QVector compileDropIndex(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+ /*! Compile a drop fulltext index command. */
+ inline QVector
+ compileDropFullText(const Blueprint &blueprint,
+ const IndexCommand &command) const override;
+ /*! Compile a drop spatial index command. */
+ inline QVector
+ compileDropSpatialIndex(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+
+ /*! Compile a drop foreign key command. */
+ inline QVector compileDropForeign(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+
+ /*! Compile a rename index command. */
+ QVector compileRenameIndex(const Blueprint &blueprint,
+ const RenameCommand &command) const;
+
+ /*! Compile a comment command. */
+ QVector compileComment(const Blueprint &blueprint,
+ const CommentCommand &command) const;
+
/*! Run command's compile method and return SQL queries. */
QVector
invokeCompileMethod(const CommandDefinition &command,
@@ -48,12 +145,121 @@ namespace Orm::SchemaNs::Grammars
const Blueprint &blueprint) const override;
protected:
+ /*! Create the main create table clause. */
+ QString compileCreateTable(const Blueprint &blueprint) const;
/*! Add the column modifiers to the definition. */
QString addModifiers(QString &&sql,
const ColumnDefinition &column) const override;
+ /*! Compile the auto-incrementing column starting values. */
+ QVector
+ compileAutoIncrementStartingValues(const Blueprint &blueprint) const;
+
+ /*! Compile a drop unique key command. */
+ QVector compileDropConstraint(const Blueprint &blueprint,
+ const IndexCommand &command) const;
+
/*! Get the SQL for the column data type. */
QString getType(const ColumnDefinition &column) const override;
+
+ /*! Create the column definition for a generatable column. */
+ static QString generatableColumn(QString &&type, const ColumnDefinition &column);
+ /*! Format the column definition for a PostGIS spatial type. */
+ static QString formatPostGisType(QString &&type, const ColumnDefinition &column);
+
+ /*! 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 double type. */
+ QString typeReal(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 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 spatial MultiPolygonZ type. */
+ QString typeMultiPolygonZ(const ColumnDefinition &column) const;
+
+ /*! 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 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 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;
};
/* public */
@@ -63,7 +269,36 @@ namespace Orm::SchemaNs::Grammars
return true;
}
-} // namespace Orm::SchemaNs::Grammars
+ QVector
+ PostgresSchemaGrammar::compileDropUnique(const Blueprint &blueprint,
+ const IndexCommand &command) const
+ {
+ return compileDropConstraint(blueprint, command);
+ }
+
+ QVector
+ PostgresSchemaGrammar::compileDropFullText(
+ const Blueprint &blueprint, const IndexCommand &command) const
+ {
+ return compileDropIndex(blueprint, command);
+ }
+
+ QVector
+ PostgresSchemaGrammar::compileDropSpatialIndex(
+ const Blueprint &blueprint, const IndexCommand &command) const
+ {
+ return compileDropIndex(blueprint, command);
+ }
+
+ QVector
+ PostgresSchemaGrammar::compileDropForeign(
+ const Blueprint &blueprint, const IndexCommand &command) const
+ {
+ return compileDropConstraint(blueprint, command);
+ }
+
+} // namespace Grammars
+} // namespace Orm::SchemaNs
TINYORM_END_COMMON_NAMESPACE
diff --git a/include/orm/schema/grammars/schemagrammar.hpp b/include/orm/schema/grammars/schemagrammar.hpp
index 611901971..1123d9f9f 100644
--- a/include/orm/schema/grammars/schemagrammar.hpp
+++ b/include/orm/schema/grammars/schemagrammar.hpp
@@ -50,9 +50,9 @@ namespace Grammars
virtual QString compileDropAllViews(const QVector &views) const;
/*! Compile the SQL needed to retrieve all table names. */
- virtual QString compileGetAllTables() const;
+ virtual QString compileGetAllTables(const QVector &databases = {}) const;
/*! Compile the SQL needed to retrieve all view names. */
- virtual QString compileGetAllViews() const;
+ virtual QString compileGetAllViews(const QVector &databases = {}) const;
/*! Compile the command to enable foreign key constraints. */
virtual QString compileEnableForeignKeyConstraints() const = 0;
diff --git a/include/orm/schema/indexdefinitionreference.hpp b/include/orm/schema/indexdefinitionreference.hpp
new file mode 100644
index 000000000..2dca5a817
--- /dev/null
+++ b/include/orm/schema/indexdefinitionreference.hpp
@@ -0,0 +1,74 @@
+#pragma once
+#ifndef ORM_SCHEMA_INDEXDEFINITIONREFERENCE_HPP
+#define ORM_SCHEMA_INDEXDEFINITIONREFERENCE_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 Orm::SchemaNs
+{
+ class IndexCommand;
+
+ /*! Reference class to the ColumnDefinition, provides setters with a nice API
+ for the database index. */
+ class SHAREDLIB_EXPORT IndexDefinitionReference
+ {
+ public:
+ /*! Constructor. */
+ IndexDefinitionReference(IndexCommand &indexCommand); // NOLINT(google-explicit-constructor)
+ /*! Default destructor. */
+ inline ~IndexDefinitionReference() = default;
+
+ /*! Copy constructor. */
+ inline IndexDefinitionReference(
+ const IndexDefinitionReference &) = default;
+ /*! Move constructor. */
+ inline IndexDefinitionReference(
+ IndexDefinitionReference &&) noexcept = default;
+
+ /*! Deleted copy assignment operator. */
+ IndexDefinitionReference &
+ operator=(const IndexDefinitionReference &) = delete;
+ /*! Deleted move assignment operator. */
+ IndexDefinitionReference &
+ operator=(IndexDefinitionReference &&) noexcept = delete;
+
+ /*! Return the reference to underlying index command. */
+ constexpr const IndexCommand &get() const noexcept;
+ /*! Return the reference to underlying index command. */
+ constexpr operator const IndexCommand &() const noexcept; // NOLINT(google-explicit-constructor)
+
+ /*! Specify an algorithm for the index (MySQL/PostgreSQL). */
+ IndexDefinitionReference &algorithm(const QString &algorithm);
+ /*! Specify a language for the full text index (PostgreSQL). */
+ IndexDefinitionReference &language(const QString &language);
+
+ private:
+ /*! Reference to an index command definition. */
+ std::reference_wrapper m_indexCommand;
+ };
+
+ /* public */
+
+ constexpr const IndexCommand &IndexDefinitionReference::get() const noexcept
+ {
+ return m_indexCommand.get();
+ }
+
+ constexpr IndexDefinitionReference::operator const IndexCommand &() const noexcept
+ {
+ return get();
+ }
+
+} // namespace Orm::SchemaNs
+
+TINYORM_END_COMMON_NAMESPACE
+
+#endif // ORM_SCHEMA_INDEXDEFINITIONREFERENCE_HPP
diff --git a/include/orm/schema/postgresschemabuilder.hpp b/include/orm/schema/postgresschemabuilder.hpp
index 30d4f1887..4114ab58a 100644
--- a/include/orm/schema/postgresschemabuilder.hpp
+++ b/include/orm/schema/postgresschemabuilder.hpp
@@ -24,12 +24,30 @@ namespace Orm::SchemaNs
/*! Virtual destructor. */
inline ~PostgresSchemaBuilder() 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;
+
protected:
/*! Parse the table name and extract the schema and table. */
- std::pair
+ std::tuple
parseSchemaAndTable(const QString &table) const;
};
diff --git a/include/orm/schema/schemaconstants_extern.hpp b/include/orm/schema/schemaconstants_extern.hpp
index bca3f9176..91351b583 100644
--- a/include/orm/schema/schemaconstants_extern.hpp
+++ b/include/orm/schema/schemaconstants_extern.hpp
@@ -35,6 +35,9 @@ namespace Constants
SHAREDLIB_EXPORT extern const QString DropForeign;
SHAREDLIB_EXPORT extern const QString RenameIndex;
+ // PostgreSQL specific command
+ SHAREDLIB_EXPORT extern const QString Comment;
+
// Indexes
SHAREDLIB_EXPORT extern const QString Primary;
SHAREDLIB_EXPORT extern const QString Unique;
diff --git a/include/orm/schema/schemaconstants_inline.hpp b/include/orm/schema/schemaconstants_inline.hpp
index 348e6b983..87e3c105e 100644
--- a/include/orm/schema/schemaconstants_inline.hpp
+++ b/include/orm/schema/schemaconstants_inline.hpp
@@ -34,6 +34,9 @@ namespace Constants
inline const QString DropForeign = QStringLiteral("dropForeign");
inline const QString RenameIndex = QStringLiteral("renameIndex");
+ // PostgreSQL specific command
+ inline const QString Comment = QStringLiteral("comment");
+
// Indexes
inline const QString Primary = QStringLiteral("primary");
inline const QString Unique = QStringLiteral("unique");
diff --git a/include/orm/schema/schematypes.hpp b/include/orm/schema/schematypes.hpp
index 2836aafd9..ae08afc44 100644
--- a/include/orm/schema/schematypes.hpp
+++ b/include/orm/schema/schematypes.hpp
@@ -74,6 +74,32 @@ namespace Orm::SchemaNs
std::optional value;
};
+ /* Common for the invokeCompileMethod() related methods */
+
+ /*! Concept for a member function. */
+ template
+ concept IsMemFun = std::is_member_function_pointer_v>;
+
+ /*! Function signature. */
+ template
+ struct FunctionSignature;
+
+ /*! Function signature, a member function specialization. */
+ template
+ struct FunctionSignature
+ {
+ using type = std::tuple;
+ };
+
+ /*! Helper function to obtain function types as std::tuple. */
+ template
+ auto argumentTypes(M &&) -> typename FunctionSignature>::type;
+
+ /*! Helper function to obtain function parameter type at I position
+ from std::tuple. */
+ template
+ auto argumentType(M &&method) -> decltype (std::get(argumentTypes(method)));
+
} // namespace Orm::SchemaNs
TINYORM_END_COMMON_NAMESPACE
diff --git a/src/orm/connectors/connectionfactory.cpp b/src/orm/connectors/connectionfactory.cpp
index fc2e26bb8..d347ecb71 100644
--- a/src/orm/connectors/connectionfactory.cpp
+++ b/src/orm/connectors/connectionfactory.cpp
@@ -55,10 +55,10 @@ ConnectionFactory::parseConfig(QVariantHash &config, const QString &name) const
normalizeDriverName(config);
if (!config.contains(database_))
- config.insert(database_, QString(""));
+ config.insert(database_, EMPTY);
if (!config.contains(prefix_))
- config.insert(prefix_, QString(""));
+ config.insert(prefix_, EMPTY);
if (!config.contains(options_))
config.insert(options_, QVariantHash());
@@ -67,6 +67,9 @@ ConnectionFactory::parseConfig(QVariantHash &config, const QString &name) const
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
+ if (config[driver_] == QPSQL && !config.contains(dont_drop))
+ // spatial_ref_sys table is used by the PostGIS
+ config.insert(dont_drop, QStringList {QStringLiteral("spatial_ref_sys")});
return config;
}
@@ -74,7 +77,7 @@ ConnectionFactory::parseConfig(QVariantHash &config, const QString &name) const
void ConnectionFactory::normalizeDriverName(QVariantHash &config) const
{
if (!config.contains(driver_))
- config.insert(driver_, QString(""));
+ config.insert(driver_, EMPTY);
else {
auto &driver = config[driver_];
diff --git a/src/orm/constants_extern.cpp b/src/orm/constants_extern.cpp
index 58da11b2f..c0e77670b 100644
--- a/src/orm/constants_extern.cpp
+++ b/src/orm/constants_extern.cpp
@@ -41,6 +41,7 @@ namespace Orm::Constants
const QString SPACE_IN = QStringLiteral("%1 %2");
const QString NOSPACE = QStringLiteral("%1%2");
const QString EMPTY = QLatin1String("");
+ const QString text_ = QStringLiteral("text");
const QString QMYSQL = QStringLiteral("QMYSQL");
const QString QPSQL = QStringLiteral("QPSQL");
@@ -63,6 +64,7 @@ namespace Orm::Constants
const QString options_ = QStringLiteral("options");
const QString strict_ = QStringLiteral("strict");
const QString engine_ = QStringLiteral("engine");
+ const QString dont_drop = QStringLiteral("dont_drop");
const QString isolation_level = QStringLiteral("isolation_level");
const QString foreign_key_constraints = QStringLiteral("foreign_key_constraints");
@@ -83,8 +85,10 @@ namespace Orm::Constants
const QString UTF8MB4 = QStringLiteral("utf8mb4");
const QString InnoDB = QStringLiteral("InnoDB");
const QString MyISAM = QStringLiteral("MyISAM");
+ const QString postgres_ = QStringLiteral("postgres");
const QString UTF8MB40900aici = QStringLiteral("utf8mb4_0900_ai_ci");
+ const QString UcsBasic = QStringLiteral("ucs_basic");
const QString NotImplemented = QStringLiteral("Not implemented :/.");
// Comparison/logical/search operators
diff --git a/src/orm/query/processors/mysqlprocessor.cpp b/src/orm/query/processors/mysqlprocessor.cpp
index df0787dd0..de8f31414 100644
--- a/src/orm/query/processors/mysqlprocessor.cpp
+++ b/src/orm/query/processors/mysqlprocessor.cpp
@@ -8,6 +8,7 @@ TINYORM_BEGIN_COMMON_NAMESPACE
namespace Orm::Query::Processors
{
+// CUR schema, duplicate silverqx
QStringList MySqlProcessor::processColumnListing(QSqlQuery &query) const
{
QStringList columns;
diff --git a/src/orm/schema/blueprint.cpp b/src/orm/schema/blueprint.cpp
index 421230249..c31ecef4b 100644
--- a/src/orm/schema/blueprint.cpp
+++ b/src/orm/schema/blueprint.cpp
@@ -5,6 +5,7 @@
#include
#include "orm/databaseconnection.hpp"
+#include "orm/schema/grammars/postgresschemagrammar.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -34,7 +35,10 @@ void Blueprint::build(DatabaseConnection &connection, const SchemaGrammar &gramm
{
// TODO clazy, old clazy check range-loop, remove after ugprade to newer clazy 1.11, it was divided to two checks in clazy 1.11 silverqx
for (const auto &queryString : toSql(connection, grammar)) // clazy:exclude=range-loop,range-loop-detach
- connection.statement(queryString);
+ /* All compile methods in the SchemaBuilders are unprepared, eg. the PostgreSQL
+ driver even doesn't allow to send DDL commands as prepared statements,
+ MySQL driver supports to send DDL commands as prepared statements. */
+ connection.unprepared(queryString);
}
QVector Blueprint::toSql(const DatabaseConnection &connection,
@@ -110,42 +114,42 @@ const RenameCommand &Blueprint::renameColumn(const QString &from, const QString
return addCommand({{}, RenameColumn, from, to});
}
-const IndexCommand &
+IndexDefinitionReference
Blueprint::primary(const QVector &columns, const QString &indexName,
const QString &algorithm)
{
return indexCommand(Primary, columns, indexName, algorithm);
}
-const IndexCommand &
+IndexDefinitionReference
Blueprint::unique(const QVector &columns, const QString &indexName,
const QString &algorithm)
{
return indexCommand(Unique, columns, indexName, algorithm);
}
-const IndexCommand &
+IndexDefinitionReference
Blueprint::index(const QVector &columns, const QString &indexName,
const QString &algorithm)
{
return indexCommand(Index, columns, indexName, algorithm); // NOLINT(readability-suspicious-call-argument)
}
-const IndexCommand &
+IndexDefinitionReference
Blueprint::fullText(const QVector &columns, const QString &indexName,
- const QString &algorithm)
+ const QString &algorithm, const QString &language)
{
- return indexCommand(Fulltext, columns, indexName, algorithm);
+ return indexCommand(Fulltext, columns, indexName, algorithm, language);
}
// 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
-const IndexCommand &
+IndexDefinitionReference
Blueprint::spatialIndex(const QVector &columns, const QString &indexName)
{
return indexCommand(SpatialIndex, columns, indexName);
}
-const IndexCommand &
+IndexDefinitionReference
Blueprint::rawIndex(const Expression &expression, const QString &indexName)
{
return addCommand({{}, Index, indexName, {expression}});
@@ -589,7 +593,7 @@ ColumnDefinitionReference<> Blueprint::addColumnDefinition(ColumnDefinition &&de
return definitionRef;
}
-void Blueprint::addImpliedCommands(const SchemaGrammar &/*unused*/)
+void Blueprint::addImpliedCommands(const SchemaGrammar &grammar)
{
if (!getAddedColumns().isEmpty() && !creating())
m_commands.emplace_front(createCommand({{}, Add}));
@@ -599,7 +603,7 @@ void Blueprint::addImpliedCommands(const SchemaGrammar &/*unused*/)
addFluentIndexes();
-// addFluentCommands(grammar);
+ addFluentCommands(grammar);
}
void Blueprint::addFluentIndexes()
@@ -659,9 +663,22 @@ void Blueprint::addFluentIndexes()
}
}
-const IndexCommand &
+void Blueprint::addFluentCommands(const SchemaGrammar &grammar)
+{
+ /* The PostgreSQL grammar is only one that has a fluent command, it is
+ the Comment command. */
+ if (dynamic_cast(&grammar) == nullptr)
+ return;
+
+ for (const auto &column : m_columns)
+ if (!column.comment.isEmpty())
+ addCommand({{}, Comment, column.name, column.comment});
+}
+
+IndexDefinitionReference
Blueprint::indexCommand(const QString &type, const QVector &columns,
- const QString &indexName, const QString &algorithm)
+ const QString &indexName, const QString &algorithm,
+ const QString &language)
{
/* 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
@@ -672,13 +689,16 @@ Blueprint::indexCommand(const QString &type, const QVector &columns,
indexName.isEmpty() ? createIndexName(type, columns)
: indexName,
QVector(columns.cbegin(), columns.cend()),
- algorithm});
+ algorithm, language});
}
const IndexCommand &
Blueprint::dropIndexCommand(const QString &command, const QString &type,
const QVector &columns)
{
+ /* Although it looks weird so these two methods works, I tested them, believe me,
+ the dropPrimary("") drops the primary key on both MySQL and PostgreSQL. */
+
// Used by dropPrimary("")
if (columns.isEmpty())
return indexCommand(command, {}, {});
diff --git a/src/orm/schema/foreignkeydefinitionreference.cpp b/src/orm/schema/foreignkeydefinitionreference.cpp
index 48b008a59..17fdab8b5 100644
--- a/src/orm/schema/foreignkeydefinitionreference.cpp
+++ b/src/orm/schema/foreignkeydefinitionreference.cpp
@@ -46,6 +46,31 @@ ForeignKeyDefinitionReference::onUpdate(const QString &action)
return *this;
}
+ForeignKeyDefinitionReference &
+ForeignKeyDefinitionReference::deferrable(const bool value)
+{
+ m_foreignKeyCommandDefinition.get().deferrable = value;
+
+ return *this;
+}
+
+ForeignKeyDefinitionReference &
+ForeignKeyDefinitionReference::initiallyImmediate(const bool value)
+{
+ m_foreignKeyCommandDefinition.get().initiallyImmediate = value;
+
+ return *this;
+}
+
+ForeignKeyDefinitionReference &ForeignKeyDefinitionReference::notValid(const bool value)
+{
+ m_foreignKeyCommandDefinition.get().notValid = value;
+
+ return *this;
+}
+
+/* Shortcuts */
+
ForeignKeyDefinitionReference &ForeignKeyDefinitionReference::cascadeOnUpdate()
{
return onUpdate(Cascade);
diff --git a/src/orm/schema/grammars/mysqlschemagrammar.cpp b/src/orm/schema/grammars/mysqlschemagrammar.cpp
index 15b8615b7..dac331316 100644
--- a/src/orm/schema/grammars/mysqlschemagrammar.cpp
+++ b/src/orm/schema/grammars/mysqlschemagrammar.cpp
@@ -40,12 +40,14 @@ QString MySqlSchemaGrammar::compileDropAllViews(const QVector &views) c
return QStringLiteral("drop view %1").arg(columnize(views));
}
-QString MySqlSchemaGrammar::compileGetAllTables() const
+QString
+MySqlSchemaGrammar::compileGetAllTables(const QVector &/*unused*/) const
{
return QStringLiteral("SHOW FULL TABLES WHERE table_type = 'BASE TABLE';");
}
-QString MySqlSchemaGrammar::compileGetAllViews() const
+QString
+MySqlSchemaGrammar::compileGetAllViews(const QVector &/*unused*/) const
{
return QStringLiteral("SHOW FULL TABLES WHERE table_type = 'VIEW';");
}
@@ -77,8 +79,6 @@ QString MySqlSchemaGrammar::compileColumnListing(const QString &/*unused*/) cons
/* Compile methods for commands */
-/* public */
-
QVector
MySqlSchemaGrammar::compileCreate(const Blueprint &blueprint,
const DatabaseConnection &connection) const
@@ -97,7 +97,11 @@ MySqlSchemaGrammar::compileCreate(const Blueprint &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};
+ QVector sql;
+ sql.reserve(2);
+
+ sql << std::move(sqlCreateTable);
+
if (!autoIncrementStartingValues.isEmpty())
sql << std::move(autoIncrementStartingValues);
@@ -137,7 +141,11 @@ QVector MySqlSchemaGrammar::compileAdd(const Blueprint &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 {sqlAlterTable};
+ QVector sql;
+ sql.reserve(2);
+
+ sql << std::move(sqlAlterTable);
+
if (!autoIncrementStartingValues.isEmpty())
sql << std::move(autoIncrementStartingValues);
@@ -235,34 +243,6 @@ MySqlSchemaGrammar::compileRenameIndex(const Blueprint &blueprint,
BaseGrammar::wrap(command.to))};
}
-namespace
-{
- /*! Concept for a member function. */
- template
- concept IsMemFun = std::is_member_function_pointer_v>;
-
- /*! Function signature. */
- template
- struct FunctionSignature;
-
- /*! Function signature, a member function specialization. */
- template
- struct FunctionSignature
- {
- using type = std::tuple;
- };
-
- /*! Helper function to obtain function types as std::tuple. */
- template
- auto argumentTypes(M &&) -> typename FunctionSignature>::type;
-
- /*! Helper function to obtain function parameter type at I position
- from std::tuple. */
- template
- auto argumentType(M &&method) -> decltype (std::get(argumentTypes(method)));
-
-} // namespace
-
QVector
MySqlSchemaGrammar::invokeCompileMethod(const CommandDefinition &command,
const DatabaseConnection &connection,
@@ -331,7 +311,8 @@ MySqlSchemaGrammar::invokeCompileMethod(const CommandDefinition &command,
Q_ASSERT_X(cached.contains(name),
qUtf8Printable(__tiny_func__),
- QStringLiteral("Compile methods map doesn't contain the '%1' key.")
+ QStringLiteral("Compile methods map doesn't contain the '%1' key "
+ "(unsupported command).")
.arg(name)
.toUtf8().constData());
@@ -627,7 +608,7 @@ QString MySqlSchemaGrammar::typeTinyText(const ColumnDefinition &/*unused*/) con
QString MySqlSchemaGrammar::typeText(const ColumnDefinition &/*unused*/) const
{
- return QStringLiteral("text");
+ return text_;
}
QString MySqlSchemaGrammar::typeMediumText(const ColumnDefinition &/*unused*/) const
@@ -752,6 +733,9 @@ QString MySqlSchemaGrammar::typeTimeTz(const ColumnDefinition &column) const
QString MySqlSchemaGrammar::typeTimestamp(const ColumnDefinition &column) const
{
+ /* The behavior if the precision is omitted (or 0 of course):
+ >0 is ok so the default will be: timestamp and the MySQL default is 0 if omitted.
+ The same as in the PostgreSQL grammar. */
auto columnType = column.precision > 0
? QStringLiteral("timestamp(%1)").arg(column.precision)
: QStringLiteral("timestamp");
diff --git a/src/orm/schema/grammars/postgresschemagrammar.cpp b/src/orm/schema/grammars/postgresschemagrammar.cpp
index fd3aaf58e..051e7f237 100644
--- a/src/orm/schema/grammars/postgresschemagrammar.cpp
+++ b/src/orm/schema/grammars/postgresschemagrammar.cpp
@@ -1,6 +1,13 @@
#include "orm/schema/grammars/postgresschemagrammar.hpp"
-#include "orm/exceptions/runtimeerror.hpp"
+#include
+
+#include "orm/databaseconnection.hpp"
+#include "orm/macros/threadlocal.hpp"
+#include "orm/utils/container.hpp"
+#include "orm/utils/type.hpp"
+
+using ContainerUtils = Orm::Utils::Container;
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -11,14 +18,62 @@ namespace Orm::SchemaNs::Grammars
/* Compile methods for the SchemaBuilder */
+QString PostgresSchemaGrammar::compileCreateDatabase(
+ const QString &name, DatabaseConnection &connection) const
+{
+ return QStringLiteral("create database %1 encoding %2")
+ .arg(wrapValue(name),
+ wrapValue(connection.getConfig(charset_).value()));
+}
+
+QString PostgresSchemaGrammar::compileDropDatabaseIfExists(const QString &name) const
+{
+ // CUR schema, duplicate silverqx
+ return QStringLiteral("drop database if exists %1").arg(wrapValue(name));
+}
+
+QString PostgresSchemaGrammar::compileDropAllTables(const QVector &tables) const
+{
+ return QStringLiteral("drop table %1 cascade").arg(columnize(tables));
+}
+
+QString PostgresSchemaGrammar::compileDropAllViews(const QVector &views) const
+{
+ return QStringLiteral("drop view %1 cascade").arg(columnize(views));
+}
+
+QString
+PostgresSchemaGrammar::compileGetAllTables(const QVector &databases) const
+{
+ return QStringLiteral("select tablename from pg_catalog.pg_tables "
+ "where schemaname in (%1)")
+ .arg(quoteString(std::move(databases)));
+}
+
+QString
+PostgresSchemaGrammar::compileGetAllViews(const QVector &databases) const
+{
+ return QStringLiteral("select viewname from pg_catalog.pg_views "
+ "where schemaname in (%1)")
+ .arg(quoteString(std::move(databases)));
+}
+
QString PostgresSchemaGrammar::compileEnableForeignKeyConstraints() const
{
- throw Exceptions::RuntimeError(NotImplemented);
+ return QStringLiteral("SET CONSTRAINTS ALL IMMEDIATE;");
}
QString PostgresSchemaGrammar::compileDisableForeignKeyConstraints() const
{
- throw Exceptions::RuntimeError(NotImplemented);
+ return QStringLiteral("SET CONSTRAINTS ALL DEFERRED;");
+}
+
+QString PostgresSchemaGrammar::compileTableExists() const
+{
+ return QStringLiteral("select * "
+ "from information_schema.tables "
+ "where table_schema = ? and table_name = ? and "
+ "table_type = 'BASE TABLE'");
}
QString PostgresSchemaGrammar::compileColumnListing(const QString &/*unused*/) const
@@ -31,29 +86,823 @@ QString PostgresSchemaGrammar::compileColumnListing(const QString &/*unused*/) c
/* Compile methods for commands */
QVector
-PostgresSchemaGrammar::compileForeign(const Blueprint &/*unused*/,
- const ForeignKeyCommand &/*unused*/) const
+PostgresSchemaGrammar::compileCreate(const Blueprint &blueprint) const
{
- throw Exceptions::RuntimeError(NotImplemented);
+ // Primary SQL query for create table
+ auto sqlCreateTable = compileCreateTable(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;
+ sql.reserve(2);
+
+ sql << std::move(sqlCreateTable);
+
+ if (!autoIncrementStartingValues.isEmpty())
+ sql << std::move(autoIncrementStartingValues);
+
+ return sql;
}
QVector
-PostgresSchemaGrammar::invokeCompileMethod(const CommandDefinition &/*unused*/,
+PostgresSchemaGrammar::compileDrop(const Blueprint &blueprint,
+ const BasicCommand &/*unused*/) const
+{
+ // CUR schema, duplicate silverqx
+ return {QStringLiteral("drop table %1").arg(wrapTable(blueprint))};
+}
+
+QVector
+PostgresSchemaGrammar::compileDropIfExists(const Blueprint &blueprint,
+ const BasicCommand &/*unused*/) const
+{
+ // CUR schema, duplicate silverqx
+ return {QStringLiteral("drop table if exists %1").arg(wrapTable(blueprint))};
+}
+
+QVector
+PostgresSchemaGrammar::compileRename(const Blueprint &blueprint,
+ const RenameCommand &command) const
+{
+ return {QStringLiteral("alter table %1 rename to %2")
+ .arg(wrapTable(blueprint), BaseGrammar::wrap(command.to))};
+}
+
+QVector
+PostgresSchemaGrammar::compileAdd(const Blueprint &blueprint,
+ const BasicCommand &/*unused*/) const
+{
+ // CUR schema, duplicate, difference only in "add column" silverqx
+ auto columns = prefixArray("add column", 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;
+ sql.reserve(2);
+
+ sql << std::move(sqlAlterTable);
+
+ if (!autoIncrementStartingValues.isEmpty())
+ sql << std::move(autoIncrementStartingValues);
+
+ return sql;
+}
+
+QVector
+PostgresSchemaGrammar::compileDropColumn(const Blueprint &blueprint,
+ const DropColumnsCommand &command) const
+{
+ // CUR schema, duplicate, difference only in "drop column" silverqx
+ return {QStringLiteral("alter table %1 %2")
+ .arg(wrapTable(blueprint),
+ columnizeWithoutWrap(prefixArray("drop column",
+ wrapArray(command.columns))))};
+}
+
+QVector
+PostgresSchemaGrammar::compileRenameColumn(const Blueprint &blueprint,
+ const RenameCommand &command) const
+{
+ // CUR schema, duplicate silverqx
+ return {QStringLiteral("alter table %1 rename column %2 to %3")
+ .arg(wrapTable(blueprint), BaseGrammar::wrap(command.from),
+ BaseGrammar::wrap(command.to))};
+}
+
+QVector
+PostgresSchemaGrammar::compilePrimary(const Blueprint &blueprint,
+ const IndexCommand &command) const
+{
+ return {QStringLiteral("alter table %1 add primary key (%2)")
+ .arg(wrapTable(blueprint), columnize(command.columns))};
+}
+
+QVector
+PostgresSchemaGrammar::compileUnique(const Blueprint &blueprint,
+ const IndexCommand &command) const
+{
+ return {QStringLiteral("alter table %1 add constraint %2 unique (%3)")
+ .arg(wrapTable(blueprint), BaseGrammar::wrap(command.index),
+ columnize(command.columns))};
+}
+
+QVector
+PostgresSchemaGrammar::compileIndex(const Blueprint &blueprint,
+ const IndexCommand &command) const
+{
+ auto algorithm = command.algorithm.isEmpty()
+ ? EMPTY
+ : QStringLiteral(" using %1").arg(command.algorithm);
+
+ return {QStringLiteral("create index %1 on %2%3 (%4)")
+ .arg(BaseGrammar::wrap(command.index), wrapTable(blueprint),
+ std::move(algorithm), columnize(command.columns))};
+}
+
+QVector
+PostgresSchemaGrammar::compileFullText(const Blueprint &blueprint,
+ const IndexCommand &command) const
+{
+ static const auto TsVectorTmpl = QStringLiteral("to_tsvector(%1, %2)");
+
+ const auto language = command.language.isEmpty() ? QStringLiteral("english")
+ : command.language;
+
+ const auto columns =
+ ranges::views::transform(command.columns,
+ [this, &language, &TsVectorTmpl = TsVectorTmpl]
+ (const auto &column)
+ {
+ return TsVectorTmpl.arg(quoteString(language), BaseGrammar::wrap(column));
+ })
+ | ranges::to>();
+
+ /* Double (()) described here, simply it's a expression not the column name:
+ https://www.postgresql.org/docs/10/indexes-expressional.html */
+ return {QStringLiteral("create index %1 on %2 using gin ((%3))")
+ .arg(BaseGrammar::wrap(command.index),
+ wrapTable(blueprint),
+ ContainerUtils::join(columns, QStringLiteral(" || ")))};
+}
+
+QVector
+PostgresSchemaGrammar::compileSpatialIndex(const Blueprint &blueprint,
+ const IndexCommand &command) const
+{
+ const_cast(command).algorithm = QStringLiteral("gist");
+
+ return compileIndex(blueprint, command);
+}
+
+QVector
+PostgresSchemaGrammar::compileForeign(const Blueprint &blueprint,
+ const ForeignKeyCommand &command) const
+{
+ auto sqlCommands = SchemaGrammar::compileForeign(blueprint, command);
+
+ Q_ASSERT(sqlCommands.size() == 1);
+
+ auto &sql = sqlCommands[0];
+
+ const auto isDeferrable = command.deferrable.has_value();
+
+ if (isDeferrable)
+ sql.append(*command.deferrable ? QStringLiteral(" deferrable")
+ : QStringLiteral(" not deferrable"));
+
+ if (isDeferrable && *command.deferrable && command.initiallyImmediate)
+ sql.append(*command.initiallyImmediate ? QStringLiteral(" initially immediate")
+ : QStringLiteral(" initially deferred"));
+
+ if (command.notValid && *command.notValid)
+ sql.append(QStringLiteral(" not valid"));
+
+ return sqlCommands;
+}
+
+QVector
+PostgresSchemaGrammar::compileDropPrimary(const Blueprint &blueprint,
+ const IndexCommand &/*unused*/) const
+{
+ auto index = BaseGrammar::wrap(QStringLiteral("%1_pkey").arg(blueprint.getTable()));
+
+ return {QStringLiteral("alter table %1 drop constraint %2")
+ .arg(wrapTable(blueprint), std::move(index))};
+}
+
+QVector
+PostgresSchemaGrammar::compileDropIndex(const Blueprint &/*unused*/,
+ const IndexCommand &command) const
+{
+ return {QStringLiteral("drop index %1").arg(BaseGrammar::wrap(command.index))};
+}
+
+QVector
+PostgresSchemaGrammar::compileRenameIndex(const Blueprint &/*unused*/,
+ const RenameCommand &command) const
+{
+ return {QStringLiteral("alter index %1 rename to %2")
+ .arg(BaseGrammar::wrap(command.from), BaseGrammar::wrap(command.to))};
+}
+
+QVector
+PostgresSchemaGrammar::compileComment(const Blueprint &blueprint,
+ const CommentCommand &command) const
+{
+ auto comment = command.comment;
+ // Escape single quotes
+ comment.replace(QChar('\''), QStringLiteral("''"));
+
+ return {QStringLiteral("comment on column %1.%2 is %3")
+ .arg(wrapTable(blueprint), BaseGrammar::wrap(command.column),
+ quoteString(comment))};
+}
+
+QVector
+PostgresSchemaGrammar::invokeCompileMethod(const CommandDefinition &command,
const DatabaseConnection &/*unused*/,
- const Blueprint &/*unused*/) const
+ const Blueprint &blueprint) const
{
- throw Exceptions::RuntimeError(NotImplemented);
+ // FUTURE concepts, somehow check that after reinterpret_cast<> is command_.name QString, i have tried but without success, I have added example to NOTES.txt silverqx
+ const auto &basicCommand = reinterpret_cast(command);
+ const auto &name = basicCommand.name;
+
+ if (name == Create)
+ return compileCreate(blueprint);
+
+ /*! Type for the compileXx() methods. */
+ using CompileMemFn =
+ std::function(
+ const PostgresSchemaGrammar &, const Blueprint &,
+ const CommandDefinition &)>;
+
+ const auto bind = [](auto &&compileMethod)
+ {
+ return [compileMethod = std::forward(compileMethod)]
+ (const PostgresSchemaGrammar &grammar, const Blueprint &blueprint_,
+ const CommandDefinition &command_) // clazy:exclude=function-args-by-value
+ {
+ /* Get type of a second parameter of compile method and cast to that type. */
+ const auto &castedCommand =
+ reinterpret_cast(compileMethod))>(command_);
+
+ return std::invoke(compileMethod, grammar, blueprint_, castedCommand);
+ };
+ };
+
+ // 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 {
+ {Add, bind(&PostgresSchemaGrammar::compileAdd)},
+ {Rename, bind(&PostgresSchemaGrammar::compileRename)},
+ {Drop, bind(&PostgresSchemaGrammar::compileDrop)},
+ {DropIfExists, bind(&PostgresSchemaGrammar::compileDropIfExists)},
+
+ {DropColumn, bind(&PostgresSchemaGrammar::compileDropColumn)},
+ {RenameColumn, bind(&PostgresSchemaGrammar::compileRenameColumn)},
+
+ {Primary, bind(&PostgresSchemaGrammar::compilePrimary)},
+ {Unique, bind(&PostgresSchemaGrammar::compileUnique)},
+ {Index, bind(&PostgresSchemaGrammar::compileIndex)},
+ {Fulltext, bind(&PostgresSchemaGrammar::compileFullText)},
+ {SpatialIndex, bind(&PostgresSchemaGrammar::compileSpatialIndex)},
+ {Foreign, bind(&PostgresSchemaGrammar::compileForeign)},
+
+ {DropPrimary, bind(&PostgresSchemaGrammar::compileDropPrimary)},
+ {DropUnique, bind(&PostgresSchemaGrammar::compileDropUnique)},
+ {DropIndex, bind(&PostgresSchemaGrammar::compileDropIndex)},
+ {DropFullText, bind(&PostgresSchemaGrammar::compileDropFullText)},
+ {DropSpatialIndex, bind(&PostgresSchemaGrammar::compileDropSpatialIndex)},
+ {DropForeign, bind(&PostgresSchemaGrammar::compileDropForeign)},
+
+ {RenameIndex, bind(&PostgresSchemaGrammar::compileRenameIndex)},
+ // PostgreSQL specific
+ {Comment, bind(&PostgresSchemaGrammar::compileComment)},
+ };
+
+ Q_ASSERT_X(cached.contains(name),
+ qUtf8Printable(__tiny_func__),
+ QStringLiteral("Compile methods map doesn't contain the '%1' key "
+ "(unsupported command).")
+ .arg(name)
+ .toUtf8().constData());
+
+ return std::invoke(cached.at(name), *this, blueprint, command);
}
-QString PostgresSchemaGrammar::addModifiers(QString &&/*unused*/,
- const ColumnDefinition &/*unused*/) const
+/* protected */
+
+// CUR schema, duplicate silverqx
+QString PostgresSchemaGrammar::compileCreateTable(const Blueprint &blueprint) const
{
- throw Exceptions::RuntimeError(NotImplemented);
+ return QStringLiteral("%1 table %2 (%3)")
+ .arg(blueprint.isTemporary() ? QStringLiteral("create temporary")
+ : Create,
+ wrapTable(blueprint),
+ columnizeWithoutWrap(getColumns(blueprint)))
+ .trimmed();
}
-QString PostgresSchemaGrammar::getType(const ColumnDefinition &/*unused*/) const
+QString PostgresSchemaGrammar::addModifiers(QString &&sql,
+ const ColumnDefinition &column) const
{
- throw Exceptions::RuntimeError(NotImplemented);
+ // CUR schema, should be T_THREAD_LOCAL? silverqx
+ constexpr static std::array modifierMethods {
+ &PostgresSchemaGrammar::modifyCollate, &PostgresSchemaGrammar::modifyIncrement,
+ &PostgresSchemaGrammar::modifyNullable, &PostgresSchemaGrammar::modifyDefault,
+ &PostgresSchemaGrammar::modifyVirtualAs, &PostgresSchemaGrammar::modifyStoredAs,
+ };
+
+ for (const auto method : modifierMethods)
+ sql.append(std::invoke(method, this, column));
+
+ return std::move(sql);
+}
+
+QVector
+PostgresSchemaGrammar::compileAutoIncrementStartingValues(
+ const Blueprint &blueprint) const
+{
+ const auto autoIncrementStartingValues = blueprint.autoIncrementStartingValues();
+
+ // Nothing to compile
+ if (autoIncrementStartingValues.isEmpty())
+ return {};
+
+ return autoIncrementStartingValues
+ | ranges::views::transform([&blueprint](const auto &startingValue)
+ -> QString
+ {
+ Q_ASSERT(startingValue.value);
+
+ return QStringLiteral(R"(alter sequence "%1_%2_seq" restart with %3)")
+ .arg(blueprint.getTable())
+ .arg(startingValue.columnName)
+ .arg(*startingValue.value);
+ })
+ | ranges::to>();
+}
+
+QVector
+PostgresSchemaGrammar::compileDropConstraint(const Blueprint &blueprint,
+ const IndexCommand &command) const
+{
+ return {QStringLiteral("alter table %1 drop constraint %2")
+ .arg(wrapTable(blueprint), BaseGrammar::wrap(command.index))};
+}
+
+QString PostgresSchemaGrammar::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
+PostgresSchemaGrammar::generatableColumn(QString &&type, const ColumnDefinition &column)
+{
+ if (!column.autoIncrement && column.generatedAs.isEmpty())
+ return std::move(type);
+
+ static const std::unordered_map GeneratableMap {
+ {QStringLiteral("smallint"), QStringLiteral("smallserial")},
+ {QStringLiteral("integer"), QStringLiteral("serial")},
+ {QStringLiteral("bigint"), QStringLiteral("bigserial")},
+ };
+
+ if (column.autoIncrement && column.generatedAs.isEmpty())
+ return GeneratableMap.at(type);
+
+ QString options;
+
+ if (!column.generatedAs.isEmpty())
+ options = QStringLiteral(" (%1)").arg(column.generatedAs);
+
+ return QStringLiteral("%1 generated %2 as identity%3")
+ .arg(std::move(type),
+ column.always ? QStringLiteral("always") : QStringLiteral("by default"),
+ std::move(options));
+}
+
+QString
+PostgresSchemaGrammar::formatPostGisType(QString &&type, const ColumnDefinition &column)
+{
+ if (!column.isGeometry)
+ return QStringLiteral("geography(%1, %2)")
+ .arg(std::move(type), column.srid ? QString::number(*column.srid)
+ : QStringLiteral("4326"));
+
+ // NOTE api different, Eloquent uses the column.projection for this, I'm reusing the column.srid silverqx
+ if (column.srid)
+ return QStringLiteral("geometry(%1, %2)")
+ .arg(std::move(type), QString::number(*column.srid));
+
+ return QStringLiteral("geometry(%1)").arg(std::move(type));
+}
+
+QString PostgresSchemaGrammar::typeChar(const ColumnDefinition &column) const
+{
+ return QStringLiteral("char(%1)").arg(column.length);
+}
+
+QString PostgresSchemaGrammar::typeString(const ColumnDefinition &column) const
+{
+ return QStringLiteral("varchar(%1)").arg(column.length);
+}
+
+QString PostgresSchemaGrammar::typeTinyText(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("varchar(255)");
+}
+
+QString PostgresSchemaGrammar::typeText(const ColumnDefinition &/*unused*/) const
+{
+ return text_;
+}
+
+QString PostgresSchemaGrammar::typeMediumText(const ColumnDefinition &/*unused*/) const
+{
+ return text_;
+}
+
+QString PostgresSchemaGrammar::typeLongText(const ColumnDefinition &/*unused*/) const
+{
+ return text_;
+}
+
+QString PostgresSchemaGrammar::typeBigInteger(const ColumnDefinition &column) const
+{
+ return generatableColumn("bigint", column);
+}
+
+QString PostgresSchemaGrammar::typeInteger(const ColumnDefinition &column) const
+{
+ return generatableColumn("integer", column);
+}
+
+QString PostgresSchemaGrammar::typeMediumInteger(const ColumnDefinition &column) const
+{
+ return generatableColumn("integer", column);
+}
+
+QString PostgresSchemaGrammar::typeTinyInteger(const ColumnDefinition &column) const
+{
+ return generatableColumn("smallint", column);
+}
+
+QString PostgresSchemaGrammar::typeSmallInteger(const ColumnDefinition &column) const
+{
+ return generatableColumn("smallint", column);
+}
+
+QString PostgresSchemaGrammar::typeFloat(const ColumnDefinition &column) const
+{
+ return typeDouble(column);
+}
+
+QString PostgresSchemaGrammar::typeDouble(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("double precision");
+}
+
+QString PostgresSchemaGrammar::typeReal(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("real");
+}
+
+QString PostgresSchemaGrammar::typeDecimal(const ColumnDefinition &column) const
+{
+ // CUR schema, duplicate and many others are duplicates silverqx
+ Q_ASSERT(column.total && column.places);
+
+ return QStringLiteral("decimal(%1, %2)").arg(*column.total).arg(*column.places);
+}
+
+QString PostgresSchemaGrammar::typeBoolean(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("boolean");
+}
+
+QString PostgresSchemaGrammar::typeEnum(const ColumnDefinition &column) const
+{
+ return QStringLiteral(R"(varchar(255) check ("%1" in (%2)))")
+ .arg(column.name, quoteString(column.allowed));
+}
+
+QString PostgresSchemaGrammar::typeJson(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("json");
+}
+
+QString PostgresSchemaGrammar::typeJsonb(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("jsonb");
+}
+
+QString PostgresSchemaGrammar::typeDate(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("date");
+}
+
+QString PostgresSchemaGrammar::typeDateTime(const ColumnDefinition &column) const
+{
+ return typeTimestamp(column);
+}
+
+QString PostgresSchemaGrammar::typeDateTimeTz(const ColumnDefinition &column) const
+{
+ return typeTimestampTz(column);
+}
+
+QString PostgresSchemaGrammar::typeTime(const ColumnDefinition &column) const
+{
+ return QStringLiteral("time%1 without time zone")
+ .arg(column.precision > 0 ? QStringLiteral("(%1)").arg(column.precision)
+ : EMPTY);
+}
+
+QString PostgresSchemaGrammar::typeTimeTz(const ColumnDefinition &column) const
+{
+ return QStringLiteral("time%1 with time zone")
+ .arg(column.precision > 0 ? QStringLiteral("(%1)").arg(column.precision)
+ : EMPTY);
+}
+
+QString PostgresSchemaGrammar::typeTimestamp(const ColumnDefinition &column) const
+{
+ auto columnType =
+ QStringLiteral("timestamp%1 without time zone")
+ /* The behavior if the precision is omitted:
+ >-1 is ok so the default will be timestamp(0), the same as
+ in the MySQL grammar. */
+ .arg(column.precision > -1 ? QStringLiteral("(%1)").arg(column.precision)
+ : EMPTY);
+
+ return column.useCurrent
+ ? QStringLiteral("%1 default CURRENT_TIMESTAMP").arg(std::move(columnType))
+ : std::move(columnType);
+}
+
+QString PostgresSchemaGrammar::typeTimestampTz(const ColumnDefinition &column) const
+{
+ auto columnType =
+ QStringLiteral("timestamp%1 with time zone")
+ .arg(column.precision > 0 ? QStringLiteral("(%1)").arg(column.precision)
+ : EMPTY);
+
+ return column.useCurrent
+ ? QStringLiteral("%1 default CURRENT_TIMESTAMP").arg(std::move(columnType))
+ : std::move(columnType);
+}
+
+QString PostgresSchemaGrammar::typeYear(const ColumnDefinition &column) const
+{
+ return typeInteger(column);
+}
+
+QString PostgresSchemaGrammar::typeBinary(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("bytea");
+}
+
+QString PostgresSchemaGrammar::typeUuid(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("uuid");
+}
+
+QString PostgresSchemaGrammar::typeIpAddress(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("inet");
+}
+
+QString PostgresSchemaGrammar::typeMacAddress(const ColumnDefinition &/*unused*/) const
+{
+ return QStringLiteral("macaddr");
+}
+
+QString PostgresSchemaGrammar::typeGeometry(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("geometry"), column);
+}
+
+QString PostgresSchemaGrammar::typePoint(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("point"), column);
+}
+
+QString PostgresSchemaGrammar::typeLineString(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("linestring"), column);
+}
+
+QString PostgresSchemaGrammar::typePolygon(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("polygon"), column);
+}
+
+QString
+PostgresSchemaGrammar::typeGeometryCollection(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("geometrycollection"), column);
+}
+
+QString PostgresSchemaGrammar::typeMultiPoint(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("multipoint"), column);
+}
+
+QString PostgresSchemaGrammar::typeMultiLineString(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("multilinestring"), column);
+}
+
+QString PostgresSchemaGrammar::typeMultiPolygon(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("multipolygon"), column);
+}
+
+QString PostgresSchemaGrammar::typeMultiPolygonZ(const ColumnDefinition &column) const
+{
+ return formatPostGisType(QStringLiteral("multipolygonz"), column);
+}
+
+QString PostgresSchemaGrammar::modifyVirtualAs(const ColumnDefinition &column) const
+{
+ if (column.virtualAs.isEmpty())
+ return {};
+
+ return QStringLiteral(" generated always as (%1)").arg(column.virtualAs);
+}
+
+QString PostgresSchemaGrammar::modifyStoredAs(const ColumnDefinition &column) const
+{
+ if (column.storedAs.isEmpty())
+ return {};
+
+ return QStringLiteral(" generated always as (%1) stored").arg(column.storedAs);
+}
+
+QString PostgresSchemaGrammar::modifyCollate(const ColumnDefinition &column) const
+{
+ if (column.collation.isEmpty())
+ return {};
+
+ return QStringLiteral(" collate %1").arg(wrapValue(column.collation));
+}
+
+QString PostgresSchemaGrammar::modifyNullable(const ColumnDefinition &column) const
+{
+ return column.nullable ? QStringLiteral(" null") : QStringLiteral(" not null");
+}
+
+QString PostgresSchemaGrammar::modifyDefault(const ColumnDefinition &column) const
+{
+ // CUR schema, duplicate silverqx
+ 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 PostgresSchemaGrammar::modifyIncrement(const ColumnDefinition &column) const
+{
+ static const std::unordered_set serials {
+ ColumnType::BigInteger, ColumnType::Integer, ColumnType::MediumInteger,
+ ColumnType::SmallInteger, ColumnType::TinyInteger
+ };
+
+ if ((serials.contains(column.type) || !column.generatedAs.isEmpty()) &&
+ column.autoIncrement
+ )
+ return QStringLiteral(" primary key");
+
+ return {};
}
} // namespace Orm::SchemaNs::Grammars
diff --git a/src/orm/schema/grammars/schemagrammar.cpp b/src/orm/schema/grammars/schemagrammar.cpp
index 66279368d..7f3671335 100644
--- a/src/orm/schema/grammars/schemagrammar.cpp
+++ b/src/orm/schema/grammars/schemagrammar.cpp
@@ -37,12 +37,12 @@ QString SchemaGrammar::compileDropAllViews(const QVector &/*unused*/) c
throw Exceptions::RuntimeError(NotImplemented);
}
-QString SchemaGrammar::compileGetAllTables() const
+QString SchemaGrammar::compileGetAllTables(const QVector &/*unused*/) const
{
throw Exceptions::RuntimeError(NotImplemented);
}
-QString SchemaGrammar::compileGetAllViews() const
+QString SchemaGrammar::compileGetAllViews(const QVector &/*unused*/) const
{
throw Exceptions::RuntimeError(NotImplemented);
}
diff --git a/src/orm/schema/indexdefinitionreference.cpp b/src/orm/schema/indexdefinitionreference.cpp
new file mode 100644
index 000000000..fa2ba3f65
--- /dev/null
+++ b/src/orm/schema/indexdefinitionreference.cpp
@@ -0,0 +1,34 @@
+#include "orm/schema/indexdefinitionreference.hpp"
+
+#include "orm/schema/columndefinition.hpp"
+
+TINYORM_BEGIN_COMMON_NAMESPACE
+
+namespace Orm::SchemaNs
+{
+
+/* public */
+
+IndexDefinitionReference::IndexDefinitionReference(IndexCommand &indexCommand)
+ : m_indexCommand(indexCommand)
+{}
+
+IndexDefinitionReference &
+IndexDefinitionReference::algorithm(const QString &algorithm)
+{
+ m_indexCommand.get().algorithm = algorithm;
+
+ return *this;
+}
+
+IndexDefinitionReference &
+IndexDefinitionReference::language(const QString &language)
+{
+ m_indexCommand.get().language = language;
+
+ return *this;
+}
+
+} // namespace Orm::SchemaNs
+
+TINYORM_END_COMMON_NAMESPACE
diff --git a/src/orm/schema/mysqlschemabuilder.cpp b/src/orm/schema/mysqlschemabuilder.cpp
index 1b8b6744b..2379ae076 100644
--- a/src/orm/schema/mysqlschemabuilder.cpp
+++ b/src/orm/schema/mysqlschemabuilder.cpp
@@ -10,6 +10,8 @@ TINYORM_BEGIN_COMMON_NAMESPACE
namespace Orm::SchemaNs
{
+/* public */
+
QSqlQuery MySqlSchemaBuilder::createDatabase(const QString &name) const
{
return m_connection.statement(
@@ -73,21 +75,21 @@ void MySqlSchemaBuilder::dropAllViews() const
QSqlQuery MySqlSchemaBuilder::getAllTables() const
{
// CUR schema, use postproccessor processColumnListing() silverqx
- return m_connection.select(m_grammar.compileGetAllTables());
+ return m_connection.selectFromWriteConnection(m_grammar.compileGetAllTables());
}
QSqlQuery MySqlSchemaBuilder::getAllViews() const
{
- return m_connection.select(m_grammar.compileGetAllViews());
+ return m_connection.selectFromWriteConnection(m_grammar.compileGetAllViews());
}
QStringList MySqlSchemaBuilder::getColumnListing(const QString &table) const
{
const auto table_ = NOSPACE.arg(m_connection.getTablePrefix(), table);
- auto query = m_connection.select(m_grammar.compileColumnListing(), {
- m_connection.getDatabaseName(), table_
- });
+ auto query = m_connection.selectFromWriteConnection(
+ m_grammar.compileColumnListing(),
+ {m_connection.getDatabaseName(), table_});
return m_connection.getPostProcessor().processColumnListing(query);
}
@@ -98,9 +100,9 @@ bool MySqlSchemaBuilder::hasTable(const QString &table) const
Q_ASSERT(m_connection.driver()->hasFeature(QSqlDriver::QuerySize));
- return m_connection.select(m_grammar.compileTableExists(),
- {m_connection.getDatabaseName(), table_})
- .size() > 0;
+ return m_connection.selectFromWriteConnection(
+ m_grammar.compileTableExists(),
+ {m_connection.getDatabaseName(), table_}).size() > 0;
}
} // namespace Orm::SchemaNs
diff --git a/src/orm/schema/postgresschemabuilder.cpp b/src/orm/schema/postgresschemabuilder.cpp
index 942a830ec..74756c52c 100644
--- a/src/orm/schema/postgresschemabuilder.cpp
+++ b/src/orm/schema/postgresschemabuilder.cpp
@@ -1,5 +1,8 @@
#include "orm/schema/postgresschemabuilder.hpp"
+#include
+#include
+
#include "orm/databaseconnection.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
@@ -9,20 +12,130 @@ namespace Orm::SchemaNs
/* public */
+QSqlQuery PostgresSchemaBuilder::createDatabase(const QString &name) const
+{
+ // CUR schema, duplicates silverqx
+ return m_connection.unprepared(
+ m_grammar.compileCreateDatabase(name, m_connection));
+}
+
+QSqlQuery PostgresSchemaBuilder::dropDatabaseIfExists(const QString &name) const
+{
+ // CUR schema, duplicates silverqx
+ return m_connection.unprepared(
+ m_grammar.compileDropDatabaseIfExists(name));
+}
+
+// CUR schema, test in functional tests silverqx
+void PostgresSchemaBuilder::dropAllTables() const
+{
+ auto query = getAllTables();
+
+ // No fields in the record
+ if (query.record().isEmpty())
+ return;
+
+ /* ConnectionFactory provides a default value 'spatial_ref_sys' for this option
+ for the QPSQL driver. */
+ const auto excludedTables = m_connection.getConfig(dont_drop).value();
+
+ QVector tables;
+ if (const auto size = query.size(); size > 0)
+ tables.reserve(size);
+
+ while (query.next())
+ if (auto table = query.value(0).value();
+ !excludedTables.contains(table)
+ )
+ tables << std::move(table);
+
+ if (tables.isEmpty())
+ return;
+
+ m_connection.unprepared(m_grammar.compileDropAllTables(tables));
+}
+
+// CUR schema, duplicate silverqx
+// CUR schema, test in functional tests silverqx
+void PostgresSchemaBuilder::dropAllViews() const
+{
+ auto query = getAllViews();
+
+ // No fields in the record
+ if (query.record().isEmpty())
+ return;
+
+ /* For these it throws that needed by the postgis extension and proposes to delete
+ extension instead, so exclude them. */
+ const QSet excludedViews {QStringLiteral("geography_columns"),
+ QStringLiteral("geometry_columns")};
+
+ QVector views;
+ if (const auto size = query.size(); size > 0)
+ views.reserve(size);
+
+ while (query.next())
+ if (auto view = query.value(0).value();
+ !excludedViews.contains(view)
+ )
+ views << std::move(view);
+
+ if (views.isEmpty())
+ return;
+
+ m_connection.unprepared(m_grammar.compileDropAllViews(views));
+}
+
+QSqlQuery PostgresSchemaBuilder::getAllTables() const
+{
+ auto schemaList = m_connection.getConfig(schema_).value();
+
+ QVector schema;
+ std::ranges::move(schemaList, std::back_inserter(schema));
+
+ // CUR schema, use postproccessor processColumnListing() silverqx
+ return m_connection.selectFromWriteConnection(
+ m_grammar.compileGetAllTables(std::move(schema)));
+}
+
+QSqlQuery PostgresSchemaBuilder::getAllViews() const
+{
+ auto schemaList = m_connection.getConfig(schema_).value();
+
+ QVector schema;
+ std::ranges::move(schemaList, std::back_inserter(schema));
+
+ return m_connection.selectFromWriteConnection(
+ m_grammar.compileGetAllViews(std::move(schema)));
+}
+
QStringList PostgresSchemaBuilder::getColumnListing(const QString &table) const
{
auto [schema, table_] = parseSchemaAndTable(table);
table_ = NOSPACE.arg(m_connection.getTablePrefix(), table);
- auto query = m_connection.select(m_grammar.compileColumnListing(), {
- schema, table_
- });
+ auto query = m_connection.selectFromWriteConnection(
+ m_grammar.compileColumnListing(), {schema, table_});
return m_connection.getPostProcessor().processColumnListing(query);
}
-std::pair
+bool PostgresSchemaBuilder::hasTable(const QString &table) const
+{
+ auto [schema, table_] = parseSchemaAndTable(table);
+
+ table_ = NOSPACE.arg(m_connection.getTablePrefix(), table);
+
+ Q_ASSERT(m_connection.driver()->hasFeature(QSqlDriver::QuerySize));
+
+ return m_connection.selectFromWriteConnection(
+ m_grammar.compileTableExists(), {schema, table_}).size() > 0;
+}
+
+/* protected */
+
+std::tuple
PostgresSchemaBuilder::parseSchemaAndTable(const QString &table) const
{
QString schema;
diff --git a/src/orm/schema/schemaconstants_extern.cpp b/src/orm/schema/schemaconstants_extern.cpp
index d11a762eb..645da9e43 100644
--- a/src/orm/schema/schemaconstants_extern.cpp
+++ b/src/orm/schema/schemaconstants_extern.cpp
@@ -6,21 +6,24 @@ 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");
+ 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");
+
+ // PostgreSQL specific command
+ const QString Comment = QStringLiteral("comment");
// Indexes
const QString Primary = QStringLiteral("primary");
diff --git a/src/src.pri b/src/src.pri
index 428112e24..8ae531905 100644
--- a/src/src.pri
+++ b/src/src.pri
@@ -43,6 +43,7 @@ sourcesList += \
$$PWD/orm/schema/grammars/postgresschemagrammar.cpp \
$$PWD/orm/schema/grammars/schemagrammar.cpp \
$$PWD/orm/schema/grammars/sqliteschemagrammar.cpp \
+ $$PWD/orm/schema/indexdefinitionreference.cpp \
$$PWD/orm/schema/mysqlschemabuilder.cpp \
$$PWD/orm/schema/postgresschemabuilder.cpp \
$$PWD/orm/schema/schemabuilder.cpp \
diff --git a/tests/TinyUtils/src/databases.cpp b/tests/TinyUtils/src/databases.cpp
index 0c3b62cee..30b549a52 100644
--- a/tests/TinyUtils/src/databases.cpp
+++ b/tests/TinyUtils/src/databases.cpp
@@ -5,6 +5,21 @@
#include "orm/exceptions/runtimeerror.hpp"
#include "orm/utils/type.hpp"
+using Orm::Constants::EMPTY;
+using Orm::Constants::H127001;
+using Orm::Constants::InnoDB;
+using Orm::Constants::P3306;
+using Orm::Constants::P5432;
+using Orm::Constants::PUBLIC;
+using Orm::Constants::QMYSQL;
+using Orm::Constants::QPSQL;
+using Orm::Constants::QSQLITE;
+using Orm::Constants::ROOT;
+using Orm::Constants::TZ00;
+using Orm::Constants::UTC;
+using Orm::Constants::UTF8;
+using Orm::Constants::UTF8MB4;
+using Orm::Constants::UTF8MB40900aici;
using Orm::Constants::database_;
using Orm::Constants::driver_;
using Orm::Constants::charset_;
@@ -12,32 +27,18 @@ 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;
-using Orm::Constants::P5432;
using Orm::Constants::password_;
using Orm::Constants::port_;
+using Orm::Constants::postgres_;
using Orm::Constants::prefix_;
using Orm::Constants::prefix_indexes;
-using Orm::Constants::PUBLIC;
-using Orm::Constants::QMYSQL;
-using Orm::Constants::QPSQL;
-using Orm::Constants::QSQLITE;
-using Orm::Constants::ROOT;
using Orm::Constants::schema_;
using Orm::Constants::strict_;
using Orm::Constants::timezone_;
using Orm::Constants::username_;
-using Orm::Constants::EMPTY;
-using Orm::Constants::TZ00;
-using Orm::Constants::UTC;
-using Orm::Constants::UTF8;
-using Orm::Constants::UTF8MB4;
-using Orm::Constants::UTF8MB40900aici;
using Orm::DB;
@@ -228,14 +229,15 @@ Databases::postgresConfiguration()
{port_, qEnvironmentVariable("DB_PGSQL_PORT", P5432)},
{database_, qEnvironmentVariable("DB_PGSQL_DATABASE", EMPTY)},
{schema_, qEnvironmentVariable("DB_PGSQL_SCHEMA", PUBLIC)},
- {username_, qEnvironmentVariable("DB_PGSQL_USERNAME",
- QStringLiteral("postgres"))},
+ {username_, qEnvironmentVariable("DB_PGSQL_USERNAME", postgres_)},
{password_, qEnvironmentVariable("DB_PGSQL_PASSWORD", EMPTY)},
{charset_, qEnvironmentVariable("DB_PGSQL_CHARSET", UTF8)},
// I don't use timezone types in postgres anyway
{timezone_, UTC},
{prefix_, EMPTY},
{prefix_indexes, true},
+ // ConnectionFactory provides a default value for this, this is only for reference
+// {dont_drop, QStringList {QStringLiteral("spatial_ref_sys")}},
{options_, QVariantHash()},
};
diff --git a/tests/auto/functional/tom/migrate/tst_migrate.cpp b/tests/auto/functional/tom/migrate/tst_migrate.cpp
index d5430f7fe..ec858aa13 100644
--- a/tests/auto/functional/tom/migrate/tst_migrate.cpp
+++ b/tests/auto/functional/tom/migrate/tst_migrate.cpp
@@ -61,12 +61,13 @@ private slots:
private:
/*! Prepare arguments and invoke runCommand(). */
[[nodiscard]] int
- invokeCommand(const QString &name, std::vector &&arguments = {}) const;
+ invokeCommand(const QString &connection, const QString &name,
+ std::vector &&arguments = {}) const;
/*! Create a tom application instance and invoke the given command. */
int runCommand(int &argc, const std::vector &argv) const;
/*! Invoke the status command to obtain results. */
- inline int invokeTestStatusCommand() const;
+ inline int invokeTestStatusCommand(const QString &connection) const;
/*! Get result of the last status command. */
Status status() const;
/*! Create a status object for comparing with the result of the status(). */
@@ -75,10 +76,7 @@ private:
Status createResetStatus() const;
/*! Prepare the migration database for running. */
- void prepareDatabase() const;
-
- /*! Connection name used in this test case. */
- QString m_connection {};
+ void prepareDatabase(const QStringList &connections) const;
/*! Migrations table name. */
inline static const auto MigrationsTable = QStringLiteral("migrations_unit_testing");
@@ -140,12 +138,19 @@ namespace
void tst_Migrate::initTestCase()
{
- m_connection = Databases::createConnection(Databases::MYSQL);
+ const auto &connections =
+ Databases::createConnections({Databases::MYSQL, Databases::POSTGRESQL});
- if (m_connection.isEmpty())
+ if (connections.isEmpty())
QSKIP(QStringLiteral("%1 autotest skipped, environment variables "
- "for '%2' connection have not been defined.")
- .arg("tst_Migrate", Databases::MYSQL).toUtf8().constData(), );
+ "for ANY connection have not been defined.")
+ .arg("tst_Migrate").toUtf8().constData(), );
+
+ QTest::addColumn("connection");
+
+ // Run all tests for all supported database connections
+ for (const auto &connection : connections)
+ QTest::newRow(connection.toUtf8().constData()) << connection;
/* Modify the migrate:status command to not output a status table to the console but
instead return a result as the vector, this vector is then used for comparing
@@ -153,21 +158,23 @@ void tst_Migrate::initTestCase()
TomApplication::enableInUnitTests();
// Prepare the migration database for running
- prepareDatabase();
+ prepareDatabase(connections);
}
void tst_Migrate::cleanup() const
{
+ QFETCH_GLOBAL(QString, connection);
+
/* All test methods need this except for two of them (reset and I don't remember
second), I will not implement special logic to skip this for these two methods. */
{
- auto exitCode = invokeCommand(MigrateReset);
+ auto exitCode = invokeCommand(connection, MigrateReset);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createResetStatus(), status());
@@ -176,14 +183,16 @@ void tst_Migrate::cleanup() const
void tst_Migrate::migrate() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate);
+ auto exitCode = invokeCommand(connection, Migrate);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -192,14 +201,16 @@ void tst_Migrate::migrate() const
void tst_Migrate::migrate_Step() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate, {"--step"});
+ auto exitCode = invokeCommand(connection, Migrate, {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
@@ -208,14 +219,16 @@ void tst_Migrate::migrate_Step() const
void tst_Migrate::reset() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(MigrateReset);
+ auto exitCode = invokeCommand(connection, MigrateReset);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createResetStatus(), status());
@@ -224,14 +237,16 @@ void tst_Migrate::reset() const
void tst_Migrate::rollback_OnMigrate() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate);
+ auto exitCode = invokeCommand(connection, Migrate);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -239,13 +254,13 @@ void tst_Migrate::rollback_OnMigrate() const
// rollback on previous migrate w/o --step
{
- auto exitCode = invokeCommand(MigrateRollback);
+ auto exitCode = invokeCommand(connection, MigrateRollback);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createResetStatus(), status());
@@ -254,14 +269,16 @@ void tst_Migrate::rollback_OnMigrate() const
void tst_Migrate::rollback_OnMigrateWithStep() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate, {"--step"});
+ auto exitCode = invokeCommand(connection, Migrate, {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
@@ -269,13 +286,13 @@ void tst_Migrate::rollback_OnMigrateWithStep() const
// rollback on previous migrate with --step
{
- auto exitCode = invokeCommand(MigrateRollback);
+ auto exitCode = invokeCommand(connection, MigrateRollback);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
@@ -289,14 +306,16 @@ void tst_Migrate::rollback_OnMigrateWithStep() const
void tst_Migrate::rollback_Step_OnMigrate() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate);
+ auto exitCode = invokeCommand(connection, Migrate);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -304,13 +323,13 @@ void tst_Migrate::rollback_Step_OnMigrate() const
// rollback on previous migrate w/o --step
{
- auto exitCode = invokeCommand(MigrateRollback, {"--step=2"});
+ auto exitCode = invokeCommand(connection, MigrateRollback, {"--step=2"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
@@ -324,14 +343,16 @@ void tst_Migrate::rollback_Step_OnMigrate() const
void tst_Migrate::rollback_Step_OnMigrateWithStep() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate, {"--step"});
+ auto exitCode = invokeCommand(connection, Migrate, {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
@@ -339,13 +360,13 @@ void tst_Migrate::rollback_Step_OnMigrateWithStep() const
// rollback on previous migrate with --step
{
- auto exitCode = invokeCommand(MigrateRollback, {"--step=2"});
+ auto exitCode = invokeCommand(connection, MigrateRollback, {"--step=2"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
@@ -359,14 +380,16 @@ void tst_Migrate::rollback_Step_OnMigrateWithStep() const
void tst_Migrate::refresh_OnMigrate() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate);
+ auto exitCode = invokeCommand(connection, Migrate);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -374,13 +397,13 @@ void tst_Migrate::refresh_OnMigrate() const
// refresh on previous migrate w/o --step
{
- auto exitCode = invokeCommand(MigrateRefresh);
+ auto exitCode = invokeCommand(connection, MigrateRefresh);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -389,14 +412,16 @@ void tst_Migrate::refresh_OnMigrate() const
void tst_Migrate::refresh_OnMigrateWithStep() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate, {"--step"});
+ auto exitCode = invokeCommand(connection, Migrate, {"--step"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
@@ -404,13 +429,13 @@ void tst_Migrate::refresh_OnMigrateWithStep() const
// refresh on previous migrate with --step
{
- auto exitCode = invokeCommand(MigrateRefresh);
+ auto exitCode = invokeCommand(connection, MigrateRefresh);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -419,14 +444,16 @@ void tst_Migrate::refresh_OnMigrateWithStep() const
void tst_Migrate::refresh_Step() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate);
+ auto exitCode = invokeCommand(connection, Migrate);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -434,13 +461,13 @@ void tst_Migrate::refresh_Step() const
// refresh on previous migrate w/o --step
{
- auto exitCode = invokeCommand(MigrateRefresh, {"--step=2"});
+ auto exitCode = invokeCommand(connection, MigrateRefresh, {"--step=2"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
@@ -454,14 +481,16 @@ void tst_Migrate::refresh_Step() const
void tst_Migrate::refresh_StepMigrate() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate);
+ auto exitCode = invokeCommand(connection, Migrate);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -469,13 +498,13 @@ void tst_Migrate::refresh_StepMigrate() const
// refresh on previous migrate w/o --step
{
- auto exitCode = invokeCommand(MigrateRefresh, {"--step-migrate"});
+ auto exitCode = invokeCommand(connection, MigrateRefresh, {"--step-migrate"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyStepMigrated), status());
@@ -484,14 +513,16 @@ void tst_Migrate::refresh_StepMigrate() const
void tst_Migrate::refresh_Step_StepMigrate() const
{
+ QFETCH_GLOBAL(QString, connection);
+
{
- auto exitCode = invokeCommand(Migrate);
+ auto exitCode = invokeCommand(connection, Migrate);
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus(FullyMigrated), status());
@@ -499,13 +530,14 @@ void tst_Migrate::refresh_Step_StepMigrate() const
// refresh on previous migrate w/o --step
{
- auto exitCode = invokeCommand(MigrateRefresh, {"--step=2", "--step-migrate"});
+ auto exitCode = invokeCommand(connection, MigrateRefresh,
+ {"--step=2", "--step-migrate"});
QVERIFY(exitCode == EXIT_SUCCESS);
}
{
- auto exitCode = invokeTestStatusCommand();
+ auto exitCode = invokeTestStatusCommand(connection);
QVERIFY(exitCode == EXIT_SUCCESS);
QCOMPARE(createStatus({
@@ -519,18 +551,15 @@ void tst_Migrate::refresh_Step_StepMigrate() const
/* private */
-int tst_Migrate::invokeCommand(const QString &name,
+int tst_Migrate::invokeCommand(const QString &connection, const QString &name,
std::vector &&arguments) const
{
static const auto connectionTmpl = QStringLiteral("--database=%1");
// Prepare fake argc and argv
const auto nameArr = name.toUtf8();
- // FUTURE tests tom, when the schema builder will support more db drivers, I can run it on all supported connections, code will look like in the tst_querybuilder.cpp, then I will fetch connection name in every test method using QFETCH_GLOBAL() and I will pass this connection name to the invokeCommand(), so I will discard m_connection and will use method parameter connection here silverqx
- /* Schema builder is implemented only for the MySQL driver, so I can use m_connection
- here as the default connection. */
// DB connection to use
- const auto connectionArr = connectionTmpl.arg(m_connection).toUtf8();
+ const auto connectionArr = connectionTmpl.arg(connection).toUtf8();
std::vector argv {
#ifdef _WIN32
@@ -570,9 +599,9 @@ int tst_Migrate::runCommand(int &argc, const std::vector &argv) co
return EXIT_FAILURE;
}
-int tst_Migrate::invokeTestStatusCommand() const
+int tst_Migrate::invokeTestStatusCommand(const QString &connection) const
{
- return invokeCommand(MigrateStatus);
+ return invokeCommand(connection, MigrateStatus);
}
Status tst_Migrate::status() const
@@ -595,25 +624,27 @@ Status tst_Migrate::createResetStatus() const
};
}
-void tst_Migrate::prepareDatabase() const
+void tst_Migrate::prepareDatabase(const QStringList &connections) const
{
- // Ownership of a unique_ptr()
- const auto schema = Databases::manager()->connection(m_connection)
- .getSchemaBuilder();
+ for (const auto &connection : connections) {
+ // Ownership of a unique_ptr()
+ const auto schema = Databases::manager()->connection(connection)
+ .getSchemaBuilder();
- // Create the migrations table if needed
- if (!schema->hasTable(MigrationsTable)) {
- auto exitCode = invokeCommand(MigrateInstall);
+ // Create the migrations table if needed
+ if (!schema->hasTable(MigrationsTable)) {
+ auto exitCode = invokeCommand(connection, MigrateInstall);
+
+ QVERIFY(exitCode == EXIT_SUCCESS);
+
+ return;
+ }
+
+ // Reset the migrations table
+ auto exitCode = invokeCommand(connection, MigrateReset);
QVERIFY(exitCode == EXIT_SUCCESS);
-
- return;
}
-
- // Reset the migrations table
- auto exitCode = invokeCommand(MigrateReset);
-
- QVERIFY(exitCode == EXIT_SUCCESS);
}
QTEST_MAIN(tst_Migrate)
diff --git a/tests/auto/unit/orm/schema/CMakeLists.txt b/tests/auto/unit/orm/schema/CMakeLists.txt
index 250964c9b..fcb381827 100644
--- a/tests/auto/unit/orm/schema/CMakeLists.txt
+++ b/tests/auto/unit/orm/schema/CMakeLists.txt
@@ -1,2 +1,3 @@
add_subdirectory(blueprint)
add_subdirectory(mysql_schemabuilder)
+add_subdirectory(postgresql_schemabuilder)
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
index a04bb765e..df879c6fd 100644
--- a/tests/auto/unit/orm/schema/mysql_schemabuilder/tst_mysql_schemabuilder.cpp
+++ b/tests/auto/unit/orm/schema/mysql_schemabuilder/tst_mysql_schemabuilder.cpp
@@ -11,15 +11,15 @@
#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::Constants::charset_;
+using Orm::Constants::driver_;
+using Orm::Constants::collation_;
using Orm::DB;
using Orm::Exceptions::LogicError;
@@ -677,9 +677,9 @@ void tst_Mysql_SchemaBuilder::modifiers() const
table.string("name2").comment("name2 note");
table.string("name3");
table.string("name4").invisible();
- table.string("name5").charset("utf8");
+ table.string("name5").charset(UTF8);
table.string("name6").collation("utf8mb4_unicode_ci");
- table.string("name7").charset("utf8").collation("utf8_unicode_ci");
+ table.string("name7").charset(UTF8).collation("utf8_unicode_ci");
table.bigInteger("big_int").isUnsigned();
table.bigInteger("big_int1");
});
diff --git a/tests/auto/unit/orm/schema/postgresql_schemabuilder/CMakeLists.txt b/tests/auto/unit/orm/schema/postgresql_schemabuilder/CMakeLists.txt
index 8945f7001..728d570eb 100644
--- a/tests/auto/unit/orm/schema/postgresql_schemabuilder/CMakeLists.txt
+++ b/tests/auto/unit/orm/schema/postgresql_schemabuilder/CMakeLists.txt
@@ -1,16 +1,16 @@
-project(mysql_schemabuilder
+project(postgresql_schemabuilder
LANGUAGES CXX
)
-add_executable(mysql_schemabuilder
+add_executable(postgresql_schemabuilder
tst_postgresql_schemabuilder.cpp
)
-add_test(NAME mysql_schemabuilder COMMAND mysql_schemabuilder)
+add_test(NAME postgresql_schemabuilder COMMAND postgresql_schemabuilder)
include(TinyTestCommon)
if(ORM)
- tiny_configure_test(mysql_schemabuilder INCLUDE_MODELS)
+ tiny_configure_test(postgresql_schemabuilder INCLUDE_MODELS)
else()
- tiny_configure_test(mysql_schemabuilder)
+ tiny_configure_test(postgresql_schemabuilder)
endif()
diff --git a/tests/auto/unit/orm/schema/postgresql_schemabuilder/postgresql_schemabuilder.pro b/tests/auto/unit/orm/schema/postgresql_schemabuilder/postgresql_schemabuilder.pro
new file mode 100644
index 000000000..8506e20d8
--- /dev/null
+++ b/tests/auto/unit/orm/schema/postgresql_schemabuilder/postgresql_schemabuilder.pro
@@ -0,0 +1,6 @@
+include($$TINYORM_SOURCE_TREE/tests/qmake/common.pri)
+include($$TINYORM_SOURCE_TREE/tests/qmake/TinyUtils.pri)
+!disable_orm: \
+ include($$TINYORM_SOURCE_TREE/tests/models/models.pri)
+
+SOURCES = tst_postgresql_schemabuilder.cpp
diff --git a/tests/auto/unit/orm/schema/postgresql_schemabuilder/tst_postgresql_schemabuilder.cpp b/tests/auto/unit/orm/schema/postgresql_schemabuilder/tst_postgresql_schemabuilder.cpp
new file mode 100644
index 000000000..b563559d4
--- /dev/null
+++ b/tests/auto/unit/orm/schema/postgresql_schemabuilder/tst_postgresql_schemabuilder.cpp
@@ -0,0 +1,1599 @@
+#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::ID;
+using Orm::Constants::NAME;
+using Orm::Constants::PUBLIC;
+using Orm::Constants::QPSQL;
+using Orm::Constants::SIZE;
+using Orm::Constants::UTF8;
+using Orm::Constants::UcsBasic;
+using Orm::Constants::charset_;
+using Orm::Constants::driver_;
+
+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_PostgreSQL_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_PostgreSQL_SchemaBuilder::initTestCase()
+{
+ m_connection = Databases::createConnection(Databases::POSTGRESQL);
+
+ if (m_connection.isEmpty())
+ QSKIP(QStringLiteral("%1 autotest skipped, environment variables "
+ "for '%2' connection have not been defined.")
+ .arg("tst_PostgreSQL_SchemaBuilder", Databases::POSTGRESQL)
+ .toUtf8().constData(), );
+}
+
+void tst_PostgreSQL_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,
+ R"(create database "firewalls" encoding "utf8")");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_SchemaBuilder::createDatabase_Charset_Collation() const
+{
+ static const auto postgresCreateDb = QStringLiteral(
+ "tinyorm_postgres_tests_create_db");
+
+ // Create a new connection with different charset
+ DB::addConnection({
+ {driver_, QPSQL},
+ {charset_, "WIN1250"},
+ }, postgresCreateDb);
+
+ auto log = DB::connection(postgresCreateDb).pretend([](auto &connection)
+ {
+ Schema::on(connection.getName()).createDatabase(Firewalls);
+ });
+
+ QVERIFY(!log.isEmpty());
+ const auto &firstLog = log.first();
+
+ QCOMPARE(log.size(), 1);
+ QCOMPARE(firstLog.query,
+ R"(create database "firewalls" encoding "WIN1250")");
+ QVERIFY(firstLog.boundValues.isEmpty());
+
+ // Restore
+ DB::removeConnection(postgresCreateDb);
+}
+
+void tst_PostgreSQL_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,
+ R"(drop database if exists "firewalls")");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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");
+
+ // PostgreSQL doesn't have unsigned integers, so they should be same as above
+ 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\" bigserial primary key not null, "
+ "\"char\" char(255) not null, "
+ "\"char_10\" char(10) not null, "
+ "\"string\" varchar(255) not null, "
+ "\"string_22\" varchar(22) not null, "
+ "\"tiny_text\" varchar(255) not null, "
+ "\"text\" text not null, "
+ "\"medium_text\" text not null, "
+ "\"long_text\" text not null, "
+ "\"integer\" integer not null, "
+ "\"tinyInteger\" smallint not null, "
+ "\"smallInteger\" smallint not null, "
+ "\"mediumInteger\" integer not null, "
+ "\"bigInteger\" bigint not null, "
+ "\"unsignedInteger\" integer not null, "
+ "\"unsignedTinyInteger\" smallint not null, "
+ "\"unsignedSmallInteger\" smallint not null, "
+ "\"unsignedMediumInteger\" integer not null, "
+ "\"unsignedBigInteger\" bigint not null, "
+ "\"uuid\" uuid not null, "
+ "\"ip_address\" inet not null, "
+ "\"mac_address\" macaddr not null)");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" bigserial primary key not null, "
+ "\"name\" varchar(255) not null)");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_SchemaBuilder::createTable_Charset_Collation_Engine() const
+{
+ auto log = DB::connection(m_connection).pretend([](auto &connection)
+ {
+ Schema::on(connection.getName())
+ .create(Firewalls, [](Blueprint &table)
+ {
+ // Ignored with the PosrgreSQL grammar
+ table.charset = "WIN1250";
+
+ table.id();
+ table.string(NAME);
+ });
+ });
+
+ QVERIFY(!log.isEmpty());
+ const auto &firstLog = log.first();
+
+ QCOMPARE(log.size(), 1);
+ QCOMPARE(firstLog.query,
+ "create table \"firewalls\" ("
+ "\"id\" bigserial primary key not null, "
+ "\"name\" varchar(255) not null)");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" bigserial primary key not null, "
+ "\"created_at\" timestamp(0) without time zone null, "
+ "\"updated_at\" timestamp(0) without time zone null, "
+ "\"remember_token\" varchar(100) null)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ "alter table \"firewalls\" "
+ "drop column \"created_at\", drop column \"updated_at\"");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ "alter table \"firewalls\" drop column \"remember_token\"");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ "create table \"firewalls\" ("
+ "\"id\" bigserial primary key not null, "
+ "\"created_at\" timestamp(3) without time zone null, "
+ "\"updated_at\" timestamp(3) without time zone null)");
+ QVERIFY(log3.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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 column \"char\" char(255) not null, "
+ "add column \"char_10\" char(10) not null, "
+ "add column \"string\" varchar(255) not null, "
+ "add column \"string_22\" varchar(22) not null, "
+ "add column \"tiny_text\" varchar(255) not null, "
+ "add column \"text\" text not null, "
+ "add column \"medium_text\" text not null, "
+ "add column \"long_text\" text not null, "
+ "add column \"integer\" integer null, "
+ "add column \"tinyInteger\" smallint not null, "
+ "add column \"smallInteger\" smallint not null, "
+ "add column \"mediumInteger\" integer not null");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ "alter table \"firewalls\" drop column \"long_text\"");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ "alter table \"firewalls\" "
+ "drop column \"medium_text\", drop column \"text\"");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ "alter table \"firewalls\" "
+ "drop column \"smallInteger\", drop column \"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_PostgreSQL_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_PostgreSQL_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_PostgreSQL_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, "alter table \"secured\" rename to \"firewalls\"");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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,
+ R"(alter table "firewalls" drop column "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,
+ R"(alter table "firewalls" drop column "name", drop column "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,
+ R"(alter table "firewalls" drop column "name", drop column "size")");
+ QVERIFY(firstLog.boundValues.isEmpty());
+ }
+}
+
+void tst_PostgreSQL_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_PostgreSQL_SchemaBuilder::dropAllTypes() const
+{
+ QVERIFY_EXCEPTION_THROWN(Schema::on(m_connection).dropAllTypes(), LogicError);
+}
+
+void tst_PostgreSQL_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,
+ "select tablename from pg_catalog.pg_tables "
+ "where schemaname in ('public')");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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,
+ "select viewname from pg_catalog.pg_views "
+ "where schemaname in ('public')");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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 CONSTRAINTS ALL IMMEDIATE;");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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 CONSTRAINTS ALL DEFERRED;");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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 "
+ "from information_schema.columns "
+ "where table_schema = ? and table_name = ?");
+ QCOMPARE(firstLog.boundValues,
+ QVector({QVariant(PUBLIC), QVariant(Firewalls)}));
+}
+
+void tst_PostgreSQL_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(PUBLIC), QVariant(Firewalls)}));
+}
+
+void tst_PostgreSQL_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_PostgreSQL_SchemaBuilder::modifiers() const
+{
+ auto log = DB::connection(m_connection).pretend([](auto &connection)
+ {
+ Schema::on(connection.getName())
+ .create(Firewalls, [](Blueprint &table)
+ {
+ table.bigInteger(ID).autoIncrement().startingValue(5);
+ table.bigInteger("big_int");
+ table.string(NAME).defaultValue("guest");
+ table.string("name1").nullable();
+ table.string("name2").comment("name2 note");
+ table.string("name3");
+ // PostgreSQL doesn't support charset on the column
+ table.string("name5").charset(UTF8);
+ table.string("name6").collation(UcsBasic);
+ // PostgreSQL doesn't support charset on the column
+ table.string("name7").charset(UTF8).collation(UcsBasic);
+ });
+ /* Tests from and also integerIncrements, this would of course fail on real DB
+ as you can not have two primary keys. */
+ Schema::on(connection.getName())
+ .table(Firewalls, [](Blueprint &table)
+ {
+ table.integerIncrements(ID).from(15);
+ });
+ });
+
+ QCOMPARE(log.size(), 5);
+
+ const auto &log0 = log.at(0);
+ QCOMPARE(log0.query,
+ "create table \"firewalls\" ("
+ "\"id\" bigserial primary key not null, "
+ "\"big_int\" bigint not null, "
+ "\"name\" varchar(255) not null default 'guest', "
+ "\"name1\" varchar(255) null, "
+ "\"name2\" varchar(255) not null, "
+ "\"name3\" varchar(255) not null, "
+ "\"name5\" varchar(255) not null, "
+ "\"name6\" varchar(255) collate \"ucs_basic\" not null, "
+ "\"name7\" varchar(255) collate \"ucs_basic\" not null)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ R"(alter sequence "firewalls_id_seq" restart with 5)");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ R"(comment on column "firewalls"."name2" is 'name2 note')");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ "alter table \"firewalls\" add column \"id\" serial primary key not null");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log4 = log.at(4);
+ QCOMPARE(log4.query,
+ R"(alter sequence "firewalls_id_seq" restart with 15)");
+ QVERIFY(log4.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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')");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" boolean not null, "
+ "\"boolean_false\" boolean not null default '0', "
+ "\"boolean_true\" boolean not null default '1', "
+ "\"boolean_0\" boolean not null default '0', "
+ "\"boolean_1\" boolean not null default '1')");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" timestamp(0) without time zone not null, "
+ "\"created_current\" timestamp(0) without time zone "
+ "default CURRENT_TIMESTAMP not null, "
+ "\"created_t\" timestamp(0) without time zone not null, "
+ "\"created_t_current\" timestamp(0) without time zone "
+ "default CURRENT_TIMESTAMP not null)");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_SchemaBuilder::useCurrentOnUpdate() const
+{
+ auto log = DB::connection(m_connection).pretend([](auto &connection)
+ {
+ Schema::on(connection.getName())
+ .create(Firewalls, [](Blueprint &table)
+ {
+ table.dateTime("updated");
+ // PostgreSQL doesn't support on update
+ 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\" timestamp(0) without time zone not null, "
+ "\"updated_current\" timestamp(0) without time zone not null, "
+ "\"updated_t\" timestamp(0) without time zone not null, "
+ "\"updated_t_current\" timestamp(0) without time zone not null)");
+ QVERIFY(firstLog.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" bigserial primary key not null, "
+ "\"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\" geography(geometry, 4326) not null, "
+ "\"coordinates_s_cn\" geography(geometry, 4326) not null)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ "alter table \"firewalls\" "
+ "add constraint \"firewalls_name_u_unique\" unique (\"name_u\")");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ R"(create index "firewalls_name_i_index" on "firewalls" ("name_i"))");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ R"(create index "name_i_cn_index" on "firewalls" ("name_i_cn"))");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log4 = log.at(4);
+ QCOMPARE(log4.query,
+ "create index \"firewalls_name_f_fulltext\" on \"firewalls\" "
+ "using gin ((to_tsvector('english', \"name_f\")))");
+ QVERIFY(log4.boundValues.isEmpty());
+
+ const auto &log5 = log.at(5);
+ QCOMPARE(log5.query,
+ "create index \"name_f_cn_fulltext\" on \"firewalls\" "
+ "using gin ((to_tsvector('english', \"name_f_cn\")))");
+ QVERIFY(log5.boundValues.isEmpty());
+
+ const auto &log6 = log.at(6);
+ QCOMPARE(log6.query,
+ "create index \"firewalls_coordinates_s_spatialindex\" on \"firewalls\" "
+ "using gist (\"coordinates_s\")");
+ QVERIFY(log6.boundValues.isEmpty());
+
+ const auto &log7 = log.at(7);
+ QCOMPARE(log7.query,
+ "create index \"coordinates_s_cn_spatial\" on \"firewalls\" "
+ "using gist (\"coordinates_s_cn\")");
+ QVERIFY(log7.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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(R"("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").isGeometry();
+ table.spatialIndex("coordinates_s");
+
+ table.point("coordinates_s_cn", 3200).isGeometry();
+ 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\" bigserial primary key not null, "
+ "\"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(geometry) not null, "
+ "\"coordinates_s_cn\" geometry(point, 3200) not null)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ "alter table \"firewalls\" "
+ "add constraint \"name_u_unique\" unique (\"name_u\")");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ R"(create index "firewalls_name_i_index" on "firewalls" ("name_i"))");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ R"(create index "name_i_cn_index" on "firewalls" ("name_i_cn"))");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log4 = log.at(4);
+ QCOMPARE(log4.query,
+ R"(create index "name_r_raw" on "firewalls" ("name_r", name_r1))");
+ QVERIFY(log4.boundValues.isEmpty());
+
+ const auto &log5 = log.at(5);
+ QCOMPARE(log5.query,
+ "create index \"firewalls_name_f_fulltext\" on \"firewalls\" "
+ "using gin ((to_tsvector('english', \"name_f\")))");
+ QVERIFY(log5.boundValues.isEmpty());
+
+ const auto &log6 = log.at(6);
+ QCOMPARE(log6.query,
+ "create index \"name_f_cn_fulltext\" on \"firewalls\" "
+ "using gin ((to_tsvector('english', \"name_f_cn\")))");
+ QVERIFY(log6.boundValues.isEmpty());
+
+ const auto &log7 = log.at(7);
+ QCOMPARE(log7.query,
+ "create index \"firewalls_coordinates_s_spatialindex\" on \"firewalls\" "
+ "using gist (\"coordinates_s\")");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log8 = log.at(8);
+ QCOMPARE(log8.query,
+ "create index \"coordinates_s_cn_spatial\" on \"firewalls\" "
+ "using gist (\"coordinates_s_cn\")");
+ QVERIFY(log8.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" bigserial primary key not null, "
+ "\"name\" varchar(255) not null)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ "alter table \"firewalls\" "
+ "add constraint \"firewalls_name_unique\" unique (\"name\")");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ "alter index \"firewalls_name_unique\" "
+ "rename to \"firewalls_name_unique_renamed\"");
+ QVERIFY(log2.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" integer not null, "
+ "\"name_u\" varchar(255) not null, "
+ "\"name_i\" varchar(255) not null, "
+ "\"name_f\" varchar(255) not null, "
+ "\"coordinates_s\" geography(geometry, 4326) not null)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ R"(alter table "firewalls" add primary key ("id"))");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ "alter table \"firewalls\" "
+ "add constraint \"firewalls_name_u_unique\" unique (\"name_u\")");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ R"(create index "firewalls_name_i_index" on "firewalls" ("name_i"))");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log4 = log.at(4);
+ QCOMPARE(log4.query,
+ "create index \"firewalls_name_f_fulltext\" on \"firewalls\" "
+ "using gin ((to_tsvector('english', \"name_f\")))");
+ QVERIFY(log4.boundValues.isEmpty());
+
+ const auto &log5 = log.at(5);
+ QCOMPARE(log5.query,
+ "create index \"firewalls_coordinates_s_spatialindex\" on \"firewalls\" "
+ "using gist (\"coordinates_s\")");
+ QVERIFY(log5.boundValues.isEmpty());
+
+ const auto &log6 = log.at(6);
+ QCOMPARE(log6.query,
+ R"(alter table "firewalls" drop constraint "firewalls_pkey")");
+ QVERIFY(log6.boundValues.isEmpty());
+
+ const auto &log7 = log.at(7);
+ QCOMPARE(log7.query,
+ R"(alter table "firewalls" drop constraint "firewalls_name_u_unique")");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log8 = log.at(8);
+ QCOMPARE(log8.query,
+ R"(drop index "firewalls_name_i_index")");
+ QVERIFY(log8.boundValues.isEmpty());
+
+ const auto &log9 = log.at(9);
+ QCOMPARE(log9.query,
+ R"(drop index "firewalls_name_f_fulltext")");
+ QVERIFY(log9.boundValues.isEmpty());
+
+ const auto &log10 = log.at(10);
+ QCOMPARE(log10.query,
+ R"(drop index "firewalls_coordinates_s_spatialindex")");
+ QVERIFY(log10.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" integer not null, "
+ "\"name_u\" varchar(255) not null, "
+ "\"name_i\" varchar(255) not null, "
+ "\"name_f\" varchar(255) not null, "
+ "\"coordinates_s\" geography(geometry, 4326) not null)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ R"(alter table "firewalls" add primary key ("id"))");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ "alter table \"firewalls\" "
+ "add constraint \"firewalls_name_u_unique\" unique (\"name_u\")");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ R"(create index "firewalls_name_i_index" on "firewalls" ("name_i"))");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log4 = log.at(4);
+ QCOMPARE(log4.query,
+ "create index \"firewalls_name_f_fulltext\" on \"firewalls\" "
+ "using gin ((to_tsvector('english', \"name_f\")))");
+ QVERIFY(log4.boundValues.isEmpty());
+
+ const auto &log5 = log.at(5);
+ QCOMPARE(log5.query,
+ "create index \"firewalls_coordinates_s_spatialindex\" on \"firewalls\" "
+ "using gist (\"coordinates_s\")");
+ QVERIFY(log5.boundValues.isEmpty());
+
+ const auto &log6 = log.at(6);
+ QCOMPARE(log6.query,
+ R"(alter table "firewalls" drop constraint "firewalls_pkey")");
+ QVERIFY(log6.boundValues.isEmpty());
+
+ const auto &log7 = log.at(7);
+ QCOMPARE(log7.query,
+ R"(alter table "firewalls" drop constraint "firewalls_name_u_unique")");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log8 = log.at(8);
+ QCOMPARE(log8.query,
+ R"(drop index "firewalls_name_i_index")");
+ QVERIFY(log8.boundValues.isEmpty());
+
+ const auto &log9 = log.at(9);
+ QCOMPARE(log9.query,
+ R"(drop index "firewalls_name_f_fulltext")");
+ QVERIFY(log9.boundValues.isEmpty());
+
+ const auto &log10 = log.at(10);
+ QCOMPARE(log10.query,
+ R"(drop index "firewalls_coordinates_s_spatialindex")");
+ QVERIFY(log10.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" integer not null, "
+ "\"id1\" integer 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)");
+ QVERIFY(log0.boundValues.isEmpty());
+
+ const auto &log1 = log.at(1);
+ QCOMPARE(log1.query,
+ R"(alter table "firewalls" add primary key ("id", "id1"))");
+ QVERIFY(log1.boundValues.isEmpty());
+
+ const auto &log2 = log.at(2);
+ QCOMPARE(log2.query,
+ "alter table \"firewalls\" "
+ "add constraint \"firewalls_name_u_name_u1_unique\" "
+ "unique (\"name_u\", \"name_u1\")");
+ QVERIFY(log2.boundValues.isEmpty());
+
+ const auto &log3 = log.at(3);
+ QCOMPARE(log3.query,
+ "create index \"firewalls_name_i_name_i1_index\" "
+ "on \"firewalls\" (\"name_i\", \"name_i1\")");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log4 = log.at(4);
+ QCOMPARE(log4.query,
+ "create index \"firewalls_name_f_name_f1_fulltext\" on \"firewalls\" "
+ "using gin ((to_tsvector('english', \"name_f\") || "
+ "to_tsvector('english', \"name_f1\")))");
+ QVERIFY(log4.boundValues.isEmpty());
+
+ const auto &log5 = log.at(5);
+ QCOMPARE(log5.query,
+ R"(alter table "firewalls" drop constraint "firewalls_pkey")");
+ QVERIFY(log5.boundValues.isEmpty());
+
+ const auto &log6 = log.at(6);
+ QCOMPARE(log6.query,
+ "alter table \"firewalls\" drop constraint "
+ "\"firewalls_name_u_name_u1_unique\"");
+ QVERIFY(log3.boundValues.isEmpty());
+
+ const auto &log7 = log.at(7);
+ QCOMPARE(log7.query,
+ R"(drop index "firewalls_name_i_name_i1_index")");
+ QVERIFY(log7.boundValues.isEmpty());
+
+ const auto &log8 = log.at(8);
+ QCOMPARE(log8.query,
+ R"(drop index "firewalls_name_f_name_f1_fulltext")");
+ QVERIFY(log8.boundValues.isEmpty());
+}
+
+void tst_PostgreSQL_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\" bigserial primary key not null, "
+ "\"user_id\" bigint not null, "
+ "\"torrent_id\" bigint not null, "
+ "\"role_id\" bigint null)");
+ 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_PostgreSQL_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\" bigserial primary key not null, "
+ "\"user_id\" bigint not null, "
+ "\"torrent_id\" bigint not null, "
+ "\"role_id\" bigint null)");
+ 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_PostgreSQL_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\" bigserial primary key not null, "
+ "\"torrent_id\" bigint not null, "
+ "\"user_id\" bigint null)");
+ 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_PostgreSQL_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\" bigserial primary key not null, "
+ "\"user_id\" bigint not null, "
+ "\"torrent_id\" bigint not null, "
+ "\"role_id\" bigint null)");
+ 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,
+ R"(alter table "firewalls" drop constraint "firewalls_user_id_foreign")");
+ QVERIFY(log4.boundValues.isEmpty());
+
+ const auto &log5 = log.at(5);
+ QCOMPARE(log5.query,
+ R"(alter table "firewalls" drop constraint "firewalls_torrent_id_foreign")");
+ QVERIFY(log5.boundValues.isEmpty());
+
+ const auto &log6 = log.at(6);
+ QCOMPARE(log6.query,
+ R"(alter table "firewalls" drop constraint "firewalls_role_id_foreign")");
+ QVERIFY(log6.boundValues.isEmpty());
+
+ const auto &log7 = log.at(7);
+ QCOMPARE(log7.query,
+ R"(alter table "firewalls" drop column "role_id")");
+ QVERIFY(log7.boundValues.isEmpty());
+}
+
+QTEST_MAIN(tst_PostgreSQL_SchemaBuilder)
+
+#include "tst_postgresql_schemabuilder.moc"
diff --git a/tests/auto/unit/orm/schema/schema.pro b/tests/auto/unit/orm/schema/schema.pro
index 1edf4a899..5a088c5d4 100644
--- a/tests/auto/unit/orm/schema/schema.pro
+++ b/tests/auto/unit/orm/schema/schema.pro
@@ -3,3 +3,4 @@ TEMPLATE = subdirs
SUBDIRS = \
blueprint \
mysql_schemabuilder \
+ postgresql_schemabuilder \