mirror of
https://github.com/silverqx/TinyORM.git
synced 2026-05-03 15:09:26 -05:00
schema modifying columns 🔥🚀🥳
Added the change() method which allows to modify the type and attributes of existing columns. - added renameTo() for MySQL which can be used with the change() - added tests - updated docs
This commit is contained in:
@@ -1058,7 +1058,25 @@ When using the MySQL database, the `after` method may be used to add columns aft
|
||||
|
||||
### Modifying Columns
|
||||
|
||||
Not implemented.
|
||||
The `change` method allows you to modify the type and attributes of existing columns. For example, you may wish to increase the size of a `string` column. To see the `change` method in action, let's increase the size of the `name` column from 25 to 50. To accomplish this, we simply define the new state of the column and then call the `change` method:
|
||||
|
||||
#include <orm/schema.hpp>
|
||||
|
||||
Schema::table("users", [](Blueprint &table)
|
||||
{
|
||||
table.string("name", 50).change();
|
||||
});
|
||||
|
||||
When modifying a column, you must explicitly include all of the modifiers you want to keep on the column definition - any missing attribute will be dropped. For example, to retain the `unsigned`, `default`, and `comment` attributes, you must call each modifier explicitly when changing the column:
|
||||
|
||||
Schema::table("users", [](Blueprint &table)
|
||||
{
|
||||
table.integer("votes").isUnsigned().defaultValue(1).comment("my comment").change();
|
||||
});
|
||||
|
||||
:::info
|
||||
The `change` method and modifying columns is not implemented for the `SQLite` database because it doesn't support modifying columns out of the box.
|
||||
:::
|
||||
|
||||
#### Renaming Columns
|
||||
|
||||
|
||||
@@ -117,6 +117,8 @@ namespace Orm::SchemaNs
|
||||
QString column;
|
||||
/*! Column comment value. */
|
||||
QString comment;
|
||||
/*! Indicates whether a column will be changed or created. */
|
||||
bool change = false;
|
||||
};
|
||||
|
||||
/*! Table auto-incrementing column starting value command (MySQL/PostgreSQL). */
|
||||
@@ -201,18 +203,20 @@ namespace Orm::SchemaNs
|
||||
QVariant onUpdate {};
|
||||
/*! Create a SQL compliant identity column (PostgreSQL). */
|
||||
QString generatedAs {};
|
||||
/*! Create a stored generated column (MySQL/PostgreSQL/SQLite). */
|
||||
QString storedAs {};
|
||||
/*! Create a virtual generated column (MySQL/PostgreSQL/SQLite). */
|
||||
QString virtualAs {};
|
||||
/*! Rename a column, used with the change() method (MySQL). */
|
||||
QString renameTo {};
|
||||
|
||||
/*! Set the starting value of an auto-incrementing field (MySQL/PostgreSQL),
|
||||
alias for the 'startingValue'. */
|
||||
std::optional<quint64> from = std::nullopt;
|
||||
/*! Allow NULL values to be inserted into the column. */
|
||||
std::optional<bool> nullable = std::nullopt; // Has to be optional because of virtualAs() and storedAs(), look at MySqlSchemaGrammar::modifyNullable()
|
||||
/*! Set the starting value of an auto-incrementing field (MySQL/PostgreSQL). */
|
||||
std::optional<quint64> startingValue = std::nullopt;
|
||||
/*! Allow NULL values to be inserted into the column. */
|
||||
std::optional<bool> nullable = std::nullopt; // Has to be optional because of virtualAs() and storedAs(), look at MySQL::modifyNullable()
|
||||
/*! Create a stored generated column (MySQL/PostgreSQL/SQLite). */
|
||||
std::optional<QString> storedAs = std::nullopt; // Has to be optional because of modifyStoredAs(), look at PostgresSchemaGrammar and tst_PostgreSQL_SchemaBuilder::drop_StoredAs() (to support "drop expression if exists")
|
||||
/*! Create a virtual generated column (MySQL/PostgreSQL/SQLite). */
|
||||
std::optional<QString> virtualAs = std::nullopt; // Has to be optional because of modifyVirtualAs(), look at PostgresSchemaGrammar (to support "drop expression if exists")
|
||||
|
||||
// Place boolean data members at the end to avoid excessive padding
|
||||
/*! Used as a modifier for generatedAs() (PostgreSQL). */
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace Orm::SchemaNs
|
||||
ColumnReferenceType &always();
|
||||
/*! Set INTEGER column as auto-increment (primary key). */
|
||||
ColumnReferenceType &autoIncrement();
|
||||
/*! Change the column. */
|
||||
ColumnReferenceType &change();
|
||||
/*! Specify a character set for the column (MySQL). */
|
||||
ColumnReferenceType &charset(const QString &charset);
|
||||
/*! Specify a collation for the column (MySQL/PostgreSQL/SQL Server). */
|
||||
@@ -83,18 +85,24 @@ namespace Orm::SchemaNs
|
||||
/*! The spatial reference identifier (SRID) of a geometry identifies the SRS
|
||||
in which the geometry is defined (MySQL/PostgreSQL), alias for the 'srid'. */
|
||||
ColumnReferenceType &projection(quint32 value);
|
||||
/*! Rename a column, use with the change() method (MySQL). */
|
||||
ColumnReferenceType &renameTo(QString columnName);
|
||||
/*! 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). */
|
||||
/*! Create a stored generated column (MySQL/PostgreSQL/SQLite). With PostgreSQL
|
||||
use an empty or null QString() to drop a generated column along with
|
||||
the change() method call. */
|
||||
ColumnReferenceType &storedAs(QString expression);
|
||||
/*! Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value. */
|
||||
ColumnReferenceType &useCurrent();
|
||||
/*! Set the TIMESTAMP column to use CURRENT_TIMESTAMP when updating (MySQL). */
|
||||
ColumnReferenceType &useCurrentOnUpdate();
|
||||
/*! Create a virtual generated column (MySQL/PostgreSQL/SQLite). */
|
||||
/*! Create a virtual generated column (MySQL/PostgreSQL/SQLite). With PostgreSQL
|
||||
use an empty or null QString() to drop a generated column along with
|
||||
the change() method call. */
|
||||
ColumnReferenceType &virtualAs(QString expression);
|
||||
|
||||
/*! Add an index. */
|
||||
@@ -167,6 +175,15 @@ namespace Orm::SchemaNs
|
||||
return columnReference();
|
||||
}
|
||||
|
||||
template<ColumnReferenceReturn R>
|
||||
typename ColumnDefinitionReference<R>::ColumnReferenceType &
|
||||
ColumnDefinitionReference<R>::change()
|
||||
{
|
||||
m_columnDefinition.get().change = true;
|
||||
|
||||
return columnReference();
|
||||
}
|
||||
|
||||
template<ColumnReferenceReturn R>
|
||||
typename ColumnDefinitionReference<R>::ColumnReferenceType &
|
||||
ColumnDefinitionReference<R>::charset(const QString &charset)
|
||||
@@ -275,6 +292,15 @@ namespace Orm::SchemaNs
|
||||
return columnReference();
|
||||
}
|
||||
|
||||
template<ColumnReferenceReturn R>
|
||||
typename ColumnDefinitionReference<R>::ColumnReferenceType &
|
||||
ColumnDefinitionReference<R>::renameTo(QString columnName)
|
||||
{
|
||||
m_columnDefinition.get().renameTo = std::move(columnName);
|
||||
|
||||
return columnReference();
|
||||
}
|
||||
|
||||
template<ColumnReferenceReturn R>
|
||||
typename ColumnDefinitionReference<R>::ColumnReferenceType &
|
||||
ColumnDefinitionReference<R>::startingValue(const int startingValue)
|
||||
|
||||
@@ -73,6 +73,10 @@ namespace Grammars
|
||||
/*! Compile an add column command. */
|
||||
QVector<QString> compileAdd(const Blueprint &blueprint,
|
||||
const BasicCommand &command) const;
|
||||
/*! Compile a change column command. */
|
||||
QVector<QString> compileChange(const Blueprint &blueprint,
|
||||
const BasicCommand &command) const;
|
||||
|
||||
/*! Compile a drop column command. */
|
||||
QVector<QString> compileDropColumn(const Blueprint &blueprint,
|
||||
const DropColumnsCommand &command) const;
|
||||
|
||||
@@ -81,6 +81,9 @@ namespace Grammars
|
||||
/*! Compile an add column command. */
|
||||
QVector<QString> compileAdd(const Blueprint &blueprint,
|
||||
const BasicCommand &command) const;
|
||||
/*! Compile a change column command. */
|
||||
QVector<QString> compileChange(const Blueprint &blueprint,
|
||||
const BasicCommand &command) const;
|
||||
/*! Compile a drop column command. */
|
||||
QVector<QString> compileDropColumn(const Blueprint &blueprint,
|
||||
const DropColumnsCommand &command) const;
|
||||
@@ -157,6 +160,8 @@ namespace Grammars
|
||||
/*! Add the column modifiers to the definition. */
|
||||
QString addModifiers(QString &&sql,
|
||||
const ColumnDefinition &column) const override;
|
||||
/*! Add the column modifiers to the definition for "alter column" (change()). */
|
||||
QVector<QString> getModifiersForChange(const ColumnDefinition &column) const;
|
||||
|
||||
/*! Compile the auto-incrementing column starting value. */
|
||||
QVector<QString>
|
||||
@@ -259,19 +264,36 @@ namespace Grammars
|
||||
QString typeMultiPolygonZ(const ColumnDefinition &column) const;
|
||||
|
||||
/*! Get the SQL for a collation column modifier. */
|
||||
QString modifyCollate(const ColumnDefinition &column) const;
|
||||
QVector<QString> modifyCollate(const ColumnDefinition &column) const;
|
||||
/*! Get the SQL for an auto-increment column modifier. */
|
||||
QString modifyIncrement(const ColumnDefinition &column) const;
|
||||
QVector<QString> modifyIncrement(const ColumnDefinition &column) const;
|
||||
/*! Get the SQL for a nullable column modifier. */
|
||||
QString modifyNullable(const ColumnDefinition &column) const;
|
||||
QVector<QString> modifyNullable(const ColumnDefinition &column) const;
|
||||
/*! Get the SQL for a default column modifier. */
|
||||
QString modifyDefault(const ColumnDefinition &column) const;
|
||||
QVector<QString> modifyDefault(const ColumnDefinition &column) const;
|
||||
/*! Get the SQL for a generated virtual column modifier. */
|
||||
QString modifyVirtualAs(const ColumnDefinition &column) const;
|
||||
QVector<QString> modifyVirtualAs(const ColumnDefinition &column) const;
|
||||
/*! Get the SQL for a generated stored column modifier. */
|
||||
QString modifyStoredAs(const ColumnDefinition &column) const;
|
||||
QVector<QString> modifyStoredAs(const ColumnDefinition &column) const;
|
||||
/*! Get the SQL for an identity column modifier. */
|
||||
QString modifyGeneratedAs(const ColumnDefinition &column) const;
|
||||
QVector<QString> modifyGeneratedAs(const ColumnDefinition &column) const;
|
||||
|
||||
private:
|
||||
/*! Throw if modifying a generated column using the change() method. */
|
||||
static void throwIfModifyingGeneratedColumn();
|
||||
|
||||
/*! Modifier methods array for the "alter column" (change()). */
|
||||
constexpr static std::array m_modifierMethodsForChange {
|
||||
&PostgresSchemaGrammar::modifyIncrement,
|
||||
&PostgresSchemaGrammar::modifyNullable,
|
||||
&PostgresSchemaGrammar::modifyDefault,
|
||||
&PostgresSchemaGrammar::modifyVirtualAs,
|
||||
&PostgresSchemaGrammar::modifyStoredAs,
|
||||
&PostgresSchemaGrammar::modifyGeneratedAs,
|
||||
};
|
||||
/*! Size of the modifier methods array. */
|
||||
constexpr static auto
|
||||
m_modifierMethodsForChangeSize = m_modifierMethodsForChange.size();
|
||||
};
|
||||
|
||||
/* public */
|
||||
|
||||
@@ -67,6 +67,10 @@ namespace Grammars
|
||||
virtual QString compileColumnListing(const QString &table = "") const = 0;
|
||||
|
||||
/* Compile methods for commands */
|
||||
/*! Compile a change column command. */
|
||||
QVector<QString> compileChange(const Blueprint &blueprint,
|
||||
const BasicCommand &command) const;
|
||||
|
||||
/*! Compile a drop table command. */
|
||||
QVector<QString> compileDrop(const Blueprint &blueprint,
|
||||
const BasicCommand &command) const;
|
||||
|
||||
@@ -582,8 +582,8 @@ void Blueprint::addImpliedCommands(const SchemaGrammar &grammar)
|
||||
if (!getAddedColumns().isEmpty() && !creating())
|
||||
m_commands.emplace_front(createCommand<BasicCommand>({{}, Add}));
|
||||
|
||||
// if (!getChangedColumns().isEmpty() && !creating())
|
||||
// m_commands.prepend(createCommand(Change));
|
||||
if (!getChangedColumns().isEmpty() && !creating())
|
||||
m_commands.emplace_front(createCommand<BasicCommand>({{}, Change}));
|
||||
|
||||
addFluentIndexes();
|
||||
|
||||
@@ -676,7 +676,7 @@ void Blueprint::addFluentCommands(const SchemaGrammar &grammar)
|
||||
// Comment command (PostgreSQL)
|
||||
else if (commandName == Comment && std::invoke(shouldAdd, column)) T_UNLIKELY
|
||||
addCommand<CommentCommand>(
|
||||
{{}, commandName, column.name, column.comment});
|
||||
{{}, commandName, column.name, column.comment, column.change});
|
||||
}
|
||||
|
||||
IndexDefinitionReference
|
||||
|
||||
@@ -110,6 +110,33 @@ QVector<QString> MySqlSchemaGrammar::compileAdd(const Blueprint &blueprint,
|
||||
prefixArray(Add, getColumns(blueprint))))};
|
||||
}
|
||||
|
||||
QVector<QString> MySqlSchemaGrammar::compileChange(const Blueprint &blueprint,
|
||||
const BasicCommand &/*unused*/) const
|
||||
{
|
||||
auto changedColumns = blueprint.getChangedColumns();
|
||||
|
||||
QVector<QString> columns;
|
||||
columns.reserve(changedColumns.size());
|
||||
|
||||
for (auto &column : changedColumns) {
|
||||
const auto isRenaming = !column.renameTo.isEmpty();
|
||||
|
||||
columns << addModifiers(
|
||||
QStringLiteral("%1 %2%3 %4")
|
||||
.arg(isRenaming ? QStringLiteral("change")
|
||||
: QStringLiteral("modify"),
|
||||
wrap(column),
|
||||
isRenaming ? QStringLiteral(" %1")
|
||||
.arg(BaseGrammar::wrap(column.renameTo))
|
||||
: "",
|
||||
getType(column)),
|
||||
column);
|
||||
}
|
||||
|
||||
return {QStringLiteral("alter table %1 %2").arg(wrapTable(blueprint),
|
||||
columnizeWithoutWrap(columns))};
|
||||
}
|
||||
|
||||
QVector<QString>
|
||||
MySqlSchemaGrammar::compileDropColumn(const Blueprint &blueprint,
|
||||
const DropColumnsCommand &command) const
|
||||
@@ -254,6 +281,7 @@ MySqlSchemaGrammar::invokeCompileMethod(const CommandDefinition &command,
|
||||
QString(command.name) -> enum. */
|
||||
static const std::unordered_map<QString, CompileMemFn> cached {
|
||||
{Add, bind(&MySqlSchemaGrammar::compileAdd)},
|
||||
{Change, bind(&MySqlSchemaGrammar::compileChange)},
|
||||
{Rename, bind(&MySqlSchemaGrammar::compileRename)},
|
||||
{Drop, bind(&MySqlSchemaGrammar::compileDrop)},
|
||||
{DropIfExists, bind(&MySqlSchemaGrammar::compileDropIfExists)},
|
||||
@@ -839,18 +867,18 @@ QString MySqlSchemaGrammar::modifyCollate(const ColumnDefinition &column) const
|
||||
|
||||
QString MySqlSchemaGrammar::modifyVirtualAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
if (column.virtualAs.isEmpty())
|
||||
if (!column.virtualAs || column.virtualAs->isEmpty())
|
||||
return {};
|
||||
|
||||
return QStringLiteral(" generated always as (%1)").arg(column.virtualAs);
|
||||
return QStringLiteral(" generated always as (%1)").arg(*column.virtualAs);
|
||||
}
|
||||
|
||||
QString MySqlSchemaGrammar::modifyStoredAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
if (column.storedAs.isEmpty())
|
||||
if (!column.storedAs || column.storedAs->isEmpty())
|
||||
return {};
|
||||
|
||||
return QStringLiteral(" generated always as (%1) stored").arg(column.storedAs);
|
||||
return QStringLiteral(" generated always as (%1) stored").arg(*column.storedAs);
|
||||
}
|
||||
|
||||
QString MySqlSchemaGrammar::modifyNullable(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
@@ -860,7 +888,8 @@ QString MySqlSchemaGrammar::modifyNullable(const ColumnDefinition &column) const
|
||||
storedAs), it accepts both, null and also not null for generated columns, I have
|
||||
tried it. */
|
||||
if (!m_isMaria ||
|
||||
(column.virtualAs.isEmpty() && column.storedAs.isEmpty())
|
||||
((!column.virtualAs || column.virtualAs->isEmpty()) &&
|
||||
(!column.storedAs || column.storedAs->isEmpty()))
|
||||
)
|
||||
return column.nullable && *column.nullable ? QStringLiteral(" null")
|
||||
: QStringLiteral(" not null");
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <unordered_set>
|
||||
|
||||
#include "orm/databaseconnection.hpp"
|
||||
#include "orm/exceptions/logicerror.hpp"
|
||||
#include "orm/macros/likely.hpp"
|
||||
#include "orm/utils/type.hpp"
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
@@ -118,6 +120,38 @@ PostgresSchemaGrammar::compileAdd(const Blueprint &blueprint,
|
||||
getColumns(blueprint))))};
|
||||
}
|
||||
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::compileChange(const Blueprint &blueprint,
|
||||
const BasicCommand &/*unused*/) const
|
||||
{
|
||||
auto changedColumns = blueprint.getChangedColumns();
|
||||
|
||||
QVector<QString> columns;
|
||||
columns.reserve(changedColumns.size());
|
||||
|
||||
for (auto &column : changedColumns) {
|
||||
QVector<QString> changes;
|
||||
changes.reserve(m_modifierMethodsForChangeSize + 1);
|
||||
|
||||
const auto collate = modifyCollate(column);
|
||||
|
||||
// The column type with the collate has to be defined at once
|
||||
changes << QStringLiteral("type %1%2")
|
||||
.arg(getType(column),
|
||||
collate.isEmpty() ? EMPTY : collate.constFirst());
|
||||
|
||||
// All other modifiers have to be alone so the "alter column" can be prepended
|
||||
changes << getModifiersForChange(column);
|
||||
|
||||
columns << columnizeWithoutWrap(
|
||||
prefixArray(QStringLiteral("alter column %1").arg(wrap(column)),
|
||||
changes));
|
||||
}
|
||||
|
||||
return {QStringLiteral("alter table %1 %2").arg(wrapTable(blueprint),
|
||||
columnizeWithoutWrap(columns))};
|
||||
}
|
||||
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::compileDropColumn(const Blueprint &blueprint,
|
||||
const DropColumnsCommand &command) const
|
||||
@@ -258,12 +292,16 @@ QVector<QString>
|
||||
PostgresSchemaGrammar::compileComment(const Blueprint &blueprint,
|
||||
const CommentCommand &command) const
|
||||
{
|
||||
const auto isCommentEmpty = command.comment.isEmpty();
|
||||
|
||||
if (isCommentEmpty && !command.change)
|
||||
return {};
|
||||
|
||||
return {QStringLiteral("comment on column %1.%2 is %3")
|
||||
.arg(wrapTable(blueprint), BaseGrammar::wrap(command.column),
|
||||
command.column.isEmpty()
|
||||
// Remove a column comment
|
||||
? null_
|
||||
: quoteString(escapeString(command.comment)))};
|
||||
// Remove a column comment (used during change())
|
||||
isCommentEmpty ? null_
|
||||
: quoteString(escapeString(command.comment)))};
|
||||
}
|
||||
|
||||
QVector<QString>
|
||||
@@ -317,6 +355,7 @@ PostgresSchemaGrammar::invokeCompileMethod(const CommandDefinition &command,
|
||||
QString(command.name) -> enum. */
|
||||
static const std::unordered_map<QString, CompileMemFn> cached {
|
||||
{Add, bind(&PostgresSchemaGrammar::compileAdd)},
|
||||
{Change, bind(&PostgresSchemaGrammar::compileChange)},
|
||||
{Rename, bind(&PostgresSchemaGrammar::compileRename)},
|
||||
{Drop, bind(&PostgresSchemaGrammar::compileDrop)},
|
||||
{DropIfExists, bind(&PostgresSchemaGrammar::compileDropIfExists)},
|
||||
@@ -384,11 +423,27 @@ QString PostgresSchemaGrammar::addModifiers(QString &&sql,
|
||||
};
|
||||
|
||||
for (const auto method : modifierMethods)
|
||||
sql += std::invoke(method, this, column);
|
||||
/* Postgres is different here, it returns a vector as it needs to return
|
||||
2 modifiers from the modifyGeneratedAs(). */
|
||||
sql += ContainerUtils::join(
|
||||
std::invoke(method, this, column), EMPTY);
|
||||
|
||||
return std::move(sql);
|
||||
}
|
||||
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::getModifiersForChange(const ColumnDefinition &column) const
|
||||
{
|
||||
QVector<QString> modifiers;
|
||||
modifiers.reserve(static_cast<QVector<QString>::size_type>(
|
||||
m_modifierMethodsForChangeSize));
|
||||
|
||||
for (const auto method : m_modifierMethodsForChange)
|
||||
modifiers << std::invoke(method, this, column);
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::compileAutoIncrementStartingValue( // NOLINT(readability-convert-member-functions-to-static)
|
||||
const Blueprint &blueprint,
|
||||
@@ -804,81 +859,143 @@ QString PostgresSchemaGrammar::typeMultiPolygonZ(const ColumnDefinition &column)
|
||||
return formatPostGisType(QStringLiteral("multipolygonz"), column);
|
||||
}
|
||||
|
||||
QString PostgresSchemaGrammar::modifyCollate(const ColumnDefinition &column) const
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::modifyCollate(const ColumnDefinition &column) const
|
||||
{
|
||||
if (column.collation.isEmpty())
|
||||
return {};
|
||||
|
||||
return QStringLiteral(" collate %1").arg(wrapValue(column.collation));
|
||||
return {QStringLiteral(" collate %1").arg(wrapValue(column.collation))};
|
||||
}
|
||||
|
||||
QString PostgresSchemaGrammar::modifyIncrement(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::modifyIncrement(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
static const std::unordered_set serials {
|
||||
ColumnType::BigInteger, ColumnType::Integer, ColumnType::MediumInteger,
|
||||
ColumnType::SmallInteger, ColumnType::TinyInteger,
|
||||
};
|
||||
|
||||
if ((serials.contains(column.type) || !column.generatedAs.isNull()) &&
|
||||
// I'm not going to invert this condition for the early return 🤯
|
||||
if (!column.change &&
|
||||
(serials.contains(column.type) || !column.generatedAs.isNull()) && // Can't be generatedAs.isEmpty()!
|
||||
column.autoIncrement
|
||||
)
|
||||
return QStringLiteral(" primary key");
|
||||
return {QStringLiteral(" primary key")};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QString PostgresSchemaGrammar::modifyNullable(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::modifyNullable(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
if (column.change)
|
||||
return {column.nullable && *column.nullable ? QStringLiteral("drop not null")
|
||||
: QStringLiteral("set not null")};
|
||||
|
||||
/* PostgreSQL doesn't need any special logic for generated columns (virtualAs and
|
||||
storedAs), it accepts both, null and also not null for generated columns, I have
|
||||
tried it. */
|
||||
return column.nullable && *column.nullable ? QStringLiteral(" null")
|
||||
: QStringLiteral(" not null");
|
||||
return {column.nullable && *column.nullable ? QStringLiteral(" null")
|
||||
: QStringLiteral(" not null")};
|
||||
}
|
||||
|
||||
QString PostgresSchemaGrammar::modifyDefault(const ColumnDefinition &column) const
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::modifyDefault(const ColumnDefinition &column) const
|
||||
{
|
||||
const auto &defaultValue = column.defaultValue;
|
||||
|
||||
if (!defaultValue.isValid() || defaultValue.isNull())
|
||||
return {};
|
||||
const auto isNotValidOrNull = !defaultValue.isValid() || defaultValue.isNull();
|
||||
|
||||
// Default value is already quoted and escaped inside the getDefaultValue()
|
||||
return QStringLiteral(" default %1").arg(getDefaultValue(defaultValue));
|
||||
|
||||
if (column.change)
|
||||
return {isNotValidOrNull ? QStringLiteral("drop default")
|
||||
: QStringLiteral("set default %1")
|
||||
.arg(getDefaultValue(defaultValue))};
|
||||
|
||||
if (isNotValidOrNull)
|
||||
return {};
|
||||
|
||||
return {QStringLiteral(" default %1").arg(getDefaultValue(defaultValue))};
|
||||
}
|
||||
|
||||
QString PostgresSchemaGrammar::modifyVirtualAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::modifyVirtualAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
/* Currently, PostgreSQL 15 doesn't support virtual generated columns, only stored,
|
||||
so this is useless. */
|
||||
if (column.virtualAs.isEmpty())
|
||||
so this method is useless. */
|
||||
|
||||
if (!column.virtualAs)
|
||||
return {};
|
||||
|
||||
return QStringLiteral(" generated always as (%1)").arg(column.virtualAs);
|
||||
if (column.change) {
|
||||
if (column.virtualAs->isEmpty()) T_LIKELY
|
||||
return {QStringLiteral("drop expression if exists")};
|
||||
|
||||
else T_UNLIKELY
|
||||
throwIfModifyingGeneratedColumn();
|
||||
}
|
||||
|
||||
return {QStringLiteral(" generated always as (%1)").arg(*column.virtualAs)};
|
||||
}
|
||||
|
||||
QString PostgresSchemaGrammar::modifyStoredAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::modifyStoredAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
if (column.storedAs.isEmpty())
|
||||
if (!column.storedAs)
|
||||
return {};
|
||||
|
||||
return QStringLiteral(" generated always as (%1) stored").arg(column.storedAs);
|
||||
if (column.change) {
|
||||
if (column.storedAs->isEmpty()) T_LIKELY
|
||||
return {QStringLiteral("drop expression if exists")};
|
||||
|
||||
else T_UNLIKELY
|
||||
throwIfModifyingGeneratedColumn();
|
||||
}
|
||||
|
||||
return {QStringLiteral(" generated always as (%1) stored").arg(*column.storedAs)};
|
||||
}
|
||||
|
||||
QString PostgresSchemaGrammar::modifyGeneratedAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
QVector<QString>
|
||||
PostgresSchemaGrammar::modifyGeneratedAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
// Nothing to do, generatedAs was not defined
|
||||
if (column.generatedAs.isNull())
|
||||
return {};
|
||||
QString sql;
|
||||
sql.reserve(100);
|
||||
|
||||
return QStringLiteral(" generated %1 as identity%2")
|
||||
.arg(
|
||||
// ALWAYS and BY DEFAULT clause
|
||||
column.always ? QStringLiteral("always") : QStringLiteral("by default"),
|
||||
// Sequence options clause
|
||||
!column.generatedAs.isEmpty() ? QStringLiteral(" (%1)")
|
||||
.arg(column.generatedAs)
|
||||
: QString(""));
|
||||
/* generatedAs.isNull() mean it was not defined at all, and isEmpty() it was called
|
||||
like generatedAs(). */
|
||||
if (!column.generatedAs.isNull())
|
||||
sql += QStringLiteral(" generated %1 as identity%2")
|
||||
.arg(
|
||||
// ALWAYS and BY DEFAULT clause
|
||||
column.always ? QStringLiteral("always")
|
||||
: QStringLiteral("by default"),
|
||||
// Sequence options clause
|
||||
!column.generatedAs.isEmpty() ? QStringLiteral(" (%1)")
|
||||
.arg(column.generatedAs)
|
||||
: QString(""));
|
||||
|
||||
if (column.change) {
|
||||
QVector<QString> changes;
|
||||
changes.reserve(2);
|
||||
|
||||
changes << QStringLiteral("drop identity if exists");
|
||||
|
||||
if (!sql.isEmpty())
|
||||
changes << std::move(sql.prepend(Add));
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
return {sql};
|
||||
}
|
||||
|
||||
/* private */
|
||||
|
||||
void PostgresSchemaGrammar::throwIfModifyingGeneratedColumn()
|
||||
{
|
||||
throw Exceptions::LogicError(
|
||||
"The PostgreSQL database does not support modifying generated columns.");
|
||||
}
|
||||
|
||||
} // namespace Orm::SchemaNs::Grammars
|
||||
|
||||
@@ -55,6 +55,13 @@ QString SchemaGrammar::compileTableExists() const
|
||||
throw Exceptions::RuntimeError(NotImplemented);
|
||||
}
|
||||
|
||||
QVector<QString> SchemaGrammar::compileChange(const Blueprint &/*unused*/,
|
||||
const BasicCommand &/*unused*/) const
|
||||
{
|
||||
throw Exceptions::LogicError(
|
||||
"This database driver does not support changing columns.");
|
||||
}
|
||||
|
||||
/* Compile methods for commands */
|
||||
|
||||
QVector<QString>
|
||||
|
||||
@@ -253,6 +253,7 @@ SQLiteSchemaGrammar::invokeCompileMethod(const CommandDefinition &command,
|
||||
QString(command.name) -> enum. */
|
||||
static const std::unordered_map<QString, CompileMemFn> cached {
|
||||
{Add, bind(&SQLiteSchemaGrammar::compileAdd)},
|
||||
{Change, bind(&SQLiteSchemaGrammar::compileChange)},
|
||||
{Rename, bind(&SQLiteSchemaGrammar::compileRename)},
|
||||
{Drop, bind(&SQLiteSchemaGrammar::compileDrop)},
|
||||
{DropIfExists, bind(&SQLiteSchemaGrammar::compileDropIfExists)},
|
||||
@@ -762,19 +763,19 @@ QString SQLiteSchemaGrammar::typeComputed(const ColumnDefinition &/*unused*/) co
|
||||
QString SQLiteSchemaGrammar::modifyVirtualAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
// FEATURE schema json silverqx
|
||||
if (column.virtualAs.isEmpty())
|
||||
if (!column.virtualAs || column.virtualAs->isEmpty())
|
||||
return {};
|
||||
|
||||
return QStringLiteral(" generated always as (%1)").arg(column.virtualAs);
|
||||
return QStringLiteral(" generated always as (%1)").arg(*column.virtualAs);
|
||||
}
|
||||
|
||||
QString SQLiteSchemaGrammar::modifyStoredAs(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
{
|
||||
// FEATURE schema json silverqx
|
||||
if (column.storedAs.isEmpty())
|
||||
if (!column.storedAs || column.storedAs->isEmpty())
|
||||
return {};
|
||||
|
||||
return QStringLiteral(" generated always as (%1) stored").arg(column.storedAs);
|
||||
return QStringLiteral(" generated always as (%1) stored").arg(*column.storedAs);
|
||||
}
|
||||
|
||||
QString SQLiteSchemaGrammar::modifyNullable(const ColumnDefinition &column) const // NOLINT(readability-convert-member-functions-to-static)
|
||||
@@ -793,8 +794,13 @@ QString SQLiteSchemaGrammar::modifyDefault(const ColumnDefinition &column) const
|
||||
{
|
||||
const auto &defaultValue = column.defaultValue;
|
||||
|
||||
/* From SQLite docs:
|
||||
Generated columns may not have a default value (they may not use the "DEFAULT"
|
||||
clause). The value of a generated column is always the value specified by
|
||||
the expression that follows the "AS" keyword. */
|
||||
if (!defaultValue.isValid() || defaultValue.isNull() ||
|
||||
!column.virtualAs.isEmpty() || !column.storedAs.isEmpty()
|
||||
(column.virtualAs && !column.virtualAs->isEmpty()) ||
|
||||
(column.storedAs && !column.storedAs->isEmpty())
|
||||
)
|
||||
return {};
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ private Q_SLOTS:
|
||||
void modifier_defaultValue_WithBoolean() const;
|
||||
void modifier_defaultValue_Escaping() const;
|
||||
|
||||
void change_modifiers() const;
|
||||
|
||||
void useCurrent() const;
|
||||
void useCurrentOnUpdate() const;
|
||||
|
||||
@@ -1056,6 +1058,88 @@ and tab end)");
|
||||
QVERIFY(firstLog.boundValues.isEmpty());
|
||||
}
|
||||
|
||||
void tst_MySql_SchemaBuilder::change_modifiers() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
{
|
||||
Schema::on(connection.getName())
|
||||
.table(Firewalls, [](Blueprint &table)
|
||||
{
|
||||
table.bigInteger(ID).autoIncrement().isUnsigned().startingValue(5).change();
|
||||
table.bigInteger("big_int").isUnsigned().change();
|
||||
table.bigInteger("big_int1").change();
|
||||
table.string(NAME).defaultValue("guest").change();
|
||||
table.string("name1").nullable().change();
|
||||
table.string("name2").comment("name2 note").change();
|
||||
table.string("name3", 191).change();
|
||||
table.string("name4").invisible().change();
|
||||
table.string("name5").charset(UTF8).change();
|
||||
table.string("name6").collation("utf8mb4_unicode_ci").change();
|
||||
table.string("name7").charset(UTF8).collation(UTF8Unicodeci).change();
|
||||
table.string("name8_old", 64).renameTo("name8").change();
|
||||
table.Double("amount", 6, 2).change();
|
||||
table.multiPolygon("positions").srid(1234).storedAs("expression").change();
|
||||
table.multiPoint("positions1").srid(1234).virtualAs("expression").nullable()
|
||||
.change();
|
||||
table.timestamp("added_on").nullable(false).useCurrent().change();
|
||||
table.timestamp("updated_at", 4).useCurrent().useCurrentOnUpdate().change();
|
||||
});
|
||||
/* 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.string(NAME).after("big_int").change();
|
||||
table.integerIncrements(ID).from(15).first().change();
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(log.size(), 4);
|
||||
|
||||
const auto &log0 = log.at(0);
|
||||
QCOMPARE(log0.query,
|
||||
"alter table `firewalls` "
|
||||
"modify `id` bigint unsigned not null auto_increment primary key, "
|
||||
"modify `big_int` bigint unsigned not null, "
|
||||
"modify `big_int1` bigint not null, "
|
||||
"modify `name` varchar(255) not null default 'guest', "
|
||||
"modify `name1` varchar(255) null, "
|
||||
"modify `name2` varchar(255) not null comment 'name2 note', "
|
||||
"modify `name3` varchar(191) not null, "
|
||||
"modify `name4` varchar(255) not null invisible, "
|
||||
"modify `name5` varchar(255) character set 'utf8' not null, "
|
||||
"modify `name6` varchar(255) collate 'utf8mb4_unicode_ci' not null, "
|
||||
"modify `name7` varchar(255) character set 'utf8' collate 'utf8_unicode_ci' "
|
||||
"not null, "
|
||||
"change `name8_old` `name8` varchar(64) not null, "
|
||||
"modify `amount` double(6, 2) not null, "
|
||||
"modify `positions` multipolygon generated always as (expression) stored "
|
||||
"not null srid 1234, "
|
||||
"modify `positions1` multipoint generated always as (expression) "
|
||||
"null srid 1234, "
|
||||
"modify `added_on` timestamp not null default current_timestamp, "
|
||||
"modify `updated_at` timestamp(4) not null default current_timestamp(4) "
|
||||
"on update current_timestamp(4)");
|
||||
QVERIFY(log0.boundValues.isEmpty());
|
||||
|
||||
const auto &log1 = log.at(1);
|
||||
QCOMPARE(log1.query,
|
||||
"alter table `firewalls` auto_increment = 5");
|
||||
QVERIFY(log1.boundValues.isEmpty());
|
||||
|
||||
const auto &log2 = log.at(2);
|
||||
QCOMPARE(log2.query,
|
||||
"alter table `firewalls` "
|
||||
"modify `name` varchar(255) not null after `big_int`, "
|
||||
"modify `id` int unsigned not null auto_increment primary key first");
|
||||
QVERIFY(log2.boundValues.isEmpty());
|
||||
|
||||
const auto &log3 = log.at(3);
|
||||
QCOMPARE(log3.query,
|
||||
"alter table `firewalls` auto_increment = 15");
|
||||
QVERIFY(log3.boundValues.isEmpty());
|
||||
}
|
||||
|
||||
void tst_MySql_SchemaBuilder::useCurrent() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
|
||||
@@ -91,6 +91,8 @@ private Q_SLOTS:
|
||||
void modifier_defaultValue_WithBoolean() const;
|
||||
void modifier_defaultValue_Escaping() const;
|
||||
|
||||
void change_modifiers() const;
|
||||
|
||||
void useCurrent() const;
|
||||
void useCurrentOnUpdate() const;
|
||||
|
||||
@@ -110,6 +112,10 @@ private Q_SLOTS:
|
||||
void virtualAs_StoredAs_ModifyTable() const;
|
||||
void virtualAs_StoredAs_Nullable_ModifyTable() const;
|
||||
|
||||
void change_VirtualAs_ThrowException() const;
|
||||
void change_StoredAs_ThrowException() const;
|
||||
void drop_StoredAs() const;
|
||||
|
||||
void indexes_Fluent() const;
|
||||
void indexes_Blueprint() const;
|
||||
|
||||
@@ -1255,6 +1261,212 @@ and tab end)");
|
||||
QVERIFY(firstLog.boundValues.isEmpty());
|
||||
}
|
||||
|
||||
void tst_PostgreSQL_SchemaBuilder::change_modifiers() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
{
|
||||
Schema::on(connection.getName())
|
||||
.table(Firewalls, [](Blueprint &table)
|
||||
{
|
||||
table.bigInteger(ID).autoIncrement().startingValue(5).change();
|
||||
// PostgreSQL doesn't support signed modifier or signed numbers
|
||||
table.bigInteger("big_int").isUnsigned().change();
|
||||
table.bigInteger("big_int1").change();
|
||||
table.string(NAME).defaultValue("guest").change();
|
||||
table.string("name1").nullable().change();
|
||||
table.string("name2").comment("name2 note").change();
|
||||
table.string("name3", 191).change();
|
||||
// PostgreSQL doesn't support invisible columns
|
||||
// table.string("name4").invisible().change();
|
||||
// PostgreSQL doesn't support charset on the column
|
||||
table.string("name5").charset(UTF8).change();
|
||||
table.string("name6").collation(UcsBasic).change();
|
||||
// PostgreSQL doesn't support charset on the column
|
||||
table.string("name7").charset(UTF8).collation(UcsBasic).change();
|
||||
// PostgreSQL doesn't support renaming columns during the change() call
|
||||
// table.string("name8_old", 64).renameTo("name8").change();
|
||||
table.Double("amount", 6, 2).change();
|
||||
// PostgreSQL doesn't support changing generated columns
|
||||
// table.multiPolygon("positions").srid(1234).storedAs("expression").change();
|
||||
table.point("positions1").isGeometry().projection(1234).change();
|
||||
table.timestamp("added_on").nullable(false).useCurrent().change();
|
||||
// PostgreSQL doesn't support useCurrentOnUpdate()
|
||||
// table.timestamp("updated_at", 4).useCurrent().useCurrentOnUpdate().change();
|
||||
});
|
||||
/* 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).change();
|
||||
});
|
||||
});
|
||||
|
||||
// The following is really wild 🤯
|
||||
|
||||
QCOMPARE(log.size(), 18);
|
||||
|
||||
const auto &log0 = log.at(0);
|
||||
QCOMPARE(log0.query,
|
||||
"alter table \"firewalls\" "
|
||||
|
||||
"alter column \"id\" type bigserial, "
|
||||
"alter column \"id\" set not null, "
|
||||
"alter column \"id\" drop default, "
|
||||
"alter column \"id\" drop identity if exists, "
|
||||
|
||||
"alter column \"big_int\" type bigint, "
|
||||
"alter column \"big_int\" set not null, "
|
||||
"alter column \"big_int\" drop default, "
|
||||
"alter column \"big_int\" drop identity if exists, "
|
||||
|
||||
"alter column \"big_int1\" type bigint, "
|
||||
"alter column \"big_int1\" set not null, "
|
||||
"alter column \"big_int1\" drop default, "
|
||||
"alter column \"big_int1\" drop identity if exists, "
|
||||
|
||||
"alter column \"name\" type varchar(255), "
|
||||
"alter column \"name\" set not null, "
|
||||
"alter column \"name\" set default 'guest', "
|
||||
"alter column \"name\" drop identity if exists, "
|
||||
|
||||
"alter column \"name1\" type varchar(255), "
|
||||
"alter column \"name1\" drop not null, "
|
||||
"alter column \"name1\" drop default, "
|
||||
"alter column \"name1\" drop identity if exists, "
|
||||
|
||||
"alter column \"name2\" type varchar(255), "
|
||||
"alter column \"name2\" set not null, "
|
||||
"alter column \"name2\" drop default, "
|
||||
"alter column \"name2\" drop identity if exists, "
|
||||
|
||||
"alter column \"name3\" type varchar(191), "
|
||||
"alter column \"name3\" set not null, "
|
||||
"alter column \"name3\" drop default, "
|
||||
"alter column \"name3\" drop identity if exists, "
|
||||
|
||||
"alter column \"name5\" type varchar(255), "
|
||||
"alter column \"name5\" set not null, "
|
||||
"alter column \"name5\" drop default, "
|
||||
"alter column \"name5\" drop identity if exists, "
|
||||
|
||||
"alter column \"name6\" type varchar(255) collate \"ucs_basic\", "
|
||||
"alter column \"name6\" set not null, "
|
||||
"alter column \"name6\" drop default, "
|
||||
"alter column \"name6\" drop identity if exists, "
|
||||
|
||||
"alter column \"name7\" type varchar(255) collate \"ucs_basic\", "
|
||||
"alter column \"name7\" set not null, "
|
||||
"alter column \"name7\" drop default, "
|
||||
"alter column \"name7\" drop identity if exists, "
|
||||
|
||||
"alter column \"amount\" type double precision, "
|
||||
"alter column \"amount\" set not null, "
|
||||
"alter column \"amount\" drop default, "
|
||||
"alter column \"amount\" drop identity if exists, "
|
||||
|
||||
"alter column \"positions1\" type geometry(point, 1234), "
|
||||
"alter column \"positions1\" set not null, "
|
||||
"alter column \"positions1\" drop default, "
|
||||
"alter column \"positions1\" drop identity if exists, "
|
||||
|
||||
"alter column \"added_on\" type timestamp(0) without time zone, "
|
||||
"alter column \"added_on\" set not null, "
|
||||
"alter column \"added_on\" set default current_timestamp, "
|
||||
"alter column \"added_on\" drop identity if exists");
|
||||
|
||||
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"."id" is null)");
|
||||
QVERIFY(log2.boundValues.isEmpty());
|
||||
|
||||
const auto &log3 = log.at(3);
|
||||
QCOMPARE(log3.query,
|
||||
R"(comment on column "firewalls"."big_int" is null)");
|
||||
QVERIFY(log3.boundValues.isEmpty());
|
||||
|
||||
const auto &log4 = log.at(4);
|
||||
QCOMPARE(log4.query,
|
||||
R"(comment on column "firewalls"."big_int1" is null)");
|
||||
QVERIFY(log4.boundValues.isEmpty());
|
||||
|
||||
const auto &log5 = log.at(5);
|
||||
QCOMPARE(log5.query,
|
||||
R"(comment on column "firewalls"."name" is null)");
|
||||
QVERIFY(log5.boundValues.isEmpty());
|
||||
|
||||
const auto &log6 = log.at(6);
|
||||
QCOMPARE(log6.query,
|
||||
R"(comment on column "firewalls"."name1" is null)");
|
||||
QVERIFY(log6.boundValues.isEmpty());
|
||||
|
||||
const auto &log7 = log.at(7);
|
||||
QCOMPARE(log7.query,
|
||||
R"(comment on column "firewalls"."name2" is 'name2 note')");
|
||||
QVERIFY(log7.boundValues.isEmpty());
|
||||
|
||||
const auto &log8 = log.at(8);
|
||||
QCOMPARE(log8.query,
|
||||
R"(comment on column "firewalls"."name3" is null)");
|
||||
QVERIFY(log8.boundValues.isEmpty());
|
||||
|
||||
const auto &log9 = log.at(9);
|
||||
QCOMPARE(log9.query,
|
||||
R"(comment on column "firewalls"."name5" is null)");
|
||||
QVERIFY(log9.boundValues.isEmpty());
|
||||
|
||||
const auto &log10 = log.at(10);
|
||||
QCOMPARE(log10.query,
|
||||
R"(comment on column "firewalls"."name6" is null)");
|
||||
QVERIFY(log10.boundValues.isEmpty());
|
||||
|
||||
const auto &log11 = log.at(11);
|
||||
QCOMPARE(log11.query,
|
||||
R"(comment on column "firewalls"."name7" is null)");
|
||||
QVERIFY(log11.boundValues.isEmpty());
|
||||
|
||||
const auto &log12 = log.at(12);
|
||||
QCOMPARE(log12.query,
|
||||
R"(comment on column "firewalls"."amount" is null)");
|
||||
QVERIFY(log12.boundValues.isEmpty());
|
||||
|
||||
const auto &log13 = log.at(13);
|
||||
QCOMPARE(log13.query,
|
||||
R"(comment on column "firewalls"."positions1" is null)");
|
||||
QVERIFY(log13.boundValues.isEmpty());
|
||||
|
||||
const auto &log14 = log.at(14);
|
||||
QCOMPARE(log14.query,
|
||||
R"(comment on column "firewalls"."added_on" is null)");
|
||||
QVERIFY(log14.boundValues.isEmpty());
|
||||
|
||||
const auto &log15 = log.at(15);
|
||||
QCOMPARE(log15.query,
|
||||
"alter table \"firewalls\" "
|
||||
"alter column \"id\" type serial, "
|
||||
"alter column \"id\" set not null, "
|
||||
"alter column \"id\" drop default, "
|
||||
"alter column \"id\" drop identity if exists");
|
||||
QVERIFY(log15.boundValues.isEmpty());
|
||||
|
||||
const auto &log16 = log.at(16);
|
||||
QCOMPARE(log16.query,
|
||||
R"(alter sequence "firewalls_id_seq" restart with 15)");
|
||||
QVERIFY(log16.boundValues.isEmpty());
|
||||
|
||||
const auto &log17 = log.at(17);
|
||||
QCOMPARE(log17.query,
|
||||
R"(comment on column "firewalls"."id" is null)");
|
||||
QVERIFY(log17.boundValues.isEmpty());
|
||||
}
|
||||
|
||||
void tst_PostgreSQL_SchemaBuilder::useCurrent() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
@@ -1636,6 +1848,70 @@ void tst_PostgreSQL_SchemaBuilder::virtualAs_StoredAs_Nullable_ModifyTable() con
|
||||
QVERIFY(firstLog.boundValues.isEmpty());
|
||||
}
|
||||
|
||||
void tst_PostgreSQL_SchemaBuilder::change_VirtualAs_ThrowException() const
|
||||
{
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
DB::connection(m_connection).pretend([](auto &connection)
|
||||
{
|
||||
Schema::on(connection.getName())
|
||||
.table(Firewalls, [](Blueprint &table)
|
||||
{
|
||||
/* Currently, PostgreSQL 15 doesn't support virtual generated columns,
|
||||
only stored, but I test it anyway. Changing generated column must throw
|
||||
exception, PostgreSQL doesn't support modifying generated columns. */
|
||||
table.integer("discounted_virtual").virtualAs("price - 5").change();
|
||||
});
|
||||
}),
|
||||
LogicError);
|
||||
}
|
||||
|
||||
void tst_PostgreSQL_SchemaBuilder::change_StoredAs_ThrowException() const
|
||||
{
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
DB::connection(m_connection).pretend([](auto &connection)
|
||||
{
|
||||
Schema::on(connection.getName())
|
||||
.table(Firewalls, [](Blueprint &table)
|
||||
{
|
||||
/* Currently, PostgreSQL 15 doesn't support virtual generated columns,
|
||||
only stored, but I test it anyway. Changing generated column must throw
|
||||
exception, PostgreSQL doesn't support modifying generated columns. */
|
||||
table.integer("discounted_virtual").storedAs("price - 5").change();
|
||||
});
|
||||
}),
|
||||
LogicError);
|
||||
}
|
||||
|
||||
void tst_PostgreSQL_SchemaBuilder::drop_StoredAs() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
{
|
||||
Schema::on(connection.getName())
|
||||
.table(Firewalls, [](Blueprint &table)
|
||||
{
|
||||
// Because of this the CommandDefinition::storedAs must be std::optional
|
||||
table.integer("foo").storedAs(QString()).nullable().change();
|
||||
});
|
||||
});
|
||||
|
||||
QCOMPARE(log.size(), 2);
|
||||
|
||||
const auto &log0 = log.at(0);
|
||||
QCOMPARE(log0.query,
|
||||
"alter table \"firewalls\" "
|
||||
"alter column \"foo\" type integer, "
|
||||
"alter column \"foo\" drop not null, "
|
||||
"alter column \"foo\" drop default, "
|
||||
"alter column \"foo\" drop expression if exists, "
|
||||
"alter column \"foo\" drop identity if exists");
|
||||
QVERIFY(log0.boundValues.isEmpty());
|
||||
|
||||
const auto &log1 = log.at(1);
|
||||
QCOMPARE(log1.query,
|
||||
R"(comment on column "firewalls"."foo" is null)");
|
||||
QVERIFY(log1.boundValues.isEmpty());
|
||||
}
|
||||
|
||||
void tst_PostgreSQL_SchemaBuilder::indexes_Fluent() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
|
||||
Reference in New Issue
Block a user