mirror of
https://github.com/silverqx/TinyORM.git
synced 2025-12-20 09:59:53 -06:00
use upsert alias on MySQL
The MySQL >=8.0.19 supports aliases in the VALUES and SET clauses of INSERT INTO ... ON DUPLICATE KEY UPDATE statement for the row to be inserted and its columns. It also generates warning from >=8.0.20 if an old style used. This enhancement detects the MySQL version and on the base of it uses alias during the upsert call. Also a user can override the version through the MySQL database configuration. It helps to save/avoid one database query (select version()) during the upsert method call or during connecting to the MySQL database (version is needed if strict mode is enabled). - added unit and functional tests - updated number of unit tests to 1402 - updated upsert docs - added ConfigUtils to avoid duplicates Others: - added the version database configuration everywhere - docs added few lines about MySQL version configuration option - docs updated database configurations, added a new missing options
This commit is contained in:
@@ -74,9 +74,10 @@ Whole library is documented as markdown documents:
|
||||
- the `tom` console application with tab completion for all shells (pwsh, bash, zsh) 🥳
|
||||
- scaffolding of models, migrations, and seeders
|
||||
- overhauled models scaffolding, every feature that is supported by models can be generated using the `tom make:model` cli command
|
||||
- a huge amount of code is unit tested, currently __1393 unit tests__ 🤯
|
||||
- a huge amount of code is unit tested, currently __1402 unit tests__ 🤯
|
||||
- C++20 only, with all the latest features used like concepts/constraints, ranges, smart pointers (no `new` keyword in the whole code 😎), folding expressions
|
||||
- qmake and CMake build systems support
|
||||
- CMake FetchContent module support 🤙
|
||||
- vcpkg support (also the vcpkg port, currently not committed to the vcpkg repository ☹️)
|
||||
- it's really fast, you can run 1000 complex queries in 500ms (heavily DB dependant, the PostgreSQL is by far the fastest) ⌚
|
||||
- extensive documentation 📃
|
||||
|
||||
@@ -97,6 +97,7 @@ function(tinyorm_sources out_headers out_sources)
|
||||
support/databaseconnectionsmap.hpp
|
||||
types/log.hpp
|
||||
types/statementscounter.hpp
|
||||
utils/configuration.hpp
|
||||
utils/container.hpp
|
||||
utils/fs.hpp
|
||||
utils/helpers.hpp
|
||||
@@ -211,6 +212,7 @@ function(tinyorm_sources out_headers out_sources)
|
||||
schema/sqliteschemabuilder.cpp
|
||||
sqliteconnection.cpp
|
||||
support/configurationoptionsparser.cpp
|
||||
utils/configuration.cpp
|
||||
utils/fs.cpp
|
||||
utils/query.cpp
|
||||
utils/thread.cpp
|
||||
|
||||
@@ -196,6 +196,7 @@ And paste the following code.
|
||||
{strict_, true},
|
||||
{isolation_level, QStringLiteral("REPEATABLE READ")},
|
||||
{engine_, InnoDB},
|
||||
{Version, {}}, // Autodetect
|
||||
{options_, QVariantHash()},
|
||||
},
|
||||
QStringLiteral("tinyorm_tom"));
|
||||
|
||||
@@ -39,17 +39,22 @@ You can create and configure new database connection by `create` method provided
|
||||
|
||||
// Ownership of a shared_ptr()
|
||||
auto manager = DB::create({
|
||||
{"driver", "QMYSQL"},
|
||||
{"host", qEnvironmentVariable("DB_HOST", "127.0.0.1")},
|
||||
{"port", qEnvironmentVariable("DB_PORT", "3306")},
|
||||
{"database", qEnvironmentVariable("DB_DATABASE", "")},
|
||||
{"username", qEnvironmentVariable("DB_USERNAME", "root")},
|
||||
{"password", qEnvironmentVariable("DB_PASSWORD", "")},
|
||||
{"charset", qEnvironmentVariable("DB_CHARSET", "utf8mb4")},
|
||||
{"collation", qEnvironmentVariable("DB_COLLATION", "utf8mb4_0900_ai_ci")},
|
||||
{"timezone", "+00:00"},
|
||||
{"strict", true},
|
||||
{"options", QVariantHash()},
|
||||
{"driver", "QMYSQL"},
|
||||
{"host", qEnvironmentVariable("DB_HOST", "127.0.0.1")},
|
||||
{"port", qEnvironmentVariable("DB_PORT", "3306")},
|
||||
{"database", qEnvironmentVariable("DB_DATABASE", "")},
|
||||
{"username", qEnvironmentVariable("DB_USERNAME", "root")},
|
||||
{"password", qEnvironmentVariable("DB_PASSWORD", "")},
|
||||
{"charset", qEnvironmentVariable("DB_CHARSET", "utf8mb4")},
|
||||
{"collation", qEnvironmentVariable("DB_COLLATION", "utf8mb4_0900_ai_ci")},
|
||||
{"timezone", "+00:00"},
|
||||
{"prefix", ""},
|
||||
{"strict", true},
|
||||
{"engine", InnoDB},
|
||||
{"version", {}}, // Autodetect
|
||||
{"options", QVariantHash()},
|
||||
{"prefix_indexes", true},
|
||||
{"isolation_level", "REPEATABLE READ"},
|
||||
});
|
||||
|
||||
The first argument is configuration hash which is of type `QVariantHash` and the second argument specifies the name of the *connection*, this connection will also be a *default connection*. You can configure multiple database connections at once and choose the needed one before executing SQL query, section [Using Multiple Database Connections](#using-multiple-database-connections) describes how to create and use multiple database connections.
|
||||
@@ -58,6 +63,10 @@ You may also configure connection options by `options` key as `QVariantHash` or
|
||||
|
||||
You can also configure [Transaction Isolation Levels](https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html) for MySQL connection with the `isolation_level` configuration option.
|
||||
|
||||
The `version` option is relevant only for the MySQL connections and you can save/avoid one database query (select version()) if you provide it manually. On the base of this version will be decided which [session variables](https://github.com/silverqx/TinyORM/blob/main/src/orm/connectors/mysqlconnector.cpp#L183) will be set if strict mode is enabled and whether to use an [alias](https://github.com/silverqx/TinyORM/blob/main/src/orm/query/grammars/mysqlgrammar.cpp#L32) during the `upsert` method call.
|
||||
|
||||
Breaking values are as follows; use an upsert alias on the MySQL >=8.0.19 and remove the `NO_AUTO_CREATE_USER` sql mode on the MySQL >=8.0.11 if the strict mode is enabled.
|
||||
|
||||
:::info
|
||||
A database connection is resolved lazily, which means that the connection configuration is only saved after the `DB::create` method call. The connection will be resolved after you run some query or you can create it using the `DB::connection` method.
|
||||
:::
|
||||
@@ -78,6 +87,7 @@ SQLite databases are contained within a single file on your filesystem. You can
|
||||
{"database", qEnvironmentVariable("DB_DATABASE", "/absolute/path/to/database.sqlite3")},
|
||||
{"foreign_key_constraints", qEnvironmentVariable("DB_FOREIGN_KEYS", "true")},
|
||||
{"check_database_exists", true},
|
||||
{"prefix", ""},
|
||||
});
|
||||
|
||||
The `database` configuration value is the absolute path to the database. To enable foreign key constraints for SQLite connections, you should set the `foreign_key_constraints` configuration value to `true`, if this configuration value is not set, then the default of the SQLite driver will be used.
|
||||
@@ -196,6 +206,7 @@ You can configure multiple database connections at once during `DatabaseManager`
|
||||
{"database", qEnvironmentVariable("DB_SQLITE_DATABASE", "")},
|
||||
{"foreign_key_constraints", qEnvironmentVariable("DB_SQLITE_FOREIGN_KEYS", "true")},
|
||||
{"check_database_exists", true},
|
||||
{"prefix", ""},
|
||||
}},
|
||||
}, "mysql");
|
||||
|
||||
|
||||
@@ -761,10 +761,14 @@ The `upsert` method will insert records that do not exist and update the records
|
||||
|
||||
In the example above, TinyORM will attempt to insert two records. If a record already exists with the same `departure` and `destination` column values, Laravel will update that record's `price` column.
|
||||
|
||||
:::warning
|
||||
:::caution
|
||||
All databases except SQL Server require the columns in the second argument of the `upsert` method to have a "primary" or "unique" index. In addition, the MySQL database driver ignores the second argument of the `upsert` method and always uses the "primary" and "unique" indexes of the table to detect existing records.
|
||||
:::
|
||||
|
||||
:::info
|
||||
Row and column aliases will be used with MySQL server >=8.0.19 instead of the VALUES() function as is described in the MySQL [documentation](https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html). The MySQL server version is auto-detected and can be overridden in the [configuration](/database/getting-started.mdx#configuration).
|
||||
:::
|
||||
|
||||
## Update Statements
|
||||
|
||||
In addition to inserting records into the database, the query builder can also update existing records using the `update` method. The `update` method, accepts a `QVector<Orm::UpdateItem>` of column and value pairs, indicating the columns to be updated and returns a `std::tuple<int, QSqlQuery>` . You may constrain the `update` query using `where` clauses:
|
||||
|
||||
@@ -46,7 +46,7 @@ The following list fastly summarizes all `TinyORM` features.
|
||||
- the `tom` console application with tab completion for all shells (pwsh, bash, zsh) 🥳
|
||||
- scaffolding of models, migrations, and seeders
|
||||
- overhauled models scaffolding, every feature that is supported by models can be generated using the `tom make:model` cli command
|
||||
- a huge amount of code is unit tested, currently __1393 unit tests__ 🤯
|
||||
- a huge amount of code is unit tested, currently __1402 unit tests__ 🤯
|
||||
- C++20 only, with all the latest features used like concepts/constraints, ranges, smart pointers (no `new` keyword in the whole code 😎), folding expressions
|
||||
- qmake and CMake build systems support
|
||||
- vcpkg support (also the vcpkg port, currently not committed to the vcpkg repository ☹️)
|
||||
|
||||
@@ -8,7 +8,7 @@ keywords: [c++ orm, supported compilers, supported build systems, tinyorm]
|
||||
|
||||
# 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 __1393 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 __1402 unit tests__ 😮💥.
|
||||
|
||||
<div id="supported-compilers">
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ std::shared_ptr<DatabaseManager> setupManager()
|
||||
{strict_, true},
|
||||
{isolation_level, QStringLiteral("REPEATABLE READ")},
|
||||
{engine_, InnoDB},
|
||||
{Version, {}}, // Autodetect
|
||||
{options_, QVariantHash()},
|
||||
}},
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ headersList += \
|
||||
$$PWD/orm/support/databaseconnectionsmap.hpp \
|
||||
$$PWD/orm/types/log.hpp \
|
||||
$$PWD/orm/types/statementscounter.hpp \
|
||||
$$PWD/orm/utils/configuration.hpp \
|
||||
$$PWD/orm/utils/container.hpp \
|
||||
$$PWD/orm/utils/fs.hpp \
|
||||
$$PWD/orm/utils/helpers.hpp \
|
||||
|
||||
@@ -29,9 +29,17 @@ namespace Orm
|
||||
/*! Get a schema builder instance for the connection. */
|
||||
std::unique_ptr<SchemaBuilder> getSchemaBuilder() final;
|
||||
|
||||
/* Getters */
|
||||
/*! Determine if the connected database is a MariaDB database. */
|
||||
/* Getters/Setters */
|
||||
/*! Get the MySQL server version. */
|
||||
std::optional<QString> version();
|
||||
/*! Is currently connected the MariaDB database server? */
|
||||
bool isMaria();
|
||||
/*! Determine whether to use the upsert alias (by MySQL version >=8.0.19). */
|
||||
bool useUpsertAlias();
|
||||
#ifdef TINYORM_TESTS_CODE
|
||||
/*! Override the version database configuration value. */
|
||||
void setConfigVersion(QString value);
|
||||
#endif
|
||||
|
||||
/* Others */
|
||||
/*! Check database connection and show warnings when the state changed.
|
||||
@@ -47,8 +55,12 @@ namespace Orm
|
||||
/*! Get the default post processor instance. */
|
||||
std::unique_ptr<QueryProcessor> getDefaultPostProcessor() const final;
|
||||
|
||||
/*! If the connected database is a MariaDB database. */
|
||||
std::optional<bool> m_isMaria;
|
||||
/*! MySQL server version. */
|
||||
std::optional<QString> m_version = std::nullopt;
|
||||
/*! Is currently connected the MariaDB database server? */
|
||||
std::optional<bool> m_isMaria = std::nullopt;
|
||||
/*! Determine whether to use the upsert alias (by MySQL version >=8.0.19). */
|
||||
std::optional<bool> m_useUpsertAlias = std::nullopt;
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
|
||||
39
include/orm/utils/configuration.hpp
Normal file
39
include/orm/utils/configuration.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#ifndef ORM_UTILS_CONFIG_HPP
|
||||
#define ORM_UTILS_CONFIG_HPP
|
||||
|
||||
#include "orm/macros/systemheader.hpp"
|
||||
TINY_SYSTEM_HEADER
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "orm/macros/commonnamespace.hpp"
|
||||
#include "orm/macros/export.hpp"
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
|
||||
namespace Orm::Utils
|
||||
{
|
||||
|
||||
/*! Database configuration related library class. */
|
||||
class SHAREDLIB_EXPORT Configuration
|
||||
{
|
||||
Q_DISABLE_COPY(Configuration)
|
||||
|
||||
public:
|
||||
/*! Deleted default constructor, this is a pure library class. */
|
||||
Configuration() = delete;
|
||||
/*! Deleted destructor. */
|
||||
~Configuration() = delete;
|
||||
|
||||
/*! Determine whether the database config. contains a valid version value. */
|
||||
static bool hasValidConfigVersion(const QVariantHash &config);
|
||||
/*! Get a valid config. version value. */
|
||||
static QString getValidConfigVersion(const QVariantHash &config);
|
||||
};
|
||||
|
||||
} // namespace Orm::Utils
|
||||
|
||||
TINYORM_END_COMMON_NAMESPACE
|
||||
|
||||
#endif // ORM_UTILS_CONFIG_HPP
|
||||
@@ -75,6 +75,9 @@ ConnectionFactory::parseConfig(QVariantHash &config, const QString &name) const
|
||||
// spatial_ref_sys table is used by the PostGIS
|
||||
config.insert(dont_drop, QStringList {QStringLiteral("spatial_ref_sys")});
|
||||
|
||||
if (config[driver_] == QMYSQL && !config.contains(Version))
|
||||
config.insert(Version, {});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "orm/constants.hpp"
|
||||
#include "orm/exceptions/queryerror.hpp"
|
||||
#include "orm/utils/configuration.hpp"
|
||||
#include "orm/utils/type.hpp"
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
@@ -17,6 +18,8 @@ using Orm::Constants::NAME;
|
||||
using Orm::Constants::strict_;
|
||||
using Orm::Constants::timezone_;
|
||||
|
||||
using ConfigUtils = Orm::Utils::Configuration;
|
||||
|
||||
namespace Orm::Connectors
|
||||
{
|
||||
|
||||
@@ -194,9 +197,11 @@ QString MySqlConnector::strictMode(const QSqlDatabase &connection,
|
||||
QString MySqlConnector::getMySqlVersion(const QSqlDatabase &connection,
|
||||
const QVariantHash &config) const
|
||||
{
|
||||
// Get the MySQL version from the configuration if it was defined
|
||||
if (config.contains("version") && !config["version"].value<QString>().isEmpty())
|
||||
return config["version"].value<QString>();
|
||||
// Get the MySQL version from the configuration if it was defined and is valid
|
||||
if (auto configVersionValue = ConfigUtils::getValidConfigVersion(config);
|
||||
!configVersionValue.isEmpty()
|
||||
)
|
||||
return configVersionValue;
|
||||
|
||||
// Obtain the MySQL version from the database
|
||||
QSqlQuery query(connection);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#ifdef TINYORM_MYSQL_PING
|
||||
# include <QDebug>
|
||||
#endif
|
||||
#include <QVersionNumber>
|
||||
#include <QtSql/QSqlDriver>
|
||||
|
||||
#ifdef TINYORM_MYSQL_PING
|
||||
@@ -23,9 +24,12 @@ disable TINYORM_MYSQL_PING preprocessor directive.
|
||||
#include "orm/query/processors/mysqlprocessor.hpp"
|
||||
#include "orm/schema/grammars/mysqlschemagrammar.hpp"
|
||||
#include "orm/schema/mysqlschemabuilder.hpp"
|
||||
#include "orm/utils/configuration.hpp"
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
|
||||
using ConfigUtils = Orm::Utils::Configuration;
|
||||
|
||||
namespace Orm
|
||||
{
|
||||
|
||||
@@ -55,18 +59,100 @@ std::unique_ptr<SchemaBuilder> MySqlConnection::getSchemaBuilder()
|
||||
return std::make_unique<SchemaNs::MySqlSchemaBuilder>(*this);
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
/* Getters/Setters */
|
||||
|
||||
std::optional<QString> MySqlConnection::version()
|
||||
{
|
||||
auto configVersionValue = ConfigUtils::getValidConfigVersion(m_config);
|
||||
|
||||
/* Default values is std::nullopt if pretending and the database config. doesn't
|
||||
contain a valid version value. */
|
||||
if (m_pretending && !m_version && configVersionValue.isEmpty())
|
||||
return std::nullopt;
|
||||
|
||||
// Return the cached value
|
||||
if (m_version)
|
||||
return m_version;
|
||||
|
||||
// A user can provide the version through the configuration to save one DB query
|
||||
if (!configVersionValue.isEmpty())
|
||||
return m_version = std::move(configVersionValue);
|
||||
|
||||
// Obtain and cache the database version value
|
||||
return m_version = selectOne(QStringLiteral("select version()")).value(0)
|
||||
.value<QString>();
|
||||
}
|
||||
|
||||
bool MySqlConnection::isMaria()
|
||||
{
|
||||
// TEST now add MariaDB tests, install mariadb add connection and run all the tests against mariadb too silverqx
|
||||
if (!m_isMaria)
|
||||
m_isMaria = selectOne("select version()").value(0).value<QString>()
|
||||
.contains("MariaDB");
|
||||
// Default values is false if pretending and the config. version was not set manually
|
||||
if (m_pretending && !m_isMaria && !m_version &&
|
||||
!ConfigUtils::hasValidConfigVersion(m_config)
|
||||
)
|
||||
return false;
|
||||
|
||||
// Return the cached value
|
||||
if (m_isMaria)
|
||||
return *m_isMaria;
|
||||
|
||||
// Obtain a version from the database if needed
|
||||
version();
|
||||
|
||||
// This should never happen 🤔 because of the condition at beginning
|
||||
if (!m_version)
|
||||
return false;
|
||||
|
||||
// Cache the value
|
||||
m_isMaria = m_version->contains(QStringLiteral("MariaDB"));
|
||||
|
||||
return *m_isMaria;
|
||||
}
|
||||
|
||||
bool MySqlConnection::useUpsertAlias()
|
||||
{
|
||||
// Default values is true if pretending and the config. version was not set manually
|
||||
if (m_pretending && !m_useUpsertAlias && !m_version &&
|
||||
!ConfigUtils::hasValidConfigVersion(m_config)
|
||||
)
|
||||
// FUTURE useUpsertAlias() default value to true after MySQL 8.0 will be end-of-life silverqx
|
||||
return false;
|
||||
|
||||
// Return the cached value
|
||||
if (m_useUpsertAlias)
|
||||
return *m_useUpsertAlias;
|
||||
|
||||
// Obtain a version from the database if needed
|
||||
version();
|
||||
|
||||
// This should never happen 🤔 because of the condition at beginning
|
||||
if (!m_version)
|
||||
return false;
|
||||
|
||||
/* The MySQL >=8.0.19 supports aliases in the VALUES and SET clauses
|
||||
of INSERT INTO ... ON DUPLICATE KEY UPDATE statement for the row to be
|
||||
inserted and its columns. It also generates warning if old style used.
|
||||
So set it to true to avoid this warning. */
|
||||
|
||||
// Cache the value
|
||||
m_useUpsertAlias = QVersionNumber::fromString(*m_version) >=
|
||||
QVersionNumber(8, 0, 19);
|
||||
|
||||
return *m_useUpsertAlias;
|
||||
}
|
||||
|
||||
#ifdef TINYORM_TESTS_CODE
|
||||
void MySqlConnection::setConfigVersion(QString value) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// Override it through the config., this ensure that more code branches will be tested
|
||||
const_cast<QVariantHash &>(m_config).insert(Version, std::move(value));
|
||||
|
||||
// We need to reset these to recomputed them again
|
||||
m_version = std::nullopt;
|
||||
m_isMaria = std::nullopt;
|
||||
m_useUpsertAlias = std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Others */
|
||||
|
||||
bool MySqlConnection::pingDatabase()
|
||||
@@ -169,3 +255,5 @@ std::unique_ptr<QueryProcessor> MySqlConnection::getDefaultPostProcessor() const
|
||||
} // namespace Orm
|
||||
|
||||
TINYORM_END_COMMON_NAMESPACE
|
||||
|
||||
// TEST now add MariaDB tests, install mariadb add connection and run all the tests against mariadb too silverqx
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "orm/query/grammars/mysqlgrammar.hpp"
|
||||
|
||||
#include "orm/macros/threadlocal.hpp"
|
||||
#include "orm/mysqlconnection.hpp"
|
||||
#include "orm/query/querybuilder.hpp"
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
@@ -25,21 +26,35 @@ QString MySqlGrammar::compileInsertOrIgnore(const QueryBuilder &query,
|
||||
}
|
||||
|
||||
QString MySqlGrammar::compileUpsert(
|
||||
QueryBuilder &query, const QVector<QVariantMap> &values,
|
||||
const QStringList &/*unused*/, const QStringList &update) const
|
||||
QueryBuilder &query, const QVector<QVariantMap> &values,
|
||||
const QStringList &/*unused*/, const QStringList &update) const
|
||||
{
|
||||
static const auto TinyOrmUpsertAlias = QStringLiteral("tinyorm_upsert_alias");
|
||||
|
||||
// Use an upsert alias on the MySQL >=8.0.19
|
||||
const auto useUpsertAlias = dynamic_cast<MySqlConnection &>(query.getConnection())
|
||||
.useUpsertAlias();
|
||||
|
||||
auto sql = compileInsert(query, values);
|
||||
|
||||
if (useUpsertAlias)
|
||||
sql += QStringLiteral(" as %1")
|
||||
.arg(wrap(QStringLiteral("tinyorm_upsert_alias")));
|
||||
|
||||
sql += QStringLiteral(" on duplicate key update ");
|
||||
|
||||
QStringList columns;
|
||||
columns.reserve(update.size());
|
||||
|
||||
for (const auto &column : update) {
|
||||
auto wrappedColumn = wrap(column);
|
||||
const auto wrappedColumn = wrap(column);
|
||||
|
||||
columns << QStringLiteral("%1 = values(%2)")
|
||||
.arg(wrappedColumn, std::move(wrappedColumn));
|
||||
columns << (useUpsertAlias ? QStringLiteral("%1 = %2")
|
||||
.arg(wrappedColumn,
|
||||
DOT_IN.arg(wrap(TinyOrmUpsertAlias),
|
||||
wrappedColumn))
|
||||
: QStringLiteral("%1 = values(%2)")
|
||||
.arg(wrappedColumn, wrappedColumn));
|
||||
}
|
||||
|
||||
return NOSPACE.arg(std::move(sql), columns.join(COMMA));
|
||||
|
||||
42
src/orm/utils/configuration.cpp
Normal file
42
src/orm/utils/configuration.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "orm/utils/configuration.hpp"
|
||||
|
||||
#include <QVariantHash>
|
||||
#include <QVersionNumber>
|
||||
|
||||
#include "orm/constants.hpp"
|
||||
|
||||
TINYORM_BEGIN_COMMON_NAMESPACE
|
||||
|
||||
using Orm::Constants::Version;
|
||||
|
||||
namespace Orm::Utils
|
||||
{
|
||||
|
||||
/* public */
|
||||
|
||||
bool Configuration::hasValidConfigVersion(const QVariantHash &config)
|
||||
{
|
||||
return !getValidConfigVersion(config).isEmpty();
|
||||
}
|
||||
|
||||
QString Configuration::getValidConfigVersion(const QVariantHash &config)
|
||||
{
|
||||
if (config.contains(Version))
|
||||
if (const auto version = config.value(Version);
|
||||
version.isValid() && !version.isNull() && version.canConvert<QString>()
|
||||
) {
|
||||
auto versionValue = version.value<QString>();
|
||||
|
||||
// Validate whether a version number is correctly formatted
|
||||
if (const auto versionNumber = QVersionNumber::fromString(versionValue);
|
||||
!versionNumber.isNull()
|
||||
)
|
||||
return versionValue;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Orm::Utils
|
||||
|
||||
TINYORM_END_COMMON_NAMESPACE
|
||||
@@ -51,6 +51,7 @@ sourcesList += \
|
||||
$$PWD/orm/schema/sqliteschemabuilder.cpp \
|
||||
$$PWD/orm/sqliteconnection.cpp \
|
||||
$$PWD/orm/support/configurationoptionsparser.cpp \
|
||||
$$PWD/orm/utils/configuration.cpp \
|
||||
$$PWD/orm/utils/fs.cpp \
|
||||
$$PWD/orm/utils/query.cpp \
|
||||
$$PWD/orm/utils/thread.cpp \
|
||||
|
||||
@@ -20,6 +20,7 @@ using Orm::Constants::UTC;
|
||||
using Orm::Constants::UTF8;
|
||||
using Orm::Constants::UTF8MB4;
|
||||
using Orm::Constants::UTF8MB40900aici;
|
||||
using Orm::Constants::Version;
|
||||
using Orm::Constants::database_;
|
||||
using Orm::Constants::driver_;
|
||||
using Orm::Constants::charset_;
|
||||
@@ -171,6 +172,7 @@ Databases::mysqlConfiguration()
|
||||
{strict_, true},
|
||||
{isolation_level, QStringLiteral("REPEATABLE READ")},
|
||||
{engine_, InnoDB},
|
||||
{Version, {}}, // Autodetect
|
||||
{options_, QVariantHash()},
|
||||
// FUTURE remove, when unit tested silverqx
|
||||
// Example
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "orm/exceptions/invalidargumenterror.hpp"
|
||||
#include "orm/exceptions/multiplerecordsfounderror.hpp"
|
||||
#include "orm/exceptions/recordsnotfounderror.hpp"
|
||||
#include "orm/mysqlconnection.hpp"
|
||||
#include "orm/query/querybuilder.hpp"
|
||||
|
||||
#include "databases.hpp"
|
||||
@@ -26,6 +27,7 @@ using Orm::DB;
|
||||
using Orm::Exceptions::InvalidArgumentError;
|
||||
using Orm::Exceptions::MultipleRecordsFoundError;
|
||||
using Orm::Exceptions::RecordsNotFoundError;
|
||||
using Orm::MySqlConnection;
|
||||
using Orm::Query::Builder;
|
||||
using Orm::Query::Expression;
|
||||
|
||||
@@ -60,6 +62,14 @@ class tst_MySql_QueryBuilder : public QObject // clazy:exclude=ctor-missing-pare
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void version() const;
|
||||
void version_InPretend() const;
|
||||
void version_InPretend_DefaultValue() const;
|
||||
|
||||
void isMaria() const;
|
||||
void isMaria_InPretend() const;
|
||||
void isMaria_InPretend_DefaultValue() const;
|
||||
|
||||
void get() const;
|
||||
void get_ColumnExpression() const;
|
||||
|
||||
@@ -192,8 +202,11 @@ private Q_SLOTS:
|
||||
void update() const;
|
||||
void update_WithExpression() const;
|
||||
|
||||
void upsert() const;
|
||||
void upsert_WithoutUpdate_UpdateAll() const;
|
||||
void upsert_UseUpsertAlias() const;
|
||||
void upsert_UseUpsertAlias_Disabled() const;
|
||||
void upsert_UseUpsertAlias_DefaultValue() const;
|
||||
void upsert_WithoutUpdate_UpdateAll_UseUpsertAlias() const;
|
||||
void upsert_WithoutUpdate_UpdateAll_UseUpsertAlias_Disabled() const;
|
||||
|
||||
void remove() const;
|
||||
void remove_WithExpression() const;
|
||||
@@ -232,6 +245,84 @@ void tst_MySql_QueryBuilder::initTestCase()
|
||||
.arg("tst_MySql_QueryBuilder", Databases::MYSQL).toUtf8().constData(), );
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::version() const
|
||||
{
|
||||
auto version = QStringLiteral("10.8.3-MariaDB");
|
||||
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion(version);
|
||||
|
||||
QCOMPARE(mysqlConnection.version(), version);
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::version_InPretend() const
|
||||
{
|
||||
auto version = QStringLiteral("10.8.3-MariaDB");
|
||||
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion(version);
|
||||
|
||||
auto log = mysqlConnection.pretend([&mysqlConnection, &version]
|
||||
{
|
||||
QCOMPARE(mysqlConnection.version(), version);
|
||||
});
|
||||
|
||||
QVERIFY(log.isEmpty());
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::version_InPretend_DefaultValue() const
|
||||
{
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion({});
|
||||
|
||||
auto log = mysqlConnection.pretend([&mysqlConnection]
|
||||
{
|
||||
// No version set so it should return std::nullopt
|
||||
QVERIFY(!mysqlConnection.version());
|
||||
});
|
||||
|
||||
QVERIFY(log.isEmpty());
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::isMaria() const
|
||||
{
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion("10.4.7-MariaDB");
|
||||
|
||||
QVERIFY(mysqlConnection.isMaria());
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::isMaria_InPretend() const
|
||||
{
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion("10.4.7-MariaDB");
|
||||
|
||||
auto log = mysqlConnection.pretend([&mysqlConnection]
|
||||
{
|
||||
QVERIFY(mysqlConnection.isMaria());
|
||||
});
|
||||
|
||||
QVERIFY(log.isEmpty());
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::isMaria_InPretend_DefaultValue() const
|
||||
{
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion({});
|
||||
|
||||
auto log = mysqlConnection.pretend([&mysqlConnection]
|
||||
{
|
||||
// No version set so it the default value is false
|
||||
QVERIFY(!mysqlConnection.isMaria());
|
||||
});
|
||||
|
||||
QVERIFY(log.isEmpty());
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::get() const
|
||||
{
|
||||
{
|
||||
@@ -2768,9 +2859,13 @@ void tst_MySql_QueryBuilder::update_WithExpression() const
|
||||
QVector<QVariant>({QVariant(6), QVariant(10)}));
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::upsert() const
|
||||
void tst_MySql_QueryBuilder::upsert_UseUpsertAlias() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion("8.0.19");
|
||||
|
||||
auto log = mysqlConnection.pretend([](auto &connection)
|
||||
{
|
||||
connection.query()->from("tag_properties")
|
||||
.upsert({{{"tag_id", 1}, {"color", "pink"}, {"position", 0}},
|
||||
@@ -2779,22 +2874,92 @@ void tst_MySql_QueryBuilder::upsert() const
|
||||
{"color"});
|
||||
});
|
||||
|
||||
QVERIFY(!log.isEmpty());
|
||||
const auto &firstLog = log.first();
|
||||
// MySQL >=8.0.19 uses upsert alias
|
||||
const auto useUpsertAlias = mysqlConnection.useUpsertAlias();
|
||||
QVERIFY(useUpsertAlias);
|
||||
|
||||
QCOMPARE(log.size(), 1);
|
||||
QCOMPARE(firstLog.query,
|
||||
|
||||
const auto &log0 = log.at(0);
|
||||
QCOMPARE(log0.query,
|
||||
"insert into `tag_properties` (`color`, `position`, `tag_id`) "
|
||||
"values (?, ?, ?), (?, ?, ?) "
|
||||
"on duplicate key update `color` = values(`color`)");
|
||||
QCOMPARE(firstLog.boundValues,
|
||||
"values (?, ?, ?), (?, ?, ?) as `tinyorm_upsert_alias` "
|
||||
"on duplicate key update "
|
||||
"`color` = `tinyorm_upsert_alias`.`color`");
|
||||
QCOMPARE(log0.boundValues,
|
||||
QVector<QVariant>({QVariant(QString("pink")), QVariant(0), QVariant(1),
|
||||
QVariant(QString("purple")), QVariant(4), QVariant(1)}));
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::upsert_WithoutUpdate_UpdateAll() const
|
||||
void tst_MySql_QueryBuilder::upsert_UseUpsertAlias_Disabled() const
|
||||
{
|
||||
auto log = DB::connection(m_connection).pretend([](auto &connection)
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion("8.0.18");
|
||||
|
||||
auto log = mysqlConnection.pretend([](auto &connection)
|
||||
{
|
||||
connection.query()->from("tag_properties")
|
||||
.upsert({{{"tag_id", 1}, {"color", "pink"}, {"position", 0}},
|
||||
{{"tag_id", 1}, {"color", "purple"}, {"position", 4}}},
|
||||
{"position"},
|
||||
{"color"});
|
||||
});
|
||||
|
||||
// MySQL <8.0.19 doesn't use upsert alias
|
||||
const auto useUpsertAlias = mysqlConnection.useUpsertAlias();
|
||||
QVERIFY(!useUpsertAlias);
|
||||
|
||||
QCOMPARE(log.size(), 1);
|
||||
|
||||
const auto &log0 = log.at(0);
|
||||
QCOMPARE(log0.query,
|
||||
"insert into `tag_properties` (`color`, `position`, `tag_id`) "
|
||||
"values (?, ?, ?), (?, ?, ?) "
|
||||
"on duplicate key update `color` = values(`color`)");
|
||||
QCOMPARE(log0.boundValues,
|
||||
QVector<QVariant>({QVariant(QString("pink")), QVariant(0), QVariant(1),
|
||||
QVariant(QString("purple")), QVariant(4), QVariant(1)}));
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::upsert_UseUpsertAlias_DefaultValue() const
|
||||
{
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion({});
|
||||
|
||||
auto log = mysqlConnection.pretend([&mysqlConnection](auto &connection)
|
||||
{
|
||||
connection.query()->from("tag_properties")
|
||||
.upsert({{{"tag_id", 1}, {"color", "pink"}, {"position", 0}},
|
||||
{{"tag_id", 1}, {"color", "purple"}, {"position", 4}}},
|
||||
{"position"},
|
||||
{"color"});
|
||||
|
||||
/* Default value for the use upsert alias feature during pretending will be false
|
||||
because no version was provided through the database configuration. */
|
||||
const auto useUpsertAlias = mysqlConnection.useUpsertAlias();
|
||||
QVERIFY(!useUpsertAlias);
|
||||
});
|
||||
|
||||
QCOMPARE(log.size(), 1);
|
||||
|
||||
const auto &log0 = log.at(0);
|
||||
QCOMPARE(log0.query,
|
||||
"insert into `tag_properties` (`color`, `position`, `tag_id`) "
|
||||
"values (?, ?, ?), (?, ?, ?) "
|
||||
"on duplicate key update `color` = values(`color`)");
|
||||
QCOMPARE(log0.boundValues,
|
||||
QVector<QVariant>({QVariant(QString("pink")), QVariant(0), QVariant(1),
|
||||
QVariant(QString("purple")), QVariant(4), QVariant(1)}));
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder::upsert_WithoutUpdate_UpdateAll_UseUpsertAlias() const
|
||||
{
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion("8.0.19");
|
||||
|
||||
auto log = mysqlConnection.pretend([](auto &connection)
|
||||
{
|
||||
connection.query()->from("tag_properties")
|
||||
.upsert({{{"tag_id", 2}, {"color", "pink"}, {"position", 0}},
|
||||
@@ -2802,6 +2967,45 @@ void tst_MySql_QueryBuilder::upsert_WithoutUpdate_UpdateAll() const
|
||||
{"position"});
|
||||
});
|
||||
|
||||
// MySQL >=8.0.19 uses upsert alias
|
||||
const auto useUpsertAlias = mysqlConnection.useUpsertAlias();
|
||||
QVERIFY(useUpsertAlias);
|
||||
|
||||
QVERIFY(!log.isEmpty());
|
||||
const auto &firstLog = log.first();
|
||||
|
||||
QCOMPARE(log.size(), 1);
|
||||
QCOMPARE(firstLog.query,
|
||||
"insert into `tag_properties` (`color`, `position`, `tag_id`) "
|
||||
"values (?, ?, ?), (?, ?, ?) as `tinyorm_upsert_alias` "
|
||||
"on duplicate key update "
|
||||
"`color` = `tinyorm_upsert_alias`.`color`, "
|
||||
"`position` = `tinyorm_upsert_alias`.`position`, "
|
||||
"`tag_id` = `tinyorm_upsert_alias`.`tag_id`");
|
||||
QCOMPARE(firstLog.boundValues,
|
||||
QVector<QVariant>({QVariant(QString("pink")), QVariant(0), QVariant(2),
|
||||
QVariant(QString("purple")), QVariant(4), QVariant(1)}));
|
||||
}
|
||||
|
||||
void tst_MySql_QueryBuilder
|
||||
::upsert_WithoutUpdate_UpdateAll_UseUpsertAlias_Disabled() const
|
||||
{
|
||||
// Need to be set before pretending
|
||||
auto &mysqlConnection = dynamic_cast<MySqlConnection &>(DB::connection(m_connection));
|
||||
mysqlConnection.setConfigVersion("8.0.18");
|
||||
|
||||
auto log = mysqlConnection.pretend([](auto &connection)
|
||||
{
|
||||
connection.query()->from("tag_properties")
|
||||
.upsert({{{"tag_id", 2}, {"color", "pink"}, {"position", 0}},
|
||||
{{"tag_id", 1}, {"color", "purple"}, {"position", 4}}},
|
||||
{"position"});
|
||||
});
|
||||
|
||||
// MySQL <8.0.19 doesn't use upsert alias
|
||||
const auto useUpsertAlias = mysqlConnection.useUpsertAlias();
|
||||
QVERIFY(!useUpsertAlias);
|
||||
|
||||
QVERIFY(!log.isEmpty());
|
||||
const auto &firstLog = log.first();
|
||||
|
||||
@@ -2810,8 +3014,8 @@ void tst_MySql_QueryBuilder::upsert_WithoutUpdate_UpdateAll() const
|
||||
"insert into `tag_properties` (`color`, `position`, `tag_id`) "
|
||||
"values (?, ?, ?), (?, ?, ?) "
|
||||
"on duplicate key update `color` = values(`color`), "
|
||||
"`position` = values(`position`), "
|
||||
"`tag_id` = values(`tag_id`)");
|
||||
"`position` = values(`position`), "
|
||||
"`tag_id` = values(`tag_id`)");
|
||||
QCOMPARE(firstLog.boundValues,
|
||||
QVector<QVariant>({QVariant(QString("pink")), QVariant(0), QVariant(2),
|
||||
QVariant(QString("purple")), QVariant(4), QVariant(1)}));
|
||||
|
||||
@@ -86,6 +86,7 @@ std::shared_ptr<DatabaseManager> setupManager()
|
||||
{strict_, true},
|
||||
{isolation_level, QStringLiteral("REPEATABLE READ")},
|
||||
{engine_, InnoDB},
|
||||
{Version, {}}, // Autodetect
|
||||
{options_, QVariantHash()},
|
||||
}},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user