Files
TinyORM/docs/database/getting-started.mdx
silverqx 9cd5e35734 docs re-ordered ul list
[skip ci]
2024-08-17 15:23:36 +02:00

911 lines
36 KiB
Plaintext

---
sidebar_position: 0
sidebar_label: Getting Started
description: TinyORM makes interacting with a database extremely simple using raw SQL, a fluent query builder, and the TinyORM. It provides first-party support for four databases MySQL/MariaDB, PostgreSQL, and SQLite.
keywords: [c++ orm, database, getting started, tinyorm]
---
import Link from '@docusaurus/Link'
import TabItem from '@theme/TabItem'
import Tabs from '@theme/Tabs'
import ConfigurationSettings from '@theme/ConfigurationSettings'
import {
shell,
bash, pwsh,
bash_label, pwsh_label,
database,
maria, mysql, postgres, sqlite,
maria_label, mysql_label, postgres_label, sqlite_label,
} from '@theme/constants'
# Database: Getting Started
- [Introduction](#introduction)
- [Configuration](#configuration)
- [SSL Connections](#ssl-connections)
- [Running SQL Queries](#running-sql-queries)
- [Using Multiple Database Connections](#using-multiple-database-connections)
- [Database Transactions](#database-transactions)
- [Multi-threading support](#multi-threading-support)
## Introduction
<div className="api-stability alert alert--success">
<Link to='/stability#stability-indexes'>__Stability: 2__</Link> - Stable
</div>
Almost every modern application interacts with a database. TinyORM makes interacting with a database extremely simple using raw SQL, a [fluent query builder](database/query-builder.mdx), and the [TinyORM](tinyorm/getting-started.mdx). Currently, TinyORM provides first-party support for four databases:
<div id='databases-supported-versions'>
- MySQL `>=5.7` ([Version Policy](https://en.wikipedia.org/wiki/MySQL#Release_history))
- MariaDB `>=10.3` ([Version Policy](https://mariadb.org/about/#maintenance-policy))
- PostgreSQL `>=11` ([Version Policy](https://www.postgresql.org/support/versioning/))
- SQLite `>=3.8.8`
- native rename column `>=3.25.0` ([docs](https://www.sqlite.org/lang_altertable.html#alter_table_rename_column) , [release notes](https://www.sqlite.org/releaselog/3_25_0.html))
- generated columns `>=3.31.0` ([docs](https://www.sqlite.org/gencol.html), [release notes](https://www.sqlite.org/releaselog/3_31_0.html))
- native drop column `>=3.35.0` ([docs](https://www.sqlite.org/lang_altertable.html#alter_table_drop_column), [release notes](https://www.sqlite.org/releaselog/3_35_0.html))
</div>
TinyORM internally uses `QtSql` module, you can look for [supported databases](https://doc.qt.io/qt/sql-driver.html#supported-databases).
:::note
TinyORM's code is ready and designed to simply add support for the SQL Server.
:::
### Configuration
You can create and configure a new database connection using the `create` method provided by the `DB` facade:
<div id='tinyorm-configuration'>
<ConfigurationSettings />
<Tabs className='tinyorm-configuration-basic' groupId={database}>
<TabItem value={mysql} label={mysql_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// 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"},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{"qt_timezone", QVariant::fromValue(QTimeZone::UTC)},
{"prefix", ""},
{"prefix_indexes", false},
{"strict", true},
{"engine", "InnoDB"},
{"options", QVariantHash()},
});
```
</TabItem>
<TabItem value={postgres} label={postgres_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QPSQL"},
{"host", qEnvironmentVariable("DB_HOST", "127.0.0.1")},
{"port", qEnvironmentVariable("DB_PORT", "5432")},
{"database", qEnvironmentVariable("DB_DATABASE", "")},
{"search_path", qEnvironmentVariable("DB_SEARCHPATH", "public")},
{"username", qEnvironmentVariable("DB_USERNAME", "postgres")},
{"password", qEnvironmentVariable("DB_PASSWORD", "")},
{"charset", qEnvironmentVariable("DB_CHARSET", "utf8")},
{"timezone", "UTC"},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{"qt_timezone", QVariant::fromValue(QTimeZone::UTC)},
{"prefix", ""},
{"prefix_indexes", false},
{"options", QVariantHash()},
});
```
</TabItem>
<TabItem value={sqlite} label={sqlite_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QSQLITE"},
{"database", qEnvironmentVariable("DB_DATABASE", "/example/example.sqlite3")},
{"foreign_key_constraints", qEnvironmentVariable("DB_FOREIGN_KEYS", "true")},
{"check_database_exists", true},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{"qt_timezone", QVariant::fromValue(QTimeZone::UTC)},
/* Return a QDateTime/QDate with the correct time zone instead of the QString,
only works when the qt_timezone isn't set to the DontConvert. */
{"return_qdatetime", true},
{"prefix", ""},
{"prefix_indexes", false},
});
```
</TabItem>
<TabItem value={maria} label={maria_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// 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_unicode_520_ci")},
{"timezone", "+00:00"},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{"qt_timezone", QVariant::fromValue(QTimeZone::UTC)},
{"prefix", ""},
{"prefix_indexes", false},
{"strict", true},
{"engine", "InnoDB"},
{"options", QVariantHash()},
});
```
</TabItem>
</Tabs>
<Tabs className='tinyorm-configuration-full' groupId={database}>
<TabItem value={mysql} label={mysql_label}>
```cpp
#include <orm/db.hpp>
#include <orm/utils/configuration.hpp>
using Orm::DB;
using ConfigUtils = Orm::Utils::Configuration;
using namespace Orm::Constants; // NOLINT(google-build-using-namespace)
// Ownership of a shared_ptr()
auto manager = DB::create({
{driver_, QMYSQL},
{host_, qEnvironmentVariable("DB_HOST", H127001)},
{port_, qEnvironmentVariable("DB_PORT", P3306)},
{database_, qEnvironmentVariable("DB_DATABASE", EMPTY)},
{username_, qEnvironmentVariable("DB_USERNAME", ROOT)},
{password_, qEnvironmentVariable("DB_PASSWORD", EMPTY)},
{charset_, qEnvironmentVariable("DB_CHARSET", UTF8MB4)},
{collation_, qEnvironmentVariable("DB_COLLATION", UTF8MB40900aici)},
// SSL-related
{ssl_ca, QStringLiteral("C:/mysql/data/ca.pem")},
{ssl_cert, QStringLiteral("C:/mysql/data/client-cert.pem")},
{ssl_key, QStringLiteral("C:/mysql/data/client-key.pem")},
{ssl_mode, VerifyCA},
// Or
// {options, ConfigUtils::mysqlSslOptions()},
{timezone_, TZ00},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{qt_timezone, QVariant::fromValue(QTimeZone::UTC)},
{prefix_, EMPTY},
{prefix_indexes, false},
{strict_, true},
// {isolation_level, QStringLiteral("REPEATABLE READ")}, // MySQL default is REPEATABLE READ for InnoDB
{engine_, InnoDB},
{Version, {}}, // Autodetect
{options_, QVariantHash()},
// Examples
// {options_, QStringLiteral("MYSQL_OPT_CONNECT_TIMEOUT = 1 ; MYSQL_OPT_READ_TIMEOUT=1")},
// {options_, QVariantHash {{QStringLiteral("MYSQL_OPT_CONNECT_TIMEOUT"), 1},
// {QStringLiteral("MYSQL_OPT_READ_TIMEOUT"), 1}}},
});
```
</TabItem>
<TabItem value={postgres} label={postgres_label}>
```cpp
#include <orm/db.hpp>
#include <orm/utils/configuration.hpp>
using Orm::DB;
using ConfigUtils = Orm::Utils::Configuration;
using namespace Orm::Constants; // NOLINT(google-build-using-namespace)
// Ownership of a shared_ptr()
auto manager = DB::create({
{driver_, QPSQL},
{application_name, QStringLiteral("Example application")},
{host_, qEnvironmentVariable("DB_HOST", H127001)},
{port_, qEnvironmentVariable("DB_PORT", P5432)},
{database_, qEnvironmentVariable("DB_DATABASE", EMPTY)},
{search_path, qEnvironmentVariable("DB_SEARCHPATH", PUBLIC)},
{username_, qEnvironmentVariable("DB_USERNAME", postgres_)},
{password_, qEnvironmentVariable("DB_PASSWORD", EMPTY)},
{charset_, qEnvironmentVariable("DB_CHARSET", UTF8)},
// SSL-related
{sslmode_, QStringLiteral("verify-full")},
{sslcert, QStringLiteral("C:/example/postgres.crt")},
{sslkey, QStringLiteral("C:/example/postgres.key")},
{sslrootcert, QStringLiteral("C:/example/root.crt")},
// Or
// {options_, ConfigUtils::postgresSslOptions()},
{timezone_, UTC},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{qt_timezone, QVariant::fromValue(QTimeZone::UTC)},
{prefix_, EMPTY},
{prefix_indexes, false},
// {isolation_level, QStringLiteral("REPEATABLE READ")}, // Postgres default is READ COMMITTED
// {synchronous_commit, QStringLiteral("off")}, // Postgres default is on
// ConnectionFactory provides a default value for this, this is only for reference
// {dont_drop, QStringList {spatial_ref_sys}},
{options_, QVariantHash()},
});
```
</TabItem>
<TabItem value={sqlite} label={sqlite_label}>
```cpp
#include <orm/db.hpp>
#include <orm/utils/configuration.hpp>
using Orm::DB;
using ConfigUtils = Orm::Utils::Configuration;
using namespace Orm::Constants; // NOLINT(google-build-using-namespace)
// Ownership of a shared_ptr()
auto manager = DB::create({
{driver_, QSQLITE},
{database_, qEnvironmentVariable("DB_DATABASE", "C:/SQLite/example.sqlite3")},
{foreign_key_constraints, qEnvironmentVariable("DB_FOREIGN_KEYS", "true")},
{check_database_exists, true},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{qt_timezone, QVariant::fromValue(QTimeZone::UTC)},
/* Return a QDateTime/QDate with the correct time zone instead of the QString,
only works when the qt_timezone isn't set to the DontConvert. */
{return_qdatetime, true},
{prefix_, EMPTY},
{prefix_indexes, false},
});
```
</TabItem>
<TabItem value={maria} label={maria_label}>
```cpp
#include <orm/db.hpp>
#include <orm/utils/configuration.hpp>
using Orm::DB;
using ConfigUtils = Orm::Utils::Configuration;
using namespace Orm::Constants; // NOLINT(google-build-using-namespace)
// Ownership of a shared_ptr()
auto manager = DB::create({
{driver_, QMYSQL},
{host_, qEnvironmentVariable("DB_HOST", H127001)},
{port_, qEnvironmentVariable("DB_PORT", P3306)},
{database_, qEnvironmentVariable("DB_DATABASE", EMPTY)},
{username_, qEnvironmentVariable("DB_USERNAME", ROOT)},
{password_, qEnvironmentVariable("DB_PASSWORD", EMPTY)},
{charset_, qEnvironmentVariable("DB_CHARSET", UTF8MB4)},
{collation_, qEnvironmentVariable("DB_COLLATION", UTF8MB4Unicode520ci)},
// SSL-related
{ssl_ca, QStringLiteral("C:/maria/data/ca.pem")},
{ssl_cert, QStringLiteral("C:/maria/data/client-cert.pem")},
{ssl_key, QStringLiteral("C:/maria/data/client-key.pem")},
// Or
// {options, ConfigUtils::mariaSslOptions()},
{timezone_, TZ00},
/* Specifies what time zone all QDateTime-s will have, the overridden default is
the QTimeZone::UTC, set to the QTimeZone::LocalTime or
QtTimeZoneType::DontConvert to use the system local time. */
{qt_timezone, QVariant::fromValue(QTimeZone::UTC)},
{prefix_, EMPTY},
{prefix_indexes, false},
{strict_, true},
// {isolation_level, QStringLiteral("REPEATABLE READ")}, // MariaDB default is REPEATABLE READ for InnoDB
{engine_, InnoDB},
{Version, {}}, // Autodetect
{options_, QVariantHash()},
// Examples
// {options_, QStringLiteral("MYSQL_OPT_CONNECT_TIMEOUT = 1 ; MYSQL_OPT_READ_TIMEOUT=1")},
// {options_, QVariantHash {{QStringLiteral("MYSQL_OPT_CONNECT_TIMEOUT"), 1},
// {QStringLiteral("MYSQL_OPT_READ_TIMEOUT"), 1}}},
});
```
</TabItem>
</Tabs>
</div>
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.
You may also configure connection options by `options` key as `QVariantHash` or `QString`, you can pass any [connection options](https://doc.qt.io/qt/qsqldatabase.html#setConnectOptions) supported by `QSqlDatabase`.
You can also configure [Transaction Isolation Levels](https://dev.mysql.com/doc/refman/9.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. Based on this version will be decided which [session variables](https://github.com/silverqx/TinyORM/blob/main/src/orm/connectors/mysqlconnector.cpp#L182) 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#L36) 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.
:::
:::tip
You can also use predefined string constants to avoid unnecessary `QString` instantiations, as used in the `tom` migrations [example](building/migrations.mdx#string-constants-example).
:::
:::info
See [Date Casting, Serialization & Timezones](tinyorm/casts.mdx#date-casting-serialization-and-timezones) if you have problems with `qt_timezone` configuration option and `QTimeZone` value on older `Qt <6.5` versions.
:::
#### SQLite Configuration
SQLite databases are contained within a single file on your filesystem. You can create a new SQLite database using the `touch` command in your terminal: `touch database.sqlite3`. After the database has been created, you may configure SQLite database connection:
```cpp
#include <orm/db.hpp>
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QSQLITE"},
{"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 (currently the default is __disabled__).
If the `check_database_exists` configuration value is set to the `true` value, then the database connection throws an `Orm::InvalidArgumentError` exception, when the SQLite database file doesn't exist. If it is set to the `false` value and the SQLite database file doesn't exist, then it will be created for you by SQLite driver. The default value is `true`.
### SSL Connections
SSL connections are supported for the `MySQL` and `PostgreSQL` databases. They can be set using the `options` configuration option.
:::info
This feature is heavily dependent on the underlying `QSqlDatabase` module. What means that you can pass the same [connection options](https://doc.qt.io/qt/qsqldatabase.html#setConnectOptions) to the `TinyORM` that the `QSqlDatabase` accepts.
:::
##### MySQL
You have to pass the `SSL_CA`, `SSL_CERT`, `SSL_KEY`, and `MYSQL_OPT_SSL_MODE` options.
<Tabs groupId={shell}>
<TabItem value={pwsh} label={pwsh_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QMYSQL"},
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
...
// highlight-start
{"options", QVariantHash({{"SSL_CA", "C:/mysql/data/ca.pem"},
{"SSL_CERT", "C:/mysql/data/client-cert.pem"},
{"SSL_KEY", "C:/mysql/data/client-key.pem"},
{"MYSQL_OPT_SSL_MODE", "VERIFY_CA"}})},
// highlight-end
});
```
</TabItem>
<TabItem value={bash} label={bash_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QMYSQL"},
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
...
// highlight-start
{"options", QVariantHash({{"SSL_CA", "~/.local/share/TinyORM/ssl/ca.pem"},
{"SSL_CERT", "~/.local/share/TinyORM/ssl/client-cert.pem"},
{"SSL_KEY", "~/.local/share/TinyORM/ssl/client-key.pem"},
{"MYSQL_OPT_SSL_MODE", "VERIFY_CA"}})},
// highlight-end
});
```
</TabItem>
</Tabs>
You may also use the `ConfigUtils::mysqlSslOptions()` or the `ConfigUtils::insertMySqlSslOptions()` methods to insert these options for you and define them using the `DB_MYSQL_SSL_CA`, `DB_MYSQL_SSL_CERT`, `DB_MYSQL_SSL_KEY`, and `DB_MYSQL_SSL_MODE` environment variables.
```cpp
#include <orm/db.hpp>
#include <orm/utils/configuration.hpp>
using Orm::DB;
using ConfigUtils = Orm::Utils::Configuration;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QMYSQL"},
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
...
// highlight-next-line
{"options", ConfigUtils::mysqlSslOptions()},
});
```
You can define these SSL-related options in the top-level configuration, they will be copied to the `options` option hash during configuration parsing. The top-level configuration takes precedence and overwrites the options in the `options` hash.
<Tabs groupId={shell}>
<TabItem value={pwsh} label={pwsh_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QMYSQL"},
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
...
// highlight-start
{"SSL_CA", "C:/mysql/data/ca.pem"},
{"SSL_CERT", "C:/mysql/data/client-cert.pem"},
{"SSL_KEY", "C:/mysql/data/client-key.pem"},
{"MYSQL_OPT_SSL_MODE", "VERIFY_CA"},
// highlight-end
});
```
</TabItem>
<TabItem value={bash} label={bash_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QMYSQL"},
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
...
// highlight-start
{"SSL_CA", "~/.local/share/TinyORM/ssl/ca.pem"},
{"SSL_CERT", "~/.local/share/TinyORM/ssl/client-cert.pem"},
{"SSL_KEY", "~/.local/share/TinyORM/ssl/client-key.pem"},
{"MYSQL_OPT_SSL_MODE", "VERIFY_CA"},
// highlight-end
});
```
</TabItem>
</Tabs>
:::tip
You can take a look at the GitHub actions how the `MySQL` certificates are generated in the CI pipeline for [Windows](https://github.com/silverqx/TinyORM/blob/main/.github/workflows/msvc2022-qt6.yml) and [Linux](https://github.com/silverqx/TinyORM/blob/main/.github/workflows/linux-qt6.yml).
:::
:::tip
You can also pass the `QString` to the `options` configuration separated by the `;` semicolon character and use the `=` to assign values.
:::
##### PostgreSQL
You have to pass the [`sslmode`](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS) or the deprecated [`requiressl`](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-REQUIRESSL) options.
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QPSQL"},
{"host", qEnvironmentVariable("DB_PGSQL_HOST", "127.0.0.1")},
...
// highlight-next-line
{"options", QVariantHash({{"sslmode", "verify-full"}})},
});
```
And place your __client__ certificates to the `~/.postgres/` on [Linux](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-FILE-USAGE) and `$env:APPDATA/postgres/` on Windows. Everything is described in the PostgreSQL's [libpq client](https://www.postgresql.org/docs/current/libpq-ssl.html) and [server](https://www.postgresql.org/docs/15/ssl-tcp.html#SSL-FILE-USAGE) documentation.
If you want to keep your __client__ certificates in your own location, you can set the `sslcert`, `sslkey`, and `sslrootcert` options.
<Tabs groupId={shell}>
<TabItem value={pwsh} label={pwsh_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QPSQL"},
{"host", qEnvironmentVariable("DB_PGSQL_HOST", "127.0.0.1")},
...
// highlight-next-line
{"options", QVariantHash({{"sslmode", "verify-full"},
// highlight-start
{"sslcert", "C:/example/postgres.crt"},
{"sslkey", "C:/example/postgres.key"},
{"sslrootcert", "C:/example/root.crt"}})},
// highlight-end
});
```
</TabItem>
<TabItem value={bash} label={bash_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QPSQL"},
{"host", qEnvironmentVariable("DB_PGSQL_HOST", "127.0.0.1")},
...
// highlight-next-line
{"options", QVariantHash({{"sslmode", "verify-full"},
// highlight-start
{"sslcert", "/example/postgres.crt"},
{"sslkey", "/example/postgres.key"},
{"sslrootcert", "/example/root.crt"}})},
// highlight-end
});
```
</TabItem>
</Tabs>
You can define these SSL-related options in the top-level configuration, they will be copied to the `options` option hash during a configuration parsing. The top-level configuration takes precedence and overwrites the options in the `options` hash.
<Tabs groupId={shell}>
<TabItem value={pwsh} label={pwsh_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QPSQL"},
{"host", qEnvironmentVariable("DB_PGSQL_HOST", "127.0.0.1")},
...
// highlight-start
{"sslmode", "verify-full"},
{"sslcert", "C:/example/postgres.crt"},
{"sslkey", "C:/example/postgres.key"},
{"sslrootcert", "C:/example/root.crt"},
// highlight-end
});
```
</TabItem>
<TabItem value={bash} label={bash_label}>
```cpp
#include <orm/db.hpp>
using Orm::DB;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QPSQL"},
{"host", qEnvironmentVariable("DB_PGSQL_HOST", "127.0.0.1")},
...
// highlight-start
{"sslmode", "verify-full"},
{"sslcert", "/example/postgres.crt"},
{"sslkey", "/example/postgres.key"},
{"sslrootcert", "/example/root.crt"},
// highlight-end
});
```
</TabItem>
</Tabs>
You may also use the `ConfigUtils::postgresSslOptions()` or the `ConfigUtils::insertPostgresSslOptions()` methods to insert the `sslmode`, `sslcert`, `sslkey`, and `sslrootcert` options for you and define them using the `DB_PGSQL_SSLMODE`, `DB_PGSQL_SSLCERT`, `DB_PGSQL_SSLKEY`, and `DB_PGSQL_SSLROOTCERT` environment variable.
```cpp
#include <orm/db.hpp>
#include <orm/utils/configuration.hpp>
using Orm::DB;
using ConfigUtils = Orm::Utils::Configuration;
// Ownership of a shared_ptr()
auto manager = DB::create({
{"driver", "QPSQL"},
{"host", qEnvironmentVariable("DB_PGSQL_HOST", "127.0.0.1")},
...
// highlight-next-line
{"options", ConfigUtils::postgresSslOptions()},
});
```
:::info
The PostgreSQL's libpq client library provides the [`PGSSLMODE`](https://www.postgresql.org/docs/current/libpq-envars.html#id-1.7.3.22.3.4.13.1.1), [`PGSSLCERT`](https://www.postgresql.org/docs/current/libpq-envars.html#id-1.7.3.22.3.4.16.1.1), [`PGSSLKEY`](https://www.postgresql.org/docs/current/libpq-envars.html#id-1.7.3.22.3.4.17.1.1), and [`PGSSLROOTCERT`](https://www.postgresql.org/docs/current/libpq-envars.html#id-1.7.3.22.3.4.18.1.1) environment variables, so you don't have to use TinyORM's `options` configuration and may use these environment variables instead.
:::
:::tip
You can take a look at the GitHub actions how the `PostgreSQL` certificates are generated in the CI pipeline for [Windows](https://github.com/silverqx/TinyORM/blob/main/.github/workflows/msvc2022-qt6.yml) and [Linux](https://github.com/silverqx/TinyORM/blob/main/.github/workflows/linux-qt6.yml).
:::
## Running SQL Queries
Once you have configured your database connection, you may run queries using the `DB` facade. The `DB` facade provides methods for each type of query: `select`, `update`, `insert`, `delete`, and `statement`.
#### Running A Select Query
To run a basic SELECT query, you may use the `select` method on the `DB` facade:
auto users = DB::select("select * from users where active = ?", {1});
The first argument passed to the `select` method is the SQL query, while the second argument is any parameter bindings that need to be bound to the query. Typically, these are the values of the `where` clause constraints. Parameter binding provides protection against SQL injection.
The `select` method returns a `QSqlQuery` containing the results of the query, where each result can be accessed by `QSqlQuery::next` method. Look into the `QSqlQuery` documentation on how to obtain results from the "query". You may access each column's value by `QSqlQuery::value` method. The first `bool` return value is the value returned from `QSqlQuery::exec` method:
```cpp
#include <QDebug>
#include <orm/db.hpp>
auto users = DB::select("select * from users");
while(users.next())
qDebug() << users.value("name").toString();
```
#### Selecting Scalar Values
Sometimes your database query may result in a single, scalar value. Instead of being required to retrieve the query's scalar result from a record instance, TinyORM allows you to retrieve this value directly using the `scalar` shortcut method:
```cpp
#include <orm/db.hpp>
auto states = DB::scalar(
"select count(case when state = 'pending' then 1 end) as states "
"from comments"
);
// With binding
auto states = DB::scalar(
"select count(case when state = ? then 1 end) as states from comments",
{"pending"}
);
```
#### Running An Insert Statement
To execute an `insert` statement, you may use the `insert` method on the `DB` facade. Like `select`, this method accepts the SQL query as its first argument and bindings as its second argument and returns `QSqlQuery`:
```cpp
#include <orm/db.hpp>
DB::insert("insert into users (id, name) values (?, ?)", {1, "Marc"});
```
#### Running An Update Statement
The `update` method should be used to update existing records in the database. The number of rows affected by the statement and `QSqlQuery` is returned by the method as `std::tuple<int, QSqlQuery>`:
```cpp
#include <QDateTime>
#include <orm/db.hpp>
auto [affected, query] = DB::update(
"update users set updated_at = ? where name = ?",
{QDateTime::currentDateTimeUtc(), "Anita"}
);
if (!affected)
qDebug() << "Any record was updated.";
```
#### Running A Delete Statement
The `remove` method should be used to delete records from the database. Like `update`, the number of affected rows and `QSqlQuery` will be returned by the method as `std::tuple<int, QSqlQuery>`:
```cpp
#include <orm/db.hpp>
auto [affected, query] = DB::remove("delete from users");
```
:::note
`delete` can not be used as the method name because it is the reserved word.
:::
#### Running A General Statement
Some database statements do not return any value. For these types of operations, you may use the `statement` method on the `DB` facade:
```cpp
DB::statement("drop table users");
```
:::tip
`DB::statement` method should be used for [DDL](https://en.wikipedia.org/wiki/Data_definition_language) queries, don't use it for "select" queries because it internally calls `recordsHaveBeenModified` method.
:::
#### Running An Unprepared Statement
Sometimes you may want to execute an SQL statement without binding any values. You may use the `DB` facade's `unprepared` method to accomplish this:
```cpp
DB::unprepared("update users set votes = 100 where name = 'Dries'");
```
:::warning
Since unprepared statements do not bind parameters, they may be vulnerable to SQL injection. You should never allow user controlled values within an unprepared statement.
:::
#### Implicit Commits
When using the `DB` facade's `statement` methods within transactions, you must be careful to avoid statements that cause [implicit commits](https://dev.mysql.com/doc/refman/9.0/en/implicit-commit.html). These statements will cause the database engine to indirectly commit the entire transaction, leaving TinyORM unaware of the database's transaction level. An example of such a statement is creating a database table:
```cpp
DB::statement("create table users (name varchar(255) null)");
```
Please refer to the MySQL manual for [a list of all statements](https://dev.mysql.com/doc/refman/9.0/en/implicit-commit.html) that trigger implicit commits.
### Using Multiple Database Connections
You can configure multiple database connections at once during `DatabaseManager` instantiation using the `DB::create` overload, where the first argument is a hash of multiple connections and is of type `QHash<QString, QVariantHash>` and the second argument is the name of the default connection:
```cpp
#include <orm/db.hpp>
// Ownership of a shared_ptr()
auto manager = DB::create({
{"mysql", {
{"driver", "QMYSQL"},
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
{"port", qEnvironmentVariable("DB_MYSQL_PORT", "3306")},
{"database", qEnvironmentVariable("DB_MYSQL_DATABASE", "")},
{"username", qEnvironmentVariable("DB_MYSQL_USERNAME", "root")},
{"password", qEnvironmentVariable("DB_MYSQL_PASSWORD", "")},
{"charset", qEnvironmentVariable("DB_MYSQL_CHARSET", "utf8mb4")},
{"collation", qEnvironmentVariable("DB_MYSQL_COLLATION", "utf8mb4_0900_ai_ci")},
{"strict", true},
{"options", QVariantHash()},
}},
{"sqlite", {
{"driver", "QSQLITE"},
{"database", qEnvironmentVariable("DB_SQLITE_DATABASE", "")},
{"foreign_key_constraints", qEnvironmentVariable("DB_SQLITE_FOREIGN_KEYS", "true")},
{"check_database_exists", true},
{"prefix", ""},
}},
}, "mysql");
```
If your application needs to use multiple connections, you may access each connection via the `connection` method provided by the `DB` facade. The connection name passed to the `connection` method should correspond to one of the connections key listed in your configuration:
```cpp
#include <orm/db.hpp>
auto query = DB::connection("mysql_test").select(...);
```
You may access the raw underlying `QSqlQuery` instance of a connection using the `getSqlQuery` method on a connection instance:
```cpp
auto query = DB::connection().getSqlQuery();
```
Or you can use the shortcut method `sqlQuery` provided by the `DB` facade:
```cpp
auto query = DB::sqlQuery();
```
## Database Transactions
#### Manually Using Transactions
If you would like to begin a transaction manually and have complete control over rollbacks and commits, you may use the `beginTransaction` method provided by the `DB` facade:
```cpp
#include <orm/db.hpp>
DB::beginTransaction();
```
You can rollback the transaction via the `rollBack` method:
```cpp
DB::rollBack();
```
Lastly, you can commit a transaction via the `commit` method:
```cpp
DB::commit();
```
All transaction methods accept a connection name as the optional argument:
```cpp
DB::beginTransaction("mysql_test");
```
:::tip
The `DB` facade's transaction methods control the transactions for both the [query builder](database/query-builder.mdx) and [TinyORM](tinyorm/getting-started.mdx).
:::
## Multi-threading support
The `TinyORM` supports multi-threading for the `MSVC` and `GCC` on Linux compilers. Multi-threading is disabled for the `Clang <14.0.3` compiler on MSYS2, `Clang <14.0.4` on Linux and for the `GCC` compiler on MSYS2. The reason are bugs in the `TLS` wrapper that is generated by the [`thread_local`](https://en.cppreference.com/w/cpp/keyword/thread_local) keyword.
A connection can only be used from within the thread that created it. Moving connections between threads or creating queries from a different thread where the connection was created is not supported.
In addition, the third party libraries used by the `QSqlDrivers` can impose further restrictions on using the SQL Module in a multithreaded program.
In short, if you create a `DB::connection` in some thread then you have to use this connection only from this particular thread and of course all queries that will be executed on this connection.
If you want to execute some query from another thread for the same connection then you have to create a new connection first and if you have a new connection you can send a query from this new thread to the database.
:::warning
The [`schema builder`](database/migrations.mdx#tables) and [`migrations`](database/migrations.mdx) don't support multi-threading.
:::