mirror of
https://github.com/getml/sqlgen.git
synced 2026-02-14 20:58:48 -06:00
committed by
GitHub
parent
1424d29c5d
commit
3f8c5556d2
29
.github/workflows/linux-cxx20-conan.yaml
vendored
29
.github/workflows/linux-cxx20-conan.yaml
vendored
@@ -10,16 +10,37 @@ jobs:
|
||||
include:
|
||||
- compiler: llvm
|
||||
compiler-version: 16
|
||||
link: "static"
|
||||
- compiler: llvm
|
||||
compiler-version: 18
|
||||
link: "static"
|
||||
- compiler: gcc
|
||||
compiler-version: 11
|
||||
additional-dep: "g++-11"
|
||||
link: "static"
|
||||
- compiler: gcc
|
||||
compiler-version: 12
|
||||
link: "static"
|
||||
- compiler: gcc
|
||||
compiler-version: 14
|
||||
name: "${{ github.job }} (${{ matrix.compiler }}-${{ matrix.compiler-version }})"
|
||||
link: "static"
|
||||
- compiler: llvm
|
||||
compiler-version: 16
|
||||
link: "shared"
|
||||
- compiler: llvm
|
||||
compiler-version: 18
|
||||
link: "shared"
|
||||
- compiler: gcc
|
||||
compiler-version: 11
|
||||
additional-dep: "g++-11"
|
||||
link: "shared"
|
||||
- compiler: gcc
|
||||
compiler-version: 12
|
||||
link: "shared"
|
||||
- compiler: gcc
|
||||
compiler-version: 14
|
||||
link: "shared"
|
||||
name: "${{ github.job }} (${{ matrix.compiler }}-${{ matrix.compiler-version }}-${{ matrix.link }})"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -47,4 +68,8 @@ jobs:
|
||||
sudo ln -s $(which ccache) /usr/local/bin/$CC
|
||||
sudo ln -s $(which ccache) /usr/local/bin/$CXX
|
||||
$CXX --version
|
||||
conan build . --build=missing -s compiler.cppstd=gnu20
|
||||
if [[ "${{ matrix.link }}" == "static" ]]; then
|
||||
conan build . --build=missing -s compiler.cppstd=gnu20
|
||||
else
|
||||
conan build . --build=missing -s compiler.cppstd=gnu20 -o sqlgen/*:with_mysql=True -o */*:shared=True
|
||||
fi
|
||||
|
||||
38
.github/workflows/linux-cxx20-vcpkg.yaml
vendored
38
.github/workflows/linux-cxx20-vcpkg.yaml
vendored
@@ -43,6 +43,22 @@ jobs:
|
||||
- compiler: gcc
|
||||
compiler-version: 14
|
||||
db: sqlite
|
||||
- compiler: llvm
|
||||
compiler-version: 16
|
||||
db: mysql
|
||||
- compiler: llvm
|
||||
compiler-version: 18
|
||||
db: mysql
|
||||
- compiler: gcc
|
||||
compiler-version: 11
|
||||
additional-dep: "g++-11"
|
||||
db: mysql
|
||||
- compiler: gcc
|
||||
compiler-version: 12
|
||||
db: mysql
|
||||
- compiler: gcc
|
||||
compiler-version: 14
|
||||
db: mysql
|
||||
name: "${{ github.job }} (${{ matrix.compiler }}-${{ matrix.compiler-version }}-${{ matrix.db }})"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -98,11 +114,33 @@ jobs:
|
||||
$CXX --version
|
||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_POSTGRES=OFF
|
||||
cmake --build build
|
||||
- name: Compile
|
||||
if: matrix.db == 'mysql'
|
||||
run: |
|
||||
if [[ "${{ matrix.compiler }}" == "llvm" ]]; then
|
||||
export CC=clang-${{ matrix.compiler-version }}
|
||||
export CXX=clang++-${{ matrix.compiler-version }}
|
||||
elif [[ "${{ matrix.compiler }}" == "gcc" ]]; then
|
||||
export CC=gcc-${{ matrix.compiler-version }}
|
||||
export CXX=g++-${{ matrix.compiler-version }}
|
||||
fi
|
||||
sudo ln -s $(which ccache) /usr/local/bin/$CC
|
||||
sudo ln -s $(which ccache) /usr/local/bin/$CXX
|
||||
$CXX --version
|
||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-linux-dynamic
|
||||
cmake --build build
|
||||
- name: Set up postgres
|
||||
if: matrix.db == 'postgres'
|
||||
run: |
|
||||
sudo systemctl start postgresql.service
|
||||
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'password';"
|
||||
- name: Set up mysql
|
||||
if: matrix.db == 'mysql'
|
||||
run: |
|
||||
sudo systemctl start mysql
|
||||
sudo mysql -uroot -proot -e "CREATE USER sqlgen IDENTIFIED WITH mysql_native_password BY 'password';"
|
||||
sudo mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'sqlgen';"
|
||||
sudo mysql -uroot -proot -e "SET GLOBAL time_zone = '+0:00';"
|
||||
- name: Run tests
|
||||
run: |
|
||||
ctest --test-dir build --output-on-failure
|
||||
|
||||
14
.github/workflows/macos-cxx20-conan.yaml
vendored
14
.github/workflows/macos-cxx20-conan.yaml
vendored
@@ -9,8 +9,14 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: "macos-latest"
|
||||
link: "static"
|
||||
- os: "macos-13"
|
||||
name: "${{ github.job }} (${{ matrix.os }})"
|
||||
link: "static"
|
||||
- os: "macos-latest"
|
||||
link: "shared"
|
||||
- os: "macos-13"
|
||||
link: "shared"
|
||||
name: "${{ github.job }} (${{ matrix.os }}-${{ matrix.link }})"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -30,4 +36,8 @@ jobs:
|
||||
CXX: clang++
|
||||
run: |
|
||||
$CXX --version
|
||||
conan build . --build=missing -s compiler.cppstd=gnu20
|
||||
if [[ "${{ matrix.link }}" == "static" ]]; then
|
||||
conan build . --build=missing -s compiler.cppstd=gnu20
|
||||
else
|
||||
conan build . --build=missing -s compiler.cppstd=gnu20 -o sqlgen/*:with_mysql=True -o */*:shared=True
|
||||
fi
|
||||
|
||||
21
.github/workflows/macos-cxx20-vcpkg.yaml
vendored
21
.github/workflows/macos-cxx20-vcpkg.yaml
vendored
@@ -15,10 +15,14 @@ jobs:
|
||||
db: postgres
|
||||
- os: "macos-latest"
|
||||
db: sqlite
|
||||
- os: "macos-latest"
|
||||
db: mysql
|
||||
- os: "macos-13"
|
||||
db: postgres
|
||||
- os: "macos-13"
|
||||
db: sqlite
|
||||
- os: "macos-13"
|
||||
db: mysql
|
||||
name: "${{ github.job }} (${{ matrix.os }}-${{ matrix.db }})"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@@ -70,6 +74,23 @@ jobs:
|
||||
$CXX --version
|
||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_POSTGRES=OFF
|
||||
cmake --build build -j 4
|
||||
- name: Compile
|
||||
if: matrix.db == 'mysql'
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
run: |
|
||||
if [[ "${{ matrix.os == 'macos-latest' }}" == "true" ]]; then
|
||||
export VCPKG_FORCE_SYSTEM_BINARIES=arm
|
||||
export CMAKE_GENERATOR=Ninja
|
||||
fi
|
||||
$CXX --version
|
||||
if [[ "${{ matrix.os == 'macos-latest' }}" == "true" ]]; then
|
||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=arm64-osx-dynamic -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
|
||||
else
|
||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-osx-dynamic -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
|
||||
fi
|
||||
cmake --build build -j 4
|
||||
- name: Run tests
|
||||
run: |
|
||||
ctest --test-dir build --output-on-failure
|
||||
|
||||
6
.github/workflows/windows-cxx20-vcpkg.yaml
vendored
6
.github/workflows/windows-cxx20-vcpkg.yaml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
include:
|
||||
- db: postgres
|
||||
- db: sqlite
|
||||
- db: mysql
|
||||
name: "(windows-${{ matrix.db }})"
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
@@ -39,6 +40,11 @@ jobs:
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_POSTGRES=OFF
|
||||
cmake --build build --config Release -j4
|
||||
- name: Compile
|
||||
if: matrix.db == 'mysql'
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
|
||||
cmake --build build --config Release -j4
|
||||
- name: Run tests
|
||||
run: |
|
||||
ctest --test-dir build --output-on-failure
|
||||
|
||||
@@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.23)
|
||||
|
||||
option(SQLGEN_BUILD_SHARED "Build shared library" ${BUILD_SHARED_LIBS})
|
||||
|
||||
option(SQLGEN_MYSQL "Enable MySQL support" OFF)
|
||||
|
||||
option(SQLGEN_POSTGRES "Enable PostgreSQL support" ON) # enabled by default
|
||||
|
||||
option(SQLGEN_SQLITE3 "Enable SQLite3 support" ON) # enabled by default
|
||||
@@ -15,6 +17,7 @@ if (NOT DEFINED CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
endif()
|
||||
|
||||
|
||||
set(SQLGEN_USE_VCPKG_DEFAULT ON)
|
||||
|
||||
option(SQLGEN_USE_VCPKG "Use VCPKG to download and build dependencies" ${SQLGEN_USE_VCPKG_DEFAULT})
|
||||
@@ -24,6 +27,10 @@ if (SQLGEN_USE_VCPKG)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "tests")
|
||||
endif()
|
||||
|
||||
if (SQLGEN_MYSQL)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "mysql")
|
||||
endif()
|
||||
|
||||
if (SQLGEN_POSTGRES)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "postgres")
|
||||
endif()
|
||||
@@ -31,11 +38,11 @@ if (SQLGEN_USE_VCPKG)
|
||||
if (SQLGEN_SQLITE3)
|
||||
list(APPEND VCPKG_MANIFEST_FEATURES "sqlite3")
|
||||
endif()
|
||||
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")
|
||||
endif ()
|
||||
|
||||
project(sqlgen VERSION 0.0.1 LANGUAGES CXX)
|
||||
project(sqlgen VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
if (SQLGEN_BUILD_SHARED)
|
||||
add_library(sqlgen SHARED)
|
||||
@@ -59,6 +66,21 @@ target_include_directories(
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
if(SQLGEN_MYSQL)
|
||||
list(APPEND SQLGEN_SOURCES src/sqlgen_mysql.cpp)
|
||||
if (SQLGEN_USE_VCPKG)
|
||||
if (NOT TARGET unofficial-libmariadb)
|
||||
find_package(unofficial-libmariadb CONFIG REQUIRED)
|
||||
endif()
|
||||
target_link_libraries(sqlgen PUBLIC unofficial::libmariadb)
|
||||
else()
|
||||
if (NOT TARGET mariadb-connector-c)
|
||||
find_package(mariadb-connector-c REQUIRED)
|
||||
endif()
|
||||
target_link_libraries(sqlgen PUBLIC mariadb-connector-c::mariadb-connector-c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (SQLGEN_POSTGRES)
|
||||
list(APPEND SQLGEN_SOURCES src/sqlgen_postgres.cpp)
|
||||
if (NOT TARGET PostgreSQL)
|
||||
|
||||
29
README.md
29
README.md
@@ -23,6 +23,16 @@ Together, reflect-cpp and sqlgen enable reliable and efficient ETL pipelines.
|
||||
- 🔌 **Multiple Backends**: Support for PostgreSQL and SQLite
|
||||
- 🔍 **Reflection Integration**: Seamless integration with [reflect-cpp](https://github.com/getml/reflect-cpp)
|
||||
|
||||
## Supported databases
|
||||
|
||||
The following table lists the databases currently supported by sqlgen and the underlying libraries used:
|
||||
|
||||
| Database | Library | Version | License | Remarks |
|
||||
|---------------|--------------------------------------------------------------------------|--------------|---------------| -----------------------------------------------------|
|
||||
| MySQL/MariaDB | [libmariadb](https://github.com/mariadb-corporation/mariadb-connector-c) | >= 3.4.5 | LGPL | |
|
||||
| PostgreSQL | [libpq](https://github.com/postgres/postgres) | >= 16.4 | PostgreSQL | Will work for all libpq-compatible databases |
|
||||
| sqlite | [sqlite](https://sqlite.org/index.html) | >= 3.49.1 | Public Domain | |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation using vcpkg
|
||||
@@ -47,6 +57,14 @@ cmake --build build -j 4 # gcc, clang
|
||||
cmake --build build --config Release -j 4 # MSVC
|
||||
```
|
||||
|
||||
This will build the static library. To build the shared library
|
||||
add `-DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=...` to the first line.
|
||||
Run `./vcpkg/vcpkg help triplets` to view all supported triplets.
|
||||
Common triplets for shared libraries are `x64-linux-dynamic`,
|
||||
`arm64-osx-dynamic` or `x64-osx-dynamic`.
|
||||
|
||||
Add `-DSQLGEN_BUILD_MYSQL=ON` to support MySQL/MariaDB.
|
||||
|
||||
4. Include in your CMake project:
|
||||
```cmake
|
||||
find_package(sqlgen REQUIRED)
|
||||
@@ -70,6 +88,17 @@ For older versions of pip, you can also use `pip` instead of `pipx`.
|
||||
conan build . --build=missing -s compiler.cppstd=gnu20
|
||||
```
|
||||
|
||||
This will build the static library. To build the shared library,
|
||||
add `-o */*:shared=True`.
|
||||
|
||||
Add `-o sqlgen/*:with_mysql=True` to support MySQL/MariaDB.
|
||||
|
||||
3. Include in your CMake project:
|
||||
```cmake
|
||||
find_package(sqlgen REQUIRED)
|
||||
target_link_libraries(your_target PRIVATE sqlgen::sqlgen)
|
||||
```
|
||||
|
||||
You can call `conan inspect .` to get an overview of the supported options.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
@@ -26,12 +26,14 @@ class SQLGenConan(ConanFile):
|
||||
options = {
|
||||
"shared": [True, False],
|
||||
"fPIC": [True, False],
|
||||
"with_mysql": [True, False],
|
||||
"with_postgres": [True, False],
|
||||
"with_sqlite3": [True, False],
|
||||
}
|
||||
default_options = {
|
||||
"shared": False,
|
||||
"fPIC": True,
|
||||
"with_mysql": False,
|
||||
"with_postgres": True,
|
||||
"with_sqlite3": True,
|
||||
}
|
||||
@@ -46,6 +48,8 @@ class SQLGenConan(ConanFile):
|
||||
|
||||
def requirements(self):
|
||||
self.requires("reflect-cpp/0.19.0", transitive_headers=True)
|
||||
if self.options.with_mysql:
|
||||
self.requires("mariadb-connector-c/3.4.3", transitive_headers=True)
|
||||
if self.options.with_postgres:
|
||||
self.requires("libpq/17.5", transitive_headers=True)
|
||||
if self.options.with_sqlite3:
|
||||
@@ -71,6 +75,7 @@ class SQLGenConan(ConanFile):
|
||||
deps.generate()
|
||||
tc = CMakeToolchain(self)
|
||||
tc.cache_variables["SQLGEN_BUILD_SHARED"] = self.options.shared
|
||||
tc.cache_variables["SQLGEN_MYSQL"] = self.options.with_mysql
|
||||
tc.cache_variables["SQLGEN_POSTGRES"] = self.options.with_postgres
|
||||
tc.cache_variables["SQLGEN_SQLITE3"] = self.options.with_sqlite3
|
||||
tc.cache_variables["SQLGEN_USE_VCPKG"] = False
|
||||
|
||||
@@ -50,6 +50,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
|
||||
|
||||
## Supported Databases
|
||||
|
||||
- [MySQL](mysql.md) - How to interact with MariaDB and MySQL
|
||||
- [PostgreSQL](postgres.md) - How to interact with PostgreSQL and compatible databases (Redshift, Aurora, Greenplum, CockroachDB, ...)
|
||||
- [SQLite](sqlite.md) - How to interact with SQLite3
|
||||
|
||||
|
||||
176
docs/mysql.md
Normal file
176
docs/mysql.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# `sqlgen::mysql`
|
||||
|
||||
The `sqlgen::mysql` module provides a type-safe and efficient interface for interacting with MySQL and MariaDB databases. It implements the core database operations through a connection-based API with support for prepared statements, transactions, and efficient data iteration.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Connection
|
||||
|
||||
Create a connection to a MySQL/MariaDB database using credentials:
|
||||
|
||||
```cpp
|
||||
// Create credentials for the database connection
|
||||
const auto creds = sqlgen::mysql::Credentials{
|
||||
.host = "localhost",
|
||||
.user = "myuser",
|
||||
.password = "mypassword",
|
||||
.dbname = "mydatabase",
|
||||
.port = 3306, // Optional, defaults to 3306
|
||||
.unix_socket = "/var/run/mysqld/mysqld.sock" // Optional, defaults to "/var/run/mysqld/mysqld.sock"
|
||||
};
|
||||
|
||||
// Connect to the database
|
||||
const auto conn = sqlgen::mysql::connect(creds);
|
||||
```
|
||||
|
||||
The connection is wrapped in a `sqlgen::Result<Ref<Connection>>` for error handling:
|
||||
|
||||
```cpp
|
||||
// Handle connection errors
|
||||
const auto conn = sqlgen::mysql::connect(creds);
|
||||
if (!conn) {
|
||||
// Handle error...
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query = sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c < 18 and "first_name"_c != "Hugo");
|
||||
|
||||
// Use the connection
|
||||
const auto minors = query(conn);
|
||||
```
|
||||
|
||||
### Basic Operations
|
||||
|
||||
Write data to the database:
|
||||
|
||||
```cpp
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
const auto people = std::vector<Person>{
|
||||
Person{.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}
|
||||
};
|
||||
|
||||
// Write data to database
|
||||
const auto result = sqlgen::write(conn, people);
|
||||
```
|
||||
|
||||
Read data with filtering and ordering:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
// Read all people ordered by age
|
||||
const auto all_people = sqlgen::read<std::vector<Person>> |
|
||||
order_by("age"_c);
|
||||
|
||||
// Read minors only
|
||||
const auto minors = sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c < 18) |
|
||||
order_by("age"_c);
|
||||
|
||||
// Use the queries
|
||||
const auto result1 = all_people(conn);
|
||||
const auto result2 = minors(conn);
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
Perform operations within transactions:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
// Delete a person and update another in a transaction
|
||||
const auto delete_hugo = delete_from<Person> |
|
||||
where("first_name"_c == "Hugo");
|
||||
|
||||
const auto update_homer = update<Person>("age"_c.set(46)) |
|
||||
where("first_name"_c == "Homer");
|
||||
|
||||
const auto result = begin_transaction(conn)
|
||||
.and_then(delete_hugo)
|
||||
.and_then(update_homer)
|
||||
.and_then(commit)
|
||||
.value();
|
||||
```
|
||||
|
||||
### Advanced Queries
|
||||
|
||||
Use complex queries with joins and projections:
|
||||
|
||||
```cpp
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
// Select specific columns with aliases
|
||||
struct FirstName {
|
||||
std::string first_name;
|
||||
};
|
||||
|
||||
const auto names = select_from<Person>("first_name"_c) |
|
||||
order_by("id"_c) |
|
||||
to<std::vector<FirstName>>;
|
||||
|
||||
// Join operations
|
||||
const auto joined_data = select_from<Person, "t1">(
|
||||
"id"_t1 | as<"id">,
|
||||
"first_name"_t1 | as<"first_name">,
|
||||
"last_name"_t2 | as<"last_name">,
|
||||
"age"_t2 | as<"age">) |
|
||||
left_join<Person, "t2">("id"_t1 == "id"_t2) |
|
||||
order_by("id"_t1) |
|
||||
to<std::vector<Person>>;
|
||||
```
|
||||
|
||||
### Connection Pools
|
||||
|
||||
Use connection pools for efficient resource management:
|
||||
|
||||
```cpp
|
||||
const auto pool_config = sqlgen::ConnectionPoolConfig{.size = 2};
|
||||
|
||||
const auto pool = sqlgen::make_connection_pool<sqlgen::mysql::Connection>(
|
||||
pool_config, credentials);
|
||||
|
||||
// Use the pool in a session
|
||||
const auto result = session(pool)
|
||||
.and_then(write(std::ref(people)))
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The module provides a type-safe interface for MySQL/MariaDB operations
|
||||
- All operations return `sqlgen::Result<T>` for error handling
|
||||
- Prepared statements are used for efficient query execution
|
||||
- The iterator interface supports batch processing of results
|
||||
- SQL generation adapts to MySQL's dialect
|
||||
- The module supports:
|
||||
- Connection management with credentials (host, port, database name, unix socket)
|
||||
- Transactions (begin, commit, rollback)
|
||||
- Prepared statements for efficient execution
|
||||
- Efficient batch operations
|
||||
- Type-safe SQL generation
|
||||
- Error handling through `Result<T>`
|
||||
- Resource management through `Ref<T>`
|
||||
- Connection pooling for high-performance applications
|
||||
- Auto-incrementing primary keys
|
||||
- Various data types including VARCHAR, TIMESTAMP, DATE
|
||||
- Complex queries with WHERE clauses, ORDER BY, LIMIT, JOINs
|
||||
- LIKE and pattern matching operations
|
||||
- Mathematical operations and string functions
|
||||
|
||||
@@ -12,7 +12,7 @@ using Type =
|
||||
rfl::TaggedUnion<"type", types::Unknown, types::Boolean, types::Float32,
|
||||
types::Float64, types::Int8, types::Int16, types::Int32,
|
||||
types::Int64, types::UInt8, types::UInt16, types::UInt32,
|
||||
types::UInt64, types::Text, types::Timestamp,
|
||||
types::UInt64, types::Text, types::Date, types::Timestamp,
|
||||
types::TimestampWithTZ, types::VarChar>;
|
||||
|
||||
} // namespace sqlgen::dynamic
|
||||
|
||||
@@ -65,6 +65,11 @@ struct Text {
|
||||
Properties properties;
|
||||
};
|
||||
|
||||
struct Date {
|
||||
std::string tz;
|
||||
Properties properties;
|
||||
};
|
||||
|
||||
struct Timestamp {
|
||||
std::string tz;
|
||||
Properties properties;
|
||||
|
||||
@@ -23,6 +23,15 @@ std::string replace_all(const std::string& _str, const std::string& _from,
|
||||
std::vector<std::string> split(const std::string& _str,
|
||||
const std::string& _delimiter);
|
||||
|
||||
std::string ltrim(const std::string& _str, const std::string& _chars = " ");
|
||||
|
||||
std::string rtrim(const std::string& _str, const std::string& _chars = " ");
|
||||
|
||||
inline std::string trim(const std::string& _str,
|
||||
const std::string& _chars = " ") {
|
||||
return ltrim(rtrim(_str, _chars), _chars);
|
||||
}
|
||||
|
||||
} // namespace sqlgen::internal::strings
|
||||
|
||||
#endif
|
||||
|
||||
9
include/sqlgen/mysql.hpp
Normal file
9
include/sqlgen/mysql.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef SQLGEN_MYSQL_HPP_
|
||||
#define SQLGEN_MYSQL_HPP_
|
||||
|
||||
#include "../sqlgen.hpp"
|
||||
#include "mysql/Credentials.hpp"
|
||||
#include "mysql/connect.hpp"
|
||||
#include "mysql/to_sql.hpp"
|
||||
|
||||
#endif
|
||||
97
include/sqlgen/mysql/Connection.hpp
Normal file
97
include/sqlgen/mysql/Connection.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifndef SQLGEN_MYSQL_CONNECTION_HPP_
|
||||
#define SQLGEN_MYSQL_CONNECTION_HPP_
|
||||
|
||||
#include <mysql.h>
|
||||
|
||||
#include <memory>
|
||||
#include <rfl.hpp>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "../IteratorBase.hpp"
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "../Transaction.hpp"
|
||||
#include "../dynamic/Column.hpp"
|
||||
#include "../dynamic/Statement.hpp"
|
||||
#include "../dynamic/Write.hpp"
|
||||
#include "../is_connection.hpp"
|
||||
#include "Credentials.hpp"
|
||||
#include "exec.hpp"
|
||||
#include "to_sql.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
class Connection {
|
||||
using ConnPtr = Ref<MYSQL>;
|
||||
using StmtPtr = std::shared_ptr<MYSQL_STMT>;
|
||||
|
||||
public:
|
||||
Connection(const Credentials& _credentials)
|
||||
: conn_(make_conn(_credentials)) {}
|
||||
|
||||
static rfl::Result<Ref<Connection>> make(
|
||||
const Credentials& _credentials) noexcept;
|
||||
|
||||
~Connection() = default;
|
||||
|
||||
Result<Nothing> begin_transaction() noexcept {
|
||||
return execute("START TRANSACTION;");
|
||||
}
|
||||
|
||||
Result<Nothing> commit() noexcept { return execute("COMMIT;"); }
|
||||
|
||||
Result<Nothing> execute(const std::string& _sql) noexcept {
|
||||
return exec(conn_, _sql);
|
||||
}
|
||||
|
||||
Result<Nothing> insert(
|
||||
const dynamic::Insert& _stmt,
|
||||
const std::vector<std::vector<std::optional<std::string>>>&
|
||||
_data) noexcept;
|
||||
|
||||
Result<Ref<IteratorBase>> read(const dynamic::SelectFrom& _query);
|
||||
|
||||
Result<Nothing> rollback() noexcept { return execute("ROLLBACK;"); }
|
||||
|
||||
std::string to_sql(const dynamic::Statement& _stmt) noexcept {
|
||||
return to_sql_impl(_stmt);
|
||||
}
|
||||
|
||||
Result<Nothing> start_write(const dynamic::Write& _stmt);
|
||||
|
||||
Result<Nothing> write(
|
||||
const std::vector<std::vector<std::optional<std::string>>>& _data);
|
||||
|
||||
Result<Nothing> end_write();
|
||||
|
||||
private:
|
||||
/// Actually inserts data based on a prepared statement -
|
||||
/// used by both .insert(...) and .write(...).
|
||||
Result<Nothing> actual_insert(
|
||||
const std::vector<std::vector<std::optional<std::string>>>& _data,
|
||||
MYSQL_STMT* _stmt) const noexcept;
|
||||
|
||||
static ConnPtr make_conn(const Credentials& _credentials);
|
||||
|
||||
Result<StmtPtr> prepare_statement(
|
||||
const std::variant<dynamic::Insert, dynamic::Write>& _stmt)
|
||||
const noexcept;
|
||||
|
||||
private:
|
||||
/// A prepared statement - needed for the read and write operations. Note that
|
||||
/// we have declared it before conn_, meaning it will be destroyed first.
|
||||
StmtPtr stmt_;
|
||||
|
||||
/// The underlying connection.
|
||||
ConnPtr conn_;
|
||||
};
|
||||
|
||||
static_assert(is_connection<Connection>,
|
||||
"Must fulfill the is_connection concept.");
|
||||
static_assert(is_connection<Transaction<Connection>>,
|
||||
"Must fulfill the is_connection concept.");
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
|
||||
#endif
|
||||
19
include/sqlgen/mysql/Credentials.hpp
Normal file
19
include/sqlgen/mysql/Credentials.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef SQLGEN_MYSQL_CREDENTIALS_HPP_
|
||||
#define SQLGEN_MYSQL_CREDENTIALS_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
struct Credentials {
|
||||
std::string host;
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string dbname = "mysql";
|
||||
int port = 3306;
|
||||
std::string unix_socket = "/var/run/mysqld/mysqld.sock";
|
||||
};
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
|
||||
#endif
|
||||
50
include/sqlgen/mysql/Iterator.hpp
Normal file
50
include/sqlgen/mysql/Iterator.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef SQLGEN_MYSQL_ITERATOR_HPP_
|
||||
#define SQLGEN_MYSQL_ITERATOR_HPP_
|
||||
|
||||
#include <mysql.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../IteratorBase.hpp"
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
#include "Connection.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
class Iterator : public sqlgen::IteratorBase {
|
||||
using ConnPtr = Ref<MYSQL>;
|
||||
using ResPtr = Ref<MYSQL_RES>;
|
||||
|
||||
public:
|
||||
Iterator(const ResPtr& _res, const ConnPtr& _conn);
|
||||
|
||||
~Iterator();
|
||||
|
||||
/// Whether the end of the available data has been reached.
|
||||
bool end() const final { return end_; }
|
||||
|
||||
/// Returns the next batch of rows.
|
||||
/// If _batch_size is greater than the number of rows left, returns all
|
||||
/// of the rows left.
|
||||
Result<std::vector<std::vector<std::optional<std::string>>>> next(
|
||||
const size_t _batch_size) final;
|
||||
|
||||
private:
|
||||
/// The underlying mysql result.
|
||||
ResPtr res_;
|
||||
|
||||
/// The underlying mysql connection. We have this in here to prevent its
|
||||
/// destruction for the lifetime of the iterator.
|
||||
ConnPtr conn_;
|
||||
|
||||
/// Whether the end is reached.
|
||||
bool end_;
|
||||
};
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
|
||||
#endif
|
||||
17
include/sqlgen/mysql/connect.hpp
Normal file
17
include/sqlgen/mysql/connect.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef SQLGEN_MYSQL_CONNECT_HPP_
|
||||
#define SQLGEN_MYSQL_CONNECT_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Connection.hpp"
|
||||
#include "Credentials.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
inline auto connect(const Credentials& _credentials) {
|
||||
return Connection::make(_credentials);
|
||||
}
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
|
||||
#endif
|
||||
17
include/sqlgen/mysql/exec.hpp
Normal file
17
include/sqlgen/mysql/exec.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef SQLGEN_MYSQL_EXEC_HPP_
|
||||
#define SQLGEN_MYSQL_EXEC_HPP_
|
||||
|
||||
#include <mysql.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
Result<Nothing> exec(const Ref<MYSQL>& _conn, const std::string& _sql) noexcept;
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
|
||||
#endif
|
||||
22
include/sqlgen/mysql/make_error.hpp
Normal file
22
include/sqlgen/mysql/make_error.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SQLGEN_MYSQL_MAKE_ERROR_HPP_
|
||||
#define SQLGEN_MYSQL_MAKE_ERROR_HPP_
|
||||
|
||||
#include <mysql.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "../Ref.hpp"
|
||||
#include "../Result.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
inline rfl::Unexpected<Error> make_error(const Ref<MYSQL>& _conn) noexcept {
|
||||
return error("MySQL error (" + std::to_string(mysql_errno(_conn.get())) +
|
||||
") [" + mysql_sqlstate(_conn.get()) + "] " +
|
||||
mysql_error(_conn.get()));
|
||||
}
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
|
||||
#endif
|
||||
27
include/sqlgen/mysql/to_sql.hpp
Normal file
27
include/sqlgen/mysql/to_sql.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef SQLGEN_MYSQL_TO_SQL_HPP_
|
||||
#define SQLGEN_MYSQL_TO_SQL_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../dynamic/Statement.hpp"
|
||||
#include "../transpilation/to_sql.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
/// Transpiles a dynamic general SQL statement to the mysql dialect.
|
||||
std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept;
|
||||
|
||||
/// Transpiles any SQL statement to the mysql dialect.
|
||||
template <class T>
|
||||
std::string to_sql(const T& _t) noexcept {
|
||||
if constexpr (std::is_same_v<std::remove_cvref_t<T>, dynamic::Statement>) {
|
||||
return to_sql_impl(_t);
|
||||
} else {
|
||||
return to_sql_impl(transpilation::to_sql(_t));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
|
||||
#endif
|
||||
@@ -29,10 +29,16 @@ struct Parser<Timestamp<_format>> {
|
||||
|
||||
static dynamic::Type to_type() noexcept {
|
||||
const std::string format = typename TSType::Format().str();
|
||||
if (format.find("%z") == std::string::npos) {
|
||||
if (format.find("%z") != std::string::npos) {
|
||||
return dynamic::types::TimestampWithTZ{};
|
||||
} else if (format.find("%H") != std::string::npos ||
|
||||
format.find("%M") != std::string::npos ||
|
||||
format.find("%S") != std::string::npos ||
|
||||
format.find("%R") != std::string::npos ||
|
||||
format.find("%T") != std::string::npos) {
|
||||
return dynamic::types::Timestamp{};
|
||||
} else {
|
||||
return dynamic::types::TimestampWithTZ{};
|
||||
return dynamic::types::Date{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "sqlgen/internal/strings/strings.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace sqlgen::internal::strings {
|
||||
|
||||
char to_lower(const char ch) {
|
||||
@@ -71,4 +73,26 @@ std::vector<std::string> split(const std::string& _str,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ltrim(const std::string& _str, const std::string& _chars) {
|
||||
auto str = _str;
|
||||
str.erase(str.begin(), std::find_if(str.begin(), str.end(), [&](char ch1) {
|
||||
return std::none_of(_chars.begin(), _chars.end(),
|
||||
[&](char ch2) { return ch1 == ch2; });
|
||||
}));
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string rtrim(const std::string& _str, const std::string& _chars) {
|
||||
auto str = _str;
|
||||
str.erase(std::find_if(str.rbegin(), str.rend(),
|
||||
[&](char ch1) {
|
||||
return std::none_of(
|
||||
_chars.begin(), _chars.end(),
|
||||
[&](char ch2) { return ch1 == ch2; });
|
||||
})
|
||||
.base(),
|
||||
str.end());
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace sqlgen::internal::strings
|
||||
|
||||
178
src/sqlgen/mysql/Connection.cpp
Normal file
178
src/sqlgen/mysql/Connection.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "sqlgen/mysql/Connection.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "sqlgen/internal/collect/vector.hpp"
|
||||
#include "sqlgen/internal/strings/strings.hpp"
|
||||
#include "sqlgen/mysql/Iterator.hpp"
|
||||
#include "sqlgen/mysql/make_error.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
Result<Nothing> Connection::actual_insert(
|
||||
const std::vector<std::vector<std::optional<std::string>>>& _data,
|
||||
MYSQL_STMT* _stmt) const noexcept {
|
||||
const auto num_params = static_cast<size_t>(mysql_stmt_param_count(_stmt));
|
||||
|
||||
std::vector<MYSQL_BIND> bind(num_params);
|
||||
|
||||
std::vector<long unsigned int> lengths(num_params);
|
||||
std::vector<my_bool> is_null(num_params);
|
||||
|
||||
for (const auto& row : _data) {
|
||||
memset(bind.data(), 0, sizeof(MYSQL_BIND) * num_params);
|
||||
|
||||
if (row.size() != num_params) {
|
||||
return error("Expected " + std::to_string(num_params) + " fields, got " +
|
||||
std::to_string(row.size()) + ".");
|
||||
}
|
||||
|
||||
auto buffer = row;
|
||||
|
||||
for (size_t i = 0; i < num_params; ++i) {
|
||||
if (buffer[i]) {
|
||||
lengths[i] = static_cast<long unsigned int>(row[i]->size());
|
||||
is_null[i] = 0;
|
||||
|
||||
bind[i].buffer_type = MYSQL_TYPE_STRING;
|
||||
bind[i].buffer = &((*buffer[i])[0]);
|
||||
bind[i].buffer_length = lengths[i];
|
||||
bind[i].is_null = &(is_null[i]);
|
||||
bind[i].length = &(lengths[i]);
|
||||
} else {
|
||||
lengths[i] = 0;
|
||||
is_null[i] = 1;
|
||||
|
||||
bind[i].buffer_type = MYSQL_TYPE_NULL;
|
||||
bind[i].buffer = nullptr;
|
||||
bind[i].buffer_length = 0;
|
||||
bind[i].is_null = &(is_null[i]);
|
||||
bind[i].length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto err = mysql_stmt_bind_param(_stmt, bind.data());
|
||||
if (err) {
|
||||
return make_error(conn_);
|
||||
}
|
||||
|
||||
err = mysql_stmt_execute(_stmt);
|
||||
if (err) {
|
||||
return make_error(conn_);
|
||||
}
|
||||
}
|
||||
|
||||
return Nothing{};
|
||||
}
|
||||
|
||||
Result<Nothing> Connection::insert(
|
||||
const dynamic::Insert& _stmt,
|
||||
const std::vector<std::vector<std::optional<std::string>>>&
|
||||
_data) noexcept {
|
||||
if (_data.size() == 0) {
|
||||
return Nothing{};
|
||||
}
|
||||
return prepare_statement(_stmt).and_then(
|
||||
[&](auto&& _stmt_ptr) { return actual_insert(_data, _stmt_ptr.get()); });
|
||||
}
|
||||
|
||||
rfl::Result<Ref<Connection>> Connection::make(
|
||||
const Credentials& _credentials) noexcept {
|
||||
try {
|
||||
return Ref<Connection>::make(_credentials);
|
||||
} catch (std::exception& e) {
|
||||
return error(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
typename Connection::ConnPtr Connection::make_conn(
|
||||
const Credentials& _credentials) {
|
||||
const auto raw_ptr = mysql_init(nullptr);
|
||||
|
||||
const auto shared_ptr = std::shared_ptr<MYSQL>(raw_ptr, mysql_close);
|
||||
|
||||
const auto res = mysql_real_connect(
|
||||
shared_ptr.get(), _credentials.host.c_str(), _credentials.user.c_str(),
|
||||
_credentials.password.c_str(), _credentials.dbname.c_str(),
|
||||
_credentials.port, _credentials.unix_socket.c_str(),
|
||||
CLIENT_MULTI_STATEMENTS);
|
||||
|
||||
if (!res) {
|
||||
throw std::runtime_error(
|
||||
make_error(ConnPtr::make(shared_ptr).value()).error().what());
|
||||
}
|
||||
|
||||
return ConnPtr::make(shared_ptr).value();
|
||||
}
|
||||
|
||||
Result<Connection::StmtPtr> Connection::prepare_statement(
|
||||
const std::variant<dynamic::Insert, dynamic::Write>& _stmt) const noexcept {
|
||||
const auto sql = std::visit(to_sql_impl, _stmt);
|
||||
const auto stmt_ptr = StmtPtr(mysql_stmt_init(conn_.get()), mysql_stmt_close);
|
||||
const auto err = mysql_stmt_prepare(stmt_ptr.get(), sql.c_str(),
|
||||
static_cast<unsigned long>(sql.size()));
|
||||
if (err) {
|
||||
return make_error(conn_);
|
||||
}
|
||||
return stmt_ptr;
|
||||
}
|
||||
|
||||
Result<Ref<IteratorBase>> Connection::read(const dynamic::SelectFrom& _query) {
|
||||
const auto sql = mysql::to_sql_impl(_query);
|
||||
const auto err =
|
||||
mysql_real_query(conn_.get(), sql.c_str(), static_cast<int>(sql.size()));
|
||||
if (err) {
|
||||
return make_error(conn_);
|
||||
}
|
||||
const auto raw_ptr = mysql_use_result(conn_.get());
|
||||
if (!raw_ptr) {
|
||||
return make_error(conn_);
|
||||
}
|
||||
return Ref<MYSQL_RES>::make(
|
||||
std::shared_ptr<MYSQL_RES>(raw_ptr, mysql_free_result))
|
||||
.transform([&](auto&& _res) { return Ref<Iterator>::make(_res, conn_); });
|
||||
}
|
||||
|
||||
Result<Nothing> Connection::start_write(const dynamic::Write& _write_stmt) {
|
||||
if (stmt_) {
|
||||
return error(
|
||||
"A write operation has already been launched. You need to call "
|
||||
".end_write() before you can start another.");
|
||||
}
|
||||
return begin_transaction()
|
||||
.and_then([&](auto&&) { return prepare_statement(_write_stmt); })
|
||||
.transform([&](auto&& _stmt) {
|
||||
stmt_ = _stmt;
|
||||
return Nothing{};
|
||||
})
|
||||
.or_else([&](auto&& _err) {
|
||||
rollback();
|
||||
return error(_err.what());
|
||||
});
|
||||
}
|
||||
|
||||
Result<Nothing> Connection::write(
|
||||
const std::vector<std::vector<std::optional<std::string>>>& _data) {
|
||||
if (!stmt_) {
|
||||
return error(
|
||||
" You need to call .start_write(...) before you can call "
|
||||
".write(...).");
|
||||
}
|
||||
return actual_insert(_data, stmt_.get()).or_else([&](const auto& _err) {
|
||||
rollback();
|
||||
stmt_ = nullptr;
|
||||
return error(_err.what());
|
||||
});
|
||||
}
|
||||
|
||||
Result<Nothing> Connection::end_write() {
|
||||
stmt_ = nullptr;
|
||||
return commit();
|
||||
}
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
44
src/sqlgen/mysql/Iterator.cpp
Normal file
44
src/sqlgen/mysql/Iterator.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "sqlgen/mysql/Iterator.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
Iterator::Iterator(const ResPtr& _res, const ConnPtr& _conn)
|
||||
: res_(_res), conn_(_conn), end_(false) {}
|
||||
|
||||
Iterator::~Iterator() = default;
|
||||
|
||||
Result<std::vector<std::vector<std::optional<std::string>>>> Iterator::next(
|
||||
const size_t _batch_size) {
|
||||
std::vector<std::vector<std::optional<std::string>>> vec;
|
||||
|
||||
const unsigned int num_fields = mysql_num_fields(res_.get());
|
||||
|
||||
for (size_t i = 0; i < _batch_size; ++i) {
|
||||
MYSQL_ROW row = mysql_fetch_row(res_.get());
|
||||
|
||||
if (!row) {
|
||||
const auto err = mysql_error(conn_.get());
|
||||
if (*err) {
|
||||
return error(err);
|
||||
}
|
||||
end_ = true;
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<std::optional<std::string>> res_row(num_fields);
|
||||
|
||||
for (unsigned int j = 0; j < num_fields; ++j) {
|
||||
if (row[j]) {
|
||||
res_row[j] = std::string(row[j]);
|
||||
} else {
|
||||
res_row[j] = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
vec.emplace_back(std::move(res_row));
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
30
src/sqlgen/mysql/exec.cpp
Normal file
30
src/sqlgen/mysql/exec.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "sqlgen/mysql/exec.hpp"
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "sqlgen/mysql/make_error.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
Result<Nothing> exec(const Ref<MYSQL>& _conn,
|
||||
const std::string& _sql) noexcept {
|
||||
const auto err = mysql_real_query(_conn.get(), _sql.c_str(),
|
||||
static_cast<int>(_sql.size()));
|
||||
|
||||
if (err) {
|
||||
return make_error(_conn);
|
||||
}
|
||||
|
||||
const auto raw_ptr = mysql_store_result(_conn.get());
|
||||
|
||||
if (raw_ptr) {
|
||||
mysql_free_result(raw_ptr);
|
||||
}
|
||||
|
||||
return Nothing{};
|
||||
}
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
809
src/sqlgen/mysql/to_sql.cpp
Normal file
809
src/sqlgen/mysql/to_sql.cpp
Normal file
@@ -0,0 +1,809 @@
|
||||
#include "sqlgen/mysql/to_sql.hpp"
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include "sqlgen/dynamic/Join.hpp"
|
||||
#include "sqlgen/dynamic/Operation.hpp"
|
||||
#include "sqlgen/internal/collect/vector.hpp"
|
||||
#include "sqlgen/internal/strings/strings.hpp"
|
||||
|
||||
namespace sqlgen::mysql {
|
||||
|
||||
std::string aggregation_to_sql(
|
||||
const dynamic::Aggregation& _aggregation) noexcept;
|
||||
|
||||
std::string cast_type_to_sql(const dynamic::Type& _type) noexcept;
|
||||
|
||||
std::string column_or_value_to_sql(const dynamic::ColumnOrValue& _col) noexcept;
|
||||
|
||||
std::string condition_to_sql(const dynamic::Condition& _cond) noexcept;
|
||||
|
||||
template <class ConditionType>
|
||||
std::string condition_to_sql_impl(const ConditionType& _condition) noexcept;
|
||||
|
||||
std::string column_to_sql_definition(const dynamic::Column& _col) noexcept;
|
||||
|
||||
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept;
|
||||
|
||||
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
std::string date_plus_duration_to_sql(
|
||||
const dynamic::Operation::DatePlusDuration& _stmt,
|
||||
const size_t _ix = 0) noexcept;
|
||||
|
||||
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept;
|
||||
|
||||
std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept;
|
||||
|
||||
std::string escape_single_quote(const std::string& _str) noexcept;
|
||||
|
||||
std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept;
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept;
|
||||
|
||||
template <class InsertOrWrite>
|
||||
std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept;
|
||||
|
||||
std::string join_to_sql(const dynamic::Join& _stmt) noexcept;
|
||||
|
||||
std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept;
|
||||
|
||||
std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept;
|
||||
|
||||
std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept;
|
||||
|
||||
std::string type_to_sql(const dynamic::Type& _type) noexcept;
|
||||
|
||||
std::string update_to_sql(const dynamic::Update& _stmt) noexcept;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
inline std::string get_name(const dynamic::Column& _col) { return _col.name; }
|
||||
|
||||
inline std::string wrap_in_quotes(const std::string& _name) noexcept {
|
||||
return "`" + _name + "`";
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
std::string aggregation_to_sql(
|
||||
const dynamic::Aggregation& _aggregation) noexcept {
|
||||
return _aggregation.val.visit([](const auto& _agg) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_agg)>;
|
||||
std::stringstream stream;
|
||||
if constexpr (std::is_same_v<Type, dynamic::Aggregation::Avg>) {
|
||||
stream << "AVG(" << operation_to_sql(*_agg.val) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Count>) {
|
||||
const auto val =
|
||||
std::string(_agg.val && _agg.distinct ? "DISTINCT " : "") +
|
||||
(_agg.val ? column_or_value_to_sql(*_agg.val) : std::string("*"));
|
||||
stream << "COUNT(" << val << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Max>) {
|
||||
stream << "MAX(" << operation_to_sql(*_agg.val) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Min>) {
|
||||
stream << "MIN(" << operation_to_sql(*_agg.val) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation::Sum>) {
|
||||
stream << "SUM(" << operation_to_sql(*_agg.val) << ")";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<Type>, "Not all cases were covered.");
|
||||
}
|
||||
return stream.str();
|
||||
});
|
||||
}
|
||||
|
||||
std::string cast_type_to_sql(const dynamic::Type& _type) noexcept {
|
||||
return _type.visit([](const auto _t) -> std::string {
|
||||
using T = std::remove_cvref_t<decltype(_t)>;
|
||||
if constexpr (std::is_same_v<T, dynamic::types::Boolean>) {
|
||||
return "BOOLEAN";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Int8> ||
|
||||
std::is_same_v<T, dynamic::types::Int16> ||
|
||||
std::is_same_v<T, dynamic::types::Int32> ||
|
||||
std::is_same_v<T, dynamic::types::Int64>) {
|
||||
return "SIGNED";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::UInt8> ||
|
||||
std::is_same_v<T, dynamic::types::UInt16> ||
|
||||
std::is_same_v<T, dynamic::types::UInt32> ||
|
||||
std::is_same_v<T, dynamic::types::UInt64>) {
|
||||
return "UNSIGNED";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Float32> ||
|
||||
std::is_same_v<T, dynamic::types::Float64>) {
|
||||
return "DECIMAL";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Text> ||
|
||||
std::is_same_v<T, dynamic::types::VarChar>) {
|
||||
return "CHAR";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Date>) {
|
||||
return "DATE";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Timestamp> ||
|
||||
std::is_same_v<T, dynamic::types::TimestampWithTZ>) {
|
||||
return "DATETIME";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Unknown>) {
|
||||
return "CHAR";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string column_or_value_to_sql(
|
||||
const dynamic::ColumnOrValue& _col) noexcept {
|
||||
const auto handle_value = [](const auto& _v) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_v)>;
|
||||
if constexpr (std::is_same_v<Type, dynamic::String>) {
|
||||
return "'" + escape_single_quote(_v.val) + "'";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Duration>) {
|
||||
const auto unit =
|
||||
_v.unit == dynamic::TimeUnit::milliseconds
|
||||
? std::string("* 1000 microsecond")
|
||||
: internal::strings::rtrim(rfl::enum_to_string(_v.unit), "s");
|
||||
return "INTERVAL " + std::to_string(_v.val) + " " + unit;
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Timestamp>) {
|
||||
return "to_timestamp(" + std::to_string(_v.seconds_since_unix) + ")";
|
||||
|
||||
} else {
|
||||
return std::to_string(_v.val);
|
||||
}
|
||||
};
|
||||
|
||||
return _col.visit([&](const auto& _c) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_c)>;
|
||||
if constexpr (std::is_same_v<Type, dynamic::Column>) {
|
||||
if (_c.alias) {
|
||||
return *_c.alias + "." + wrap_in_quotes(_c.name);
|
||||
} else {
|
||||
return wrap_in_quotes(_c.name);
|
||||
}
|
||||
} else {
|
||||
return _c.val.visit(handle_value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string condition_to_sql(const dynamic::Condition& _cond) noexcept {
|
||||
return _cond.val.visit(
|
||||
[&](const auto& _c) { return condition_to_sql_impl(_c); });
|
||||
}
|
||||
|
||||
template <class ConditionType>
|
||||
std::string condition_to_sql_impl(const ConditionType& _condition) noexcept {
|
||||
using C = std::remove_cvref_t<ConditionType>;
|
||||
|
||||
std::stringstream stream;
|
||||
|
||||
if constexpr (std::is_same_v<C, dynamic::Condition::And>) {
|
||||
stream << "(" << condition_to_sql(*_condition.cond1) << ") AND ("
|
||||
<< condition_to_sql(*_condition.cond2) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Equal>) {
|
||||
stream << operation_to_sql(_condition.op1) << " = "
|
||||
<< operation_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::GreaterEqual>) {
|
||||
stream << operation_to_sql(_condition.op1)
|
||||
<< " >= " << operation_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::GreaterThan>) {
|
||||
stream << operation_to_sql(_condition.op1) << " > "
|
||||
<< operation_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::IsNull>) {
|
||||
stream << operation_to_sql(_condition.op) << " IS NULL";
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::IsNotNull>) {
|
||||
stream << operation_to_sql(_condition.op) << " IS NOT NULL";
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::LesserEqual>) {
|
||||
stream << operation_to_sql(_condition.op1)
|
||||
<< " <= " << operation_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::LesserThan>) {
|
||||
stream << operation_to_sql(_condition.op1) << " < "
|
||||
<< operation_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Like>) {
|
||||
stream << operation_to_sql(_condition.op) << " LIKE "
|
||||
<< column_or_value_to_sql(_condition.pattern);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Not>) {
|
||||
stream << "NOT (" << condition_to_sql(*_condition.cond) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::NotEqual>) {
|
||||
stream << operation_to_sql(_condition.op1)
|
||||
<< " != " << operation_to_sql(_condition.op2);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::NotLike>) {
|
||||
stream << operation_to_sql(_condition.op) << " NOT LIKE "
|
||||
<< column_or_value_to_sql(_condition.pattern);
|
||||
|
||||
} else if constexpr (std::is_same_v<C, dynamic::Condition::Or>) {
|
||||
stream << "(" << condition_to_sql(*_condition.cond1) << ") OR ("
|
||||
<< condition_to_sql(*_condition.cond2) << ")";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<C>, "Not all cases were covered.");
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string column_to_sql_definition(const dynamic::Column& _col) noexcept {
|
||||
return wrap_in_quotes(_col.name) + " " + type_to_sql(_col.type) +
|
||||
properties_to_sql(
|
||||
_col.type.visit([](const auto& _t) { return _t.properties; }));
|
||||
}
|
||||
|
||||
std::string create_index_to_sql(const dynamic::CreateIndex& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
std::stringstream stream;
|
||||
|
||||
if (_stmt.unique) {
|
||||
stream << "CREATE UNIQUE INDEX ";
|
||||
} else {
|
||||
stream << "CREATE INDEX ";
|
||||
}
|
||||
|
||||
if (_stmt.if_not_exists) {
|
||||
stream << "IF NOT EXISTS ";
|
||||
}
|
||||
|
||||
stream << wrap_in_quotes(_stmt.name) << " ";
|
||||
|
||||
stream << "ON ";
|
||||
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
stream << "(";
|
||||
stream << internal::strings::join(
|
||||
", ",
|
||||
internal::collect::vector(_stmt.columns | transform(wrap_in_quotes)));
|
||||
stream << ")";
|
||||
|
||||
if (_stmt.where) {
|
||||
stream << " WHERE " << condition_to_sql(*_stmt.where);
|
||||
}
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string create_table_to_sql(const dynamic::CreateTable& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto col_to_sql = [&](const auto& _col) {
|
||||
return column_to_sql_definition(_col);
|
||||
};
|
||||
|
||||
std::stringstream stream;
|
||||
stream << "CREATE TABLE ";
|
||||
|
||||
if (_stmt.if_not_exists) {
|
||||
stream << "IF NOT EXISTS ";
|
||||
}
|
||||
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name) << " ";
|
||||
|
||||
stream << "(";
|
||||
stream << internal::strings::join(
|
||||
", ", internal::collect::vector(_stmt.columns | transform(col_to_sql)));
|
||||
|
||||
const auto primary_keys = get_primary_keys(_stmt);
|
||||
|
||||
if (primary_keys.size() != 0) {
|
||||
stream << ", PRIMARY KEY (" << internal::strings::join(", ", primary_keys)
|
||||
<< ")";
|
||||
}
|
||||
|
||||
stream << ");";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string date_plus_duration_to_sql(
|
||||
const dynamic::Operation::DatePlusDuration& _stmt,
|
||||
const size_t _ix) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
std::stringstream stream;
|
||||
stream << internal::strings::join(
|
||||
"",
|
||||
internal::collect::vector(
|
||||
_stmt.durations | transform([](const auto&) -> std::string {
|
||||
return "date_add(";
|
||||
})))
|
||||
<< operation_to_sql(*_stmt.date) << ", "
|
||||
<< internal::strings::join(
|
||||
"), ", internal::collect::vector(
|
||||
_stmt.durations | transform([](const auto& _d) {
|
||||
return column_or_value_to_sql(dynamic::Value{_d});
|
||||
})))
|
||||
<< ")";
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string delete_from_to_sql(const dynamic::DeleteFrom& _stmt) noexcept {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "DELETE FROM ";
|
||||
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
if (_stmt.where) {
|
||||
stream << " WHERE " << condition_to_sql(*_stmt.where);
|
||||
}
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string drop_to_sql(const dynamic::Drop& _stmt) noexcept {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "DROP TABLE ";
|
||||
|
||||
if (_stmt.if_exists) {
|
||||
stream << "IF EXISTS ";
|
||||
}
|
||||
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string escape_single_quote(const std::string& _str) noexcept {
|
||||
return internal::strings::replace_all(_str, "'", "''");
|
||||
}
|
||||
|
||||
std::string field_to_str(const dynamic::SelectFrom::Field& _field) noexcept {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << operation_to_sql(_field.val);
|
||||
|
||||
if (_field.as) {
|
||||
stream << " AS " << wrap_in_quotes(*_field.as);
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> get_primary_keys(
|
||||
const dynamic::CreateTable& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto is_primary_key = [](const auto& _col) -> bool {
|
||||
return _col.type.visit(
|
||||
[](const auto& _t) -> bool { return _t.properties.primary; });
|
||||
};
|
||||
|
||||
return internal::collect::vector(_stmt.columns | filter(is_primary_key) |
|
||||
transform(get_name) |
|
||||
transform(wrap_in_quotes));
|
||||
}
|
||||
|
||||
template <class InsertOrWrite>
|
||||
std::string insert_or_write_to_sql(const InsertOrWrite& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "INSERT INTO ";
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
stream << " (";
|
||||
stream << internal::strings::join(
|
||||
", ",
|
||||
internal::collect::vector(_stmt.columns | transform(wrap_in_quotes)));
|
||||
stream << ")";
|
||||
|
||||
stream << " VALUES (";
|
||||
stream << internal::strings::join(
|
||||
", ", internal::collect::vector(
|
||||
_stmt.columns |
|
||||
transform([](const auto&) -> std::string { return "?"; })));
|
||||
stream << ");";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string join_to_sql(const dynamic::Join& _stmt) noexcept {
|
||||
std::stringstream stream;
|
||||
|
||||
stream << internal::strings::to_upper(internal::strings::replace_all(
|
||||
rfl::enum_to_string(_stmt.how), "_", " "))
|
||||
<< " ";
|
||||
|
||||
stream << _stmt.table_or_query.visit(
|
||||
[](const auto& _table_or_query) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_table_or_query)>;
|
||||
if constexpr (std::is_same_v<Type, dynamic::Table>) {
|
||||
return wrap_in_quotes(_table_or_query.name);
|
||||
} else {
|
||||
return "(" + select_from_to_sql(*_table_or_query) + ")";
|
||||
}
|
||||
})
|
||||
<< " ";
|
||||
|
||||
stream << _stmt.alias << " ";
|
||||
|
||||
if (_stmt.on) {
|
||||
stream << "ON " << condition_to_sql(*_stmt.on);
|
||||
} else {
|
||||
stream << "ON 1 = 1";
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string operation_to_sql(const dynamic::Operation& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
return _stmt.val.visit([](const auto& _s) -> std::string {
|
||||
using Type = std::remove_cvref_t<decltype(_s)>;
|
||||
|
||||
std::stringstream stream;
|
||||
|
||||
if constexpr (std::is_same_v<Type, dynamic::Operation::Abs>) {
|
||||
stream << "abs(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Aggregation>) {
|
||||
stream << aggregation_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Cast>) {
|
||||
stream << "cast(" << operation_to_sql(*_s.op1) << " as "
|
||||
<< cast_type_to_sql(_s.target_type) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Coalesce>) {
|
||||
stream << "coalesce("
|
||||
<< internal::strings::join(
|
||||
", ", internal::collect::vector(
|
||||
_s.ops | transform([](const auto& _op) {
|
||||
return operation_to_sql(*_op);
|
||||
})))
|
||||
<< ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Ceil>) {
|
||||
stream << "ceil(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Column>) {
|
||||
stream << column_or_value_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Concat>) {
|
||||
stream << "concat("
|
||||
<< internal::strings::join(
|
||||
", ", internal::collect::vector(
|
||||
_s.ops | transform([](const auto& _op) {
|
||||
return operation_to_sql(*_op);
|
||||
})))
|
||||
<< ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Cos>) {
|
||||
stream << "cos(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type,
|
||||
dynamic::Operation::DatePlusDuration>) {
|
||||
return date_plus_duration_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Day>) {
|
||||
stream << "extract(DAY from " << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type,
|
||||
dynamic::Operation::DaysBetween>) {
|
||||
stream << "datediff(" << operation_to_sql(*_s.op2) << ", "
|
||||
<< operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Divides>) {
|
||||
stream << "(" << operation_to_sql(*_s.op1) << ") / ("
|
||||
<< operation_to_sql(*_s.op2) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Exp>) {
|
||||
stream << "exp(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Floor>) {
|
||||
stream << "floor(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Hour>) {
|
||||
stream << "extract(HOUR from " << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Length>) {
|
||||
stream << "length(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Ln>) {
|
||||
stream << "ln(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Log2>) {
|
||||
stream << "log2( " << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Lower>) {
|
||||
stream << "lower(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::LTrim>) {
|
||||
stream << "trim(leading " << operation_to_sql(*_s.op2) << " FROM "
|
||||
<< operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Minus>) {
|
||||
stream << "(" << operation_to_sql(*_s.op1) << ") - ("
|
||||
<< operation_to_sql(*_s.op2) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Minute>) {
|
||||
stream << "extract(MINUTE from " << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Mod>) {
|
||||
stream << "mod(" << operation_to_sql(*_s.op1) << ", "
|
||||
<< operation_to_sql(*_s.op2) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Month>) {
|
||||
stream << "extract(MONTH from " << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Multiplies>) {
|
||||
stream << "(" << operation_to_sql(*_s.op1) << ") * ("
|
||||
<< operation_to_sql(*_s.op2) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Plus>) {
|
||||
stream << "(" << operation_to_sql(*_s.op1) << ") + ("
|
||||
<< operation_to_sql(*_s.op2) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Replace>) {
|
||||
stream << "replace(" << operation_to_sql(*_s.op1) << ", "
|
||||
<< operation_to_sql(*_s.op2) << ", " << operation_to_sql(*_s.op3)
|
||||
<< ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Round>) {
|
||||
stream << "round(" << operation_to_sql(*_s.op1) << ", "
|
||||
<< operation_to_sql(*_s.op2) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::RTrim>) {
|
||||
stream << "trim(trailing " << operation_to_sql(*_s.op2) << " FROM "
|
||||
<< operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Second>) {
|
||||
stream << "extract(SECOND from " << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Sin>) {
|
||||
stream << "sin(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Sqrt>) {
|
||||
stream << "sqrt(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Tan>) {
|
||||
stream << "tan(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Trim>) {
|
||||
stream << "trim(both " << operation_to_sql(*_s.op2) << " FROM "
|
||||
<< operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Unixepoch>) {
|
||||
stream << "unix_timestamp(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Upper>) {
|
||||
stream << "upper(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Value>) {
|
||||
stream << column_or_value_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Weekday>) {
|
||||
stream << "dayofweek(" << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<Type, dynamic::Operation::Year>) {
|
||||
stream << "extract(YEAR from " << operation_to_sql(*_s.op1) << ")";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<Type>, "Unsupported type.");
|
||||
}
|
||||
return stream.str();
|
||||
});
|
||||
}
|
||||
|
||||
std::string properties_to_sql(const dynamic::types::Properties& _p) noexcept {
|
||||
if (_p.auto_incr) {
|
||||
return " AUTO_INCREMENT";
|
||||
} else if (!_p.nullable) {
|
||||
return " NOT NULL";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string select_from_to_sql(const dynamic::SelectFrom& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto order_by_to_str = [](const auto& _w) -> std::string {
|
||||
return column_or_value_to_sql(_w.column) + (_w.desc ? " DESC" : "");
|
||||
};
|
||||
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "SELECT ";
|
||||
stream << internal::strings::join(
|
||||
", ", internal::collect::vector(_stmt.fields | transform(field_to_str)));
|
||||
|
||||
stream << " FROM ";
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
if (_stmt.alias) {
|
||||
stream << " " << *_stmt.alias;
|
||||
}
|
||||
|
||||
if (_stmt.joins) {
|
||||
stream << " "
|
||||
<< internal::strings::join(
|
||||
" ", internal::collect::vector(*_stmt.joins |
|
||||
transform(join_to_sql)));
|
||||
}
|
||||
|
||||
if (_stmt.where) {
|
||||
stream << " WHERE " << condition_to_sql(*_stmt.where);
|
||||
}
|
||||
|
||||
if (_stmt.group_by) {
|
||||
stream << " GROUP BY "
|
||||
<< internal::strings::join(
|
||||
", ",
|
||||
internal::collect::vector(_stmt.group_by->columns |
|
||||
transform(column_or_value_to_sql)));
|
||||
}
|
||||
|
||||
if (_stmt.order_by) {
|
||||
stream << " ORDER BY "
|
||||
<< internal::strings::join(
|
||||
", ", internal::collect::vector(_stmt.order_by->columns |
|
||||
transform(order_by_to_str)));
|
||||
}
|
||||
|
||||
if (_stmt.limit) {
|
||||
stream << " LIMIT " << _stmt.limit->val;
|
||||
}
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string to_sql_impl(const dynamic::Statement& _stmt) noexcept {
|
||||
return _stmt.visit([&](const auto& _s) -> std::string {
|
||||
using S = std::remove_cvref_t<decltype(_s)>;
|
||||
|
||||
if constexpr (std::is_same_v<S, dynamic::CreateIndex>) {
|
||||
return create_index_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::CreateTable>) {
|
||||
return create_table_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::DeleteFrom>) {
|
||||
return delete_from_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::Drop>) {
|
||||
return drop_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::Insert> ||
|
||||
std::is_same_v<S, dynamic::Write>) {
|
||||
return insert_or_write_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::SelectFrom>) {
|
||||
return select_from_to_sql(_s);
|
||||
|
||||
} else if constexpr (std::is_same_v<S, dynamic::Update>) {
|
||||
return update_to_sql(_s);
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<S>, "Unsupported type.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string type_to_sql(const dynamic::Type& _type) noexcept {
|
||||
return _type.visit([](const auto _t) -> std::string {
|
||||
using T = std::remove_cvref_t<decltype(_t)>;
|
||||
if constexpr (std::is_same_v<T, dynamic::types::Boolean>) {
|
||||
return "BOOLEAN";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Int8>) {
|
||||
return "TINYINT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::UInt8> ||
|
||||
std::is_same_v<T, dynamic::types::Int16>) {
|
||||
return "SMALLINT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::UInt16> ||
|
||||
std::is_same_v<T, dynamic::types::Int32>) {
|
||||
return "INT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::UInt32> ||
|
||||
std::is_same_v<T, dynamic::types::Int64> ||
|
||||
std::is_same_v<T, dynamic::types::UInt64>) {
|
||||
return "BIGINT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Float32> ||
|
||||
std::is_same_v<T, dynamic::types::Float64>) {
|
||||
return "DECIMAL";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Text>) {
|
||||
return "TEXT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::VarChar>) {
|
||||
return "VARCHAR(" + std::to_string(_t.length) + ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Date>) {
|
||||
return "DATE";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Timestamp> ||
|
||||
std::is_same_v<T, dynamic::types::TimestampWithTZ>) {
|
||||
return "DATETIME";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Unknown>) {
|
||||
return "TEXT";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::string update_to_sql(const dynamic::Update& _stmt) noexcept {
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto to_str = [](const auto& _set) -> std::string {
|
||||
return wrap_in_quotes(_set.col.name) + " = " +
|
||||
column_or_value_to_sql(_set.to);
|
||||
};
|
||||
|
||||
std::stringstream stream;
|
||||
|
||||
stream << "UPDATE ";
|
||||
|
||||
if (_stmt.table.schema) {
|
||||
stream << wrap_in_quotes(*_stmt.table.schema) << ".";
|
||||
}
|
||||
stream << wrap_in_quotes(_stmt.table.name);
|
||||
|
||||
stream << " SET ";
|
||||
|
||||
stream << internal::strings::join(
|
||||
", ", internal::collect::vector(_stmt.sets | transform(to_str)));
|
||||
|
||||
if (_stmt.where) {
|
||||
stream << " WHERE " << condition_to_sql(*_stmt.where);
|
||||
}
|
||||
|
||||
stream << ";";
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace sqlgen::mysql
|
||||
@@ -58,19 +58,15 @@ Result<std::vector<std::vector<std::optional<std::string>>>> Iterator::next(
|
||||
return vec;
|
||||
};
|
||||
|
||||
const auto shutdown_if_necessary =
|
||||
[this](std::vector<std::vector<std::optional<std::string>>>&& _vec)
|
||||
-> std::vector<std::vector<std::optional<std::string>>> {
|
||||
if (_vec.size() == 0) {
|
||||
shutdown();
|
||||
}
|
||||
return std::move(_vec);
|
||||
};
|
||||
|
||||
return exec(conn_, "FETCH FORWARD " + std::to_string(_batch_size) + " FROM " +
|
||||
cursor_name_ + ";")
|
||||
.transform(to_vector)
|
||||
.transform(shutdown_if_necessary);
|
||||
.transform([this](auto&& _vec) {
|
||||
if (_vec.size() == 0) {
|
||||
shutdown();
|
||||
}
|
||||
return std::move(_vec);
|
||||
});
|
||||
}
|
||||
|
||||
Iterator& Iterator::operator=(Iterator&& _other) noexcept {
|
||||
|
||||
@@ -671,30 +671,43 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept {
|
||||
using T = std::remove_cvref_t<decltype(_t)>;
|
||||
if constexpr (std::is_same_v<T, dynamic::types::Boolean>) {
|
||||
return "BOOLEAN";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Int8> ||
|
||||
std::is_same_v<T, dynamic::types::Int16> ||
|
||||
std::is_same_v<T, dynamic::types::UInt8> ||
|
||||
std::is_same_v<T, dynamic::types::UInt16>) {
|
||||
return "SMALLINT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Int32> ||
|
||||
std::is_same_v<T, dynamic::types::UInt32>) {
|
||||
return "INTEGER";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Int64> ||
|
||||
std::is_same_v<T, dynamic::types::UInt64>) {
|
||||
return "BIGINT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Float32> ||
|
||||
std::is_same_v<T, dynamic::types::Float64>) {
|
||||
return "NUMERIC";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Text>) {
|
||||
return "TEXT";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::VarChar>) {
|
||||
return "VARCHAR(" + std::to_string(_t.length) + ")";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Date>) {
|
||||
return "DATE";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Timestamp>) {
|
||||
return "TIMESTAMP";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::TimestampWithTZ>) {
|
||||
return "TIMESTAMP WITH TIME ZONE";
|
||||
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Unknown>) {
|
||||
return "TEXT";
|
||||
|
||||
} else {
|
||||
static_assert(rfl::always_false_v<T>, "Not all cases were covered.");
|
||||
}
|
||||
|
||||
@@ -692,6 +692,7 @@ std::string type_to_sql(const dynamic::Type& _type) noexcept {
|
||||
} else if constexpr (std::is_same_v<T, dynamic::types::Unknown> ||
|
||||
std::is_same_v<T, dynamic::types::Text> ||
|
||||
std::is_same_v<T, dynamic::types::VarChar> ||
|
||||
std::is_same_v<T, dynamic::types::Date> ||
|
||||
std::is_same_v<T, dynamic::types::Timestamp> ||
|
||||
std::is_same_v<T, dynamic::types::TimestampWithTZ>) {
|
||||
return "TEXT";
|
||||
|
||||
4
src/sqlgen_mysql.cpp
Normal file
4
src/sqlgen_mysql.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "sqlgen/mysql/Connection.cpp"
|
||||
#include "sqlgen/mysql/Iterator.cpp"
|
||||
#include "sqlgen/mysql/exec.cpp"
|
||||
#include "sqlgen/mysql/to_sql.cpp"
|
||||
@@ -8,6 +8,10 @@ if (SQLGEN_BUILD_DRY_TESTS_ONLY)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSQLGEN_BUILD_DRY_TESTS_ONLY")
|
||||
endif()
|
||||
|
||||
if(SQLGEN_MYSQL)
|
||||
add_subdirectory(mysql)
|
||||
endif()
|
||||
|
||||
if(SQLGEN_POSTGRES)
|
||||
add_subdirectory(postgres)
|
||||
endif()
|
||||
|
||||
19
tests/mysql/CMakeLists.txt
Normal file
19
tests/mysql/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
project(sqlgen-mysql-tests)
|
||||
|
||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "*.cpp")
|
||||
|
||||
add_executable(
|
||||
sqlgen-mysql-tests
|
||||
${SOURCES}
|
||||
)
|
||||
target_precompile_headers(sqlgen-mysql-tests PRIVATE [["sqlgen.hpp"]] <iostream> <string> <functional> <gtest/gtest.h>)
|
||||
|
||||
|
||||
target_link_libraries(
|
||||
sqlgen-mysql-tests
|
||||
PRIVATE
|
||||
"${SQLGEN_GTEST_LIB}"
|
||||
)
|
||||
|
||||
find_package(GTest)
|
||||
gtest_discover_tests(sqlgen-mysql-tests)
|
||||
73
tests/mysql/test_aggregations.cpp
Normal file
73
tests/mysql/test_aggregations.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_aggregations {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
double age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_aggregations) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct Children {
|
||||
int num_children;
|
||||
int num_last_names;
|
||||
double avg_age;
|
||||
double max_age;
|
||||
double min_age;
|
||||
double sum_age;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Person>(
|
||||
avg("age"_c).as<"avg_age">(), count().as<"num_children">(),
|
||||
max("age"_c).as<"max_age">(), min("age"_c).as<"min_age">(),
|
||||
sum("age"_c).as<"sum_age">(),
|
||||
count_distinct("last_name"_c).as<"num_last_names">()) |
|
||||
where("age"_c < 18) | to<Children>;
|
||||
|
||||
const auto children = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_children)
|
||||
.value();
|
||||
|
||||
using namespace std::ranges::views;
|
||||
|
||||
EXPECT_EQ(children.num_children, 3);
|
||||
EXPECT_EQ(children.num_last_names, 1);
|
||||
EXPECT_EQ(children.avg_age, 6.0);
|
||||
EXPECT_EQ(children.max_age, 10.0);
|
||||
EXPECT_EQ(children.min_age, 0.0);
|
||||
EXPECT_EQ(children.sum_age, 18.0);
|
||||
}
|
||||
|
||||
} // namespace test_aggregations
|
||||
|
||||
#endif
|
||||
72
tests/mysql/test_aggregations_with_nullable.cpp
Normal file
72
tests/mysql/test_aggregations_with_nullable.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_aggregations_with_nullable {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<double> age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_aggregations_with_nullable) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{.id = 3, .first_name = "Maggie", .last_name = "Simpson"}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct Children {
|
||||
int num_children;
|
||||
int num_last_names;
|
||||
std::optional<double> avg_age;
|
||||
std::optional<double> max_age;
|
||||
std::optional<double> min_age;
|
||||
std::optional<double> sum_age;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Person>(
|
||||
avg("age"_c).as<"avg_age">(), count().as<"num_children">(),
|
||||
max("age"_c).as<"max_age">(), min("age"_c).as<"min_age">(),
|
||||
sum("age"_c).as<"sum_age">(),
|
||||
count_distinct("last_name"_c).as<"num_last_names">()) |
|
||||
where("age"_c < 18 or "age"_c.is_null()) | to<Children>;
|
||||
|
||||
const auto children = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_children)
|
||||
.value();
|
||||
|
||||
using namespace std::ranges::views;
|
||||
|
||||
EXPECT_EQ(children.num_children, 3);
|
||||
EXPECT_EQ(children.num_last_names, 1);
|
||||
EXPECT_EQ(children.avg_age, 9.0);
|
||||
EXPECT_EQ(children.max_age, 10.0);
|
||||
EXPECT_EQ(children.min_age, 8.0);
|
||||
EXPECT_EQ(children.sum_age, 18.0);
|
||||
}
|
||||
|
||||
} // namespace test_aggregations_with_nullable
|
||||
|
||||
#endif
|
||||
55
tests/mysql/test_auto_incr_primary_key.cpp
Normal file
55
tests/mysql/test_auto_incr_primary_key.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_auto_incr_primary_key {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_auto_incr_primary_key) {
|
||||
auto people1 = std::vector<Person>(
|
||||
{Person{.first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{.first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people2 = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>> |
|
||||
order_by("age"_c.desc()))
|
||||
.value();
|
||||
|
||||
people1.at(0).id = 1;
|
||||
people1.at(1).id = 2;
|
||||
people1.at(2).id = 3;
|
||||
people1.at(3).id = 4;
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_auto_incr_primary_key
|
||||
|
||||
#endif
|
||||
44
tests/mysql/test_create_index.cpp
Normal file
44
tests/mysql/test_create_index.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_create_index {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
sqlgen::Varchar<100> first_name;
|
||||
sqlgen::Varchar<100> last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_create_index) {
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people = sqlgen::mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(create_table<Person> | if_not_exists)
|
||||
.and_then(create_index<"test_table_ix", Person>(
|
||||
"first_name"_c, "last_name"_c))
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
|
||||
const std::string expected = R"([])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people), expected);
|
||||
}
|
||||
|
||||
} // namespace test_create_index
|
||||
|
||||
#endif
|
||||
27
tests/mysql/test_create_index_dry.cpp
Normal file
27
tests/mysql/test_create_index_dry.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_create_index_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_create_index_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query =
|
||||
create_index<"test_table_ix", TestTable>("field1"_c, "field2"_c);
|
||||
|
||||
const auto expected =
|
||||
R"(CREATE INDEX `test_table_ix` ON `TestTable`(`field1`, `field2`);)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_create_index_dry
|
||||
42
tests/mysql/test_create_table.cpp
Normal file
42
tests/mysql/test_create_table.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_create_table {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_create_table) {
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people = sqlgen::mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(create_table<Person> | if_not_exists)
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
|
||||
const std::string expected = R"([])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people), expected);
|
||||
}
|
||||
|
||||
} // namespace test_create_table
|
||||
|
||||
#endif
|
||||
26
tests/mysql/test_create_table_dry.cpp
Normal file
26
tests/mysql/test_create_table_dry.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_create_table_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_create_table_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query = create_table<TestTable> | if_not_exists;
|
||||
|
||||
const auto expected =
|
||||
R"(CREATE TABLE IF NOT EXISTS `TestTable` (`field1` TEXT NOT NULL, `field2` INT NOT NULL, `id` BIGINT NOT NULL, `nullable` TEXT, PRIMARY KEY (`id`));)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_create_table_dry
|
||||
60
tests/mysql/test_delete_from.cpp
Normal file
60
tests/mysql/test_delete_from.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_delete_from {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_delete_from) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{
|
||||
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto delete_hugo =
|
||||
delete_from<Person> | where("first_name"_c == "Hugo");
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(delete_hugo)
|
||||
.and_then(sqlgen::read<std::vector<Person>> | order_by("id"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id":0,"first_name":"Homer","last_name":"Simpson","age":45},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_delete_from
|
||||
|
||||
#endif
|
||||
25
tests/mysql/test_delete_from_dry.cpp
Normal file
25
tests/mysql/test_delete_from_dry.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_delete_from_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_delete_from_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query = delete_from<TestTable> | where("field2"_c > 0);
|
||||
|
||||
const auto expected = R"(DELETE FROM `TestTable` WHERE `field2` > 0;)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_delete_from_dry
|
||||
25
tests/mysql/test_drop_dry.cpp
Normal file
25
tests/mysql/test_drop_dry.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_drop_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_drop_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query = drop<TestTable> | if_exists;
|
||||
|
||||
const auto expected = R"(DROP TABLE IF EXISTS `TestTable`;)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_drop_dry
|
||||
77
tests/mysql/test_group_by.cpp
Normal file
77
tests/mysql/test_group_by.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_group_by {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
double age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_group_by) {
|
||||
static_assert(std::ranges::input_range<sqlgen::Range<Person>>,
|
||||
"Must be an input range.");
|
||||
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct Children {
|
||||
std::string last_name;
|
||||
int num_children;
|
||||
int num_last_names;
|
||||
double avg_age;
|
||||
double max_age;
|
||||
double min_age;
|
||||
double sum_age;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Person>(
|
||||
"last_name"_c, avg("age"_c).as<"avg_age">(),
|
||||
count().as<"num_children">(), max("age"_c).as<"max_age">(),
|
||||
min("age"_c).as<"min_age">(), sum("age"_c).as<"sum_age">(),
|
||||
count_distinct("last_name"_c).as<"num_last_names">()) |
|
||||
where("age"_c < 18) | group_by("last_name"_c) | to<std::vector<Children>>;
|
||||
|
||||
const auto children = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_children)
|
||||
.value();
|
||||
|
||||
EXPECT_EQ(children.size(), 1);
|
||||
EXPECT_EQ(children.at(0).last_name, "Simpson");
|
||||
EXPECT_EQ(children.at(0).num_children, 3);
|
||||
EXPECT_EQ(children.at(0).num_last_names, 1);
|
||||
EXPECT_EQ(children.at(0).avg_age, 6.0);
|
||||
EXPECT_EQ(children.at(0).max_age, 10.0);
|
||||
EXPECT_EQ(children.at(0).min_age, 0.0);
|
||||
EXPECT_EQ(children.at(0).sum_age, 18.0);
|
||||
}
|
||||
|
||||
} // namespace test_group_by
|
||||
|
||||
#endif
|
||||
73
tests/mysql/test_group_by_with_operations.cpp
Normal file
73
tests/mysql/test_group_by_with_operations.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_group_by_with_operations {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_group_by_with_operations) {
|
||||
static_assert(std::ranges::input_range<sqlgen::Range<Person>>,
|
||||
"Must be an input range.");
|
||||
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct Children {
|
||||
std::string last_name;
|
||||
std::string last_name_trimmed;
|
||||
double avg_age;
|
||||
double max_age_plus_one;
|
||||
double min_age_plus_one;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Person>(
|
||||
"last_name"_c, trim("last_name"_c).as<"last_name_trimmed">(),
|
||||
max(cast<double>("age"_c) + 1.0).as<"max_age_plus_one">(),
|
||||
(min(cast<double>("age"_c)) + 1.0).as<"min_age_plus_one">(),
|
||||
round(avg(cast<double>("age"_c))).as<"avg_age">()) |
|
||||
where("age"_c < 18) | group_by("last_name"_c) | to<std::vector<Children>>;
|
||||
|
||||
const auto children = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_children)
|
||||
.value();
|
||||
|
||||
EXPECT_EQ(children.size(), 1);
|
||||
EXPECT_EQ(children.at(0).last_name, "Simpson");
|
||||
EXPECT_EQ(children.at(0).last_name_trimmed, "Simpson");
|
||||
EXPECT_EQ(children.at(0).avg_age, 6.0);
|
||||
EXPECT_EQ(children.at(0).max_age_plus_one, 11.0);
|
||||
EXPECT_EQ(children.at(0).min_age_plus_one, 1.0);
|
||||
}
|
||||
|
||||
} // namespace test_group_by_with_operations
|
||||
|
||||
#endif
|
||||
54
tests/mysql/test_insert_and_read.cpp
Normal file
54
tests/mysql/test_insert_and_read.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_insert_and_read {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_insert_and_read) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people2 = sqlgen::mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(begin_transaction)
|
||||
.and_then(create_table<Person> | if_not_exists)
|
||||
.and_then(insert(std::ref(people1)))
|
||||
.and_then(commit)
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_insert_and_read
|
||||
|
||||
#endif
|
||||
67
tests/mysql/test_insert_and_read_two_tables.cpp
Normal file
67
tests/mysql/test_insert_and_read_two_tables.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_insert_and_read_two_tables {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
struct Children {
|
||||
uint32_t id_parent;
|
||||
sqlgen::PrimaryKey<uint32_t> id_child;
|
||||
};
|
||||
|
||||
TEST(mysql, test_insert_and_read_two_tables) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto children =
|
||||
std::vector<Children>({Children{.id_parent = 0, .id_child = 1},
|
||||
Children{.id_parent = 0, .id_child = 2},
|
||||
Children{.id_parent = 0, .id_child = 3}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people2 = sqlgen::mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(drop<Children> | if_exists)
|
||||
.and_then(begin_transaction)
|
||||
.and_then(create_table<Person> | if_not_exists)
|
||||
.and_then(create_table<Children> | if_not_exists)
|
||||
.and_then(insert(std::ref(people1)))
|
||||
.and_then(insert(std::ref(children)))
|
||||
.and_then(commit)
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_insert_and_read_two_tables
|
||||
|
||||
#endif
|
||||
23
tests/mysql/test_insert_dry.cpp
Normal file
23
tests/mysql/test_insert_dry.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_insert_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_insert_dry) {
|
||||
const auto query = sqlgen::Insert<TestTable>{};
|
||||
|
||||
const auto expected =
|
||||
R"(INSERT INTO `TestTable` (`field1`, `field2`, `id`, `nullable`) VALUES (?, ?, ?, ?);)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_insert_dry
|
||||
63
tests/mysql/test_is_null.cpp
Normal file
63
tests/mysql/test_is_null.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_is_null {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_is_null) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{.id = 0, .first_name = "Homer", .last_name = "Simpson"},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{.id = 4, .first_name = "Hugo", .last_name = "Simpson"}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn = mysql::connect(credentials);
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_null()) | order_by("first_name"_c.desc()))
|
||||
.value();
|
||||
|
||||
const auto people3 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_not_null()) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected1 =
|
||||
R"([{"id":4,"first_name":"Hugo","last_name":"Simpson"},{"id":0,"first_name":"Homer","last_name":"Simpson"}])";
|
||||
|
||||
const std::string expected2 =
|
||||
R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected1);
|
||||
EXPECT_EQ(rfl::json::write(people3), expected2);
|
||||
}
|
||||
|
||||
} // namespace test_is_null
|
||||
|
||||
#endif
|
||||
32
tests/mysql/test_is_null_dry.cpp
Normal file
32
tests/mysql/test_is_null_dry.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_is_null_dry {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_is_null_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto sql =
|
||||
mysql::to_sql(sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c.is_null()) | order_by("first_name"_c.desc()));
|
||||
|
||||
const std::string expected =
|
||||
R"(SELECT `id`, `first_name`, `last_name`, `age` FROM `Person` WHERE `age` IS NULL ORDER BY `first_name` DESC)";
|
||||
|
||||
EXPECT_EQ(sql, expected);
|
||||
}
|
||||
|
||||
} // namespace test_is_null_dry
|
||||
65
tests/mysql/test_join.cpp
Normal file
65
tests/mysql/test_join.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_join {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
double age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_join) {
|
||||
static_assert(std::ranges::input_range<sqlgen::Range<Person>>,
|
||||
"Must be an input range.");
|
||||
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto get_people =
|
||||
select_from<Person, "t1">(
|
||||
"id"_t1 | as<"id">, "first_name"_t1 | as<"first_name">,
|
||||
"last_name"_t2 | as<"last_name">, "age"_t2 | as<"age">) |
|
||||
left_join<Person, "t2">("id"_t1 == "id"_t2) | order_by("id"_t1) |
|
||||
to<std::vector<Person>>;
|
||||
|
||||
const auto people = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_people)
|
||||
.value();
|
||||
|
||||
const std::string expected_query =
|
||||
R"(SELECT t1.`id` AS `id`, t1.`first_name` AS `first_name`, t2.`last_name` AS `last_name`, t2.`age` AS `age` FROM `Person` t1 LEFT JOIN `Person` t2 ON t1.`id` = t2.`id` ORDER BY t1.`id`)";
|
||||
const std::string expected =
|
||||
R"([{"id":0,"first_name":"Homer","last_name":"Simpson","age":45.0},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10.0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8.0},{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0.0}])";
|
||||
|
||||
EXPECT_EQ(mysql::to_sql(get_people), expected_query);
|
||||
EXPECT_EQ(rfl::json::write(people), expected);
|
||||
}
|
||||
|
||||
} // namespace test_join
|
||||
|
||||
#endif
|
||||
95
tests/mysql/test_joins_nested.cpp
Normal file
95
tests/mysql/test_joins_nested.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_joins_nested {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
double age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::PrimaryKey<uint32_t> parent_id;
|
||||
sqlgen::PrimaryKey<uint32_t> child_id;
|
||||
};
|
||||
|
||||
TEST(mysql, test_joins_nested) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{
|
||||
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
|
||||
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 3, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 4, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto relationships =
|
||||
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
|
||||
Relationship{.parent_id = 0, .child_id = 3},
|
||||
Relationship{.parent_id = 0, .child_id = 4},
|
||||
Relationship{.parent_id = 1, .child_id = 2},
|
||||
Relationship{.parent_id = 1, .child_id = 3},
|
||||
Relationship{.parent_id = 1, .child_id = 4}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct ParentAndChild {
|
||||
std::string last_name;
|
||||
std::string first_name_parent;
|
||||
std::string first_name_child;
|
||||
double parent_age_at_birth;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Relationship, "t1">("parent_id"_t1 | as<"id">,
|
||||
"first_name"_t2 | as<"first_name">,
|
||||
"age"_t2 | as<"age">) |
|
||||
left_join<Person, "t2">("id"_t2 == "child_id"_t1);
|
||||
|
||||
const auto get_people =
|
||||
select_from<Person, "t1">(
|
||||
"last_name"_t1 | as<"last_name">,
|
||||
"first_name"_t1 | as<"first_name_parent">,
|
||||
"first_name"_t2 | as<"first_name_child">,
|
||||
("age"_t1 - "age"_t2) | as<"parent_age_at_birth">) |
|
||||
inner_join<"t2">(get_children, "id"_t1 == "id"_t2) |
|
||||
order_by("id"_t1, "id"_t2) | to<std::vector<ParentAndChild>>;
|
||||
|
||||
const auto people = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(write(std::ref(relationships)))
|
||||
.and_then(get_people)
|
||||
.value();
|
||||
|
||||
const std::string expected_query =
|
||||
R"(SELECT t1.`last_name` AS `last_name`, t1.`first_name` AS `first_name_parent`, t2.`first_name` AS `first_name_child`, (t1.`age`) - (t2.`age`) AS `parent_age_at_birth` FROM `Person` t1 INNER JOIN (SELECT t1.`parent_id` AS `id`, t2.`first_name` AS `first_name`, t2.`age` AS `age` FROM `Relationship` t1 LEFT JOIN `Person` t2 ON t2.`id` = t1.`child_id`) t2 ON t1.`id` = t2.`id` ORDER BY t1.`id`, t2.`id`)";
|
||||
|
||||
const std::string expected =
|
||||
R"([{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Bart","parent_age_at_birth":35.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Lisa","parent_age_at_birth":37.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Maggie","parent_age_at_birth":45.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Bart","parent_age_at_birth":30.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Lisa","parent_age_at_birth":32.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Maggie","parent_age_at_birth":40.0}])";
|
||||
|
||||
EXPECT_EQ(mysql::to_sql(get_people), expected_query);
|
||||
EXPECT_EQ(rfl::json::write(people), expected);
|
||||
}
|
||||
|
||||
} // namespace test_joins_nested
|
||||
|
||||
#endif
|
||||
94
tests/mysql/test_joins_nested_grouped.cpp
Normal file
94
tests/mysql/test_joins_nested_grouped.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_joins_nested_grouped {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
double age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::PrimaryKey<uint32_t> parent_id;
|
||||
sqlgen::PrimaryKey<uint32_t> child_id;
|
||||
};
|
||||
|
||||
TEST(mysql, test_joins_nested_grouped) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{
|
||||
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
|
||||
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 3, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 4, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto relationships =
|
||||
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
|
||||
Relationship{.parent_id = 0, .child_id = 3},
|
||||
Relationship{.parent_id = 0, .child_id = 4},
|
||||
Relationship{.parent_id = 1, .child_id = 2},
|
||||
Relationship{.parent_id = 1, .child_id = 3},
|
||||
Relationship{.parent_id = 1, .child_id = 4}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct ParentAndChild {
|
||||
std::string last_name;
|
||||
std::string first_name_child;
|
||||
double avg_parent_age_at_birth;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Relationship, "t1">("parent_id"_t1 | as<"id">,
|
||||
"first_name"_t2 | as<"first_name">,
|
||||
"age"_t2 | as<"age">) |
|
||||
left_join<Person, "t2">("id"_t2 == "child_id"_t1);
|
||||
|
||||
const auto get_people =
|
||||
select_from<Person, "t1">(
|
||||
"last_name"_t1 | as<"last_name">,
|
||||
"first_name"_t2 | as<"first_name_child">,
|
||||
avg("age"_t1 - "age"_t2) | as<"avg_parent_age_at_birth">) |
|
||||
inner_join<"t2">(get_children, "id"_t1 == "id"_t2) |
|
||||
group_by("last_name"_t1, "first_name"_t2) | order_by("first_name"_t2) |
|
||||
to<std::vector<ParentAndChild>>;
|
||||
|
||||
const auto people = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(write(std::ref(relationships)))
|
||||
.and_then(get_people)
|
||||
.value();
|
||||
|
||||
const std::string expected_query =
|
||||
R"(SELECT t1.`last_name` AS `last_name`, t2.`first_name` AS `first_name_child`, AVG((t1.`age`) - (t2.`age`)) AS `avg_parent_age_at_birth` FROM `Person` t1 INNER JOIN (SELECT t1.`parent_id` AS `id`, t2.`first_name` AS `first_name`, t2.`age` AS `age` FROM `Relationship` t1 LEFT JOIN `Person` t2 ON t2.`id` = t1.`child_id`) t2 ON t1.`id` = t2.`id` GROUP BY t1.`last_name`, t2.`first_name` ORDER BY t2.`first_name`)";
|
||||
|
||||
const std::string expected =
|
||||
R"([{"last_name":"Simpson","first_name_child":"Bart","avg_parent_age_at_birth":32.5},{"last_name":"Simpson","first_name_child":"Lisa","avg_parent_age_at_birth":34.5},{"last_name":"Simpson","first_name_child":"Maggie","avg_parent_age_at_birth":42.5}])";
|
||||
|
||||
EXPECT_EQ(mysql::to_sql(get_people), expected_query);
|
||||
EXPECT_EQ(rfl::json::write(people), expected);
|
||||
}
|
||||
|
||||
} // namespace test_joins_nested_grouped
|
||||
|
||||
#endif
|
||||
89
tests/mysql/test_joins_two_tables.cpp
Normal file
89
tests/mysql/test_joins_two_tables.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_joins_two_tables {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
double age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::PrimaryKey<uint32_t> parent_id;
|
||||
sqlgen::PrimaryKey<uint32_t> child_id;
|
||||
};
|
||||
|
||||
TEST(mysql, test_joins_two_tables) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{
|
||||
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
|
||||
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 3, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 4, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto relationships =
|
||||
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
|
||||
Relationship{.parent_id = 0, .child_id = 3},
|
||||
Relationship{.parent_id = 0, .child_id = 4},
|
||||
Relationship{.parent_id = 1, .child_id = 2},
|
||||
Relationship{.parent_id = 1, .child_id = 3},
|
||||
Relationship{.parent_id = 1, .child_id = 4}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct ParentAndChild {
|
||||
std::string last_name;
|
||||
std::string first_name_parent;
|
||||
std::string first_name_child;
|
||||
double parent_age_at_birth;
|
||||
};
|
||||
|
||||
const auto get_people =
|
||||
select_from<Person, "t1">(
|
||||
"last_name"_t1 | as<"last_name">,
|
||||
"first_name"_t1 | as<"first_name_parent">,
|
||||
"first_name"_t3 | as<"first_name_child">,
|
||||
("age"_t1 - "age"_t3) | as<"parent_age_at_birth">) |
|
||||
inner_join<Relationship, "t2">("id"_t1 == "parent_id"_t2) |
|
||||
left_join<Person, "t3">("id"_t3 == "child_id"_t2) |
|
||||
order_by("id"_t1, "id"_t3) | to<std::vector<ParentAndChild>>;
|
||||
|
||||
const auto people = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(write(std::ref(relationships)))
|
||||
.and_then(get_people)
|
||||
.value();
|
||||
|
||||
const std::string expected_query =
|
||||
R"(SELECT t1.`last_name` AS `last_name`, t1.`first_name` AS `first_name_parent`, t3.`first_name` AS `first_name_child`, (t1.`age`) - (t3.`age`) AS `parent_age_at_birth` FROM `Person` t1 INNER JOIN `Relationship` t2 ON t1.`id` = t2.`parent_id` LEFT JOIN `Person` t3 ON t3.`id` = t2.`child_id` ORDER BY t1.`id`, t3.`id`)";
|
||||
const std::string expected =
|
||||
R"([{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Bart","parent_age_at_birth":35.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Lisa","parent_age_at_birth":37.0},{"last_name":"Simpson","first_name_parent":"Homer","first_name_child":"Maggie","parent_age_at_birth":45.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Bart","parent_age_at_birth":30.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Lisa","parent_age_at_birth":32.0},{"last_name":"Simpson","first_name_parent":"Marge","first_name_child":"Maggie","parent_age_at_birth":40.0}])";
|
||||
|
||||
EXPECT_EQ(mysql::to_sql(get_people), expected_query);
|
||||
EXPECT_EQ(rfl::json::write(people), expected);
|
||||
}
|
||||
|
||||
} // namespace test_joins_two_tables
|
||||
|
||||
#endif
|
||||
90
tests/mysql/test_joins_two_tables_grouped.cpp
Normal file
90
tests/mysql/test_joins_two_tables_grouped.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_joins_two_tables_grouped {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
double age;
|
||||
};
|
||||
|
||||
struct Relationship {
|
||||
sqlgen::PrimaryKey<uint32_t> parent_id;
|
||||
sqlgen::PrimaryKey<uint32_t> child_id;
|
||||
};
|
||||
|
||||
TEST(mysql, test_joins_two_tables_grouped) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{
|
||||
.id = 1, .first_name = "Marge", .last_name = "Simpson", .age = 40},
|
||||
Person{.id = 2, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 3, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 4, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto relationships =
|
||||
std::vector<Relationship>({Relationship{.parent_id = 0, .child_id = 2},
|
||||
Relationship{.parent_id = 0, .child_id = 3},
|
||||
Relationship{.parent_id = 0, .child_id = 4},
|
||||
Relationship{.parent_id = 1, .child_id = 2},
|
||||
Relationship{.parent_id = 1, .child_id = 3},
|
||||
Relationship{.parent_id = 1, .child_id = 4}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct ParentAndChild {
|
||||
std::string last_name;
|
||||
std::string first_name_child;
|
||||
double avg_parent_age_at_birth;
|
||||
};
|
||||
|
||||
const auto get_people =
|
||||
select_from<Person, "t1">(
|
||||
"last_name"_t1 | as<"last_name">,
|
||||
"first_name"_t3 | as<"first_name_child">,
|
||||
avg("age"_t1 - "age"_t3) | as<"avg_parent_age_at_birth">) |
|
||||
inner_join<Relationship, "t2">("id"_t1 == "parent_id"_t2) |
|
||||
left_join<Person, "t3">("id"_t3 == "child_id"_t2) |
|
||||
group_by("last_name"_t1, "first_name"_t3) |
|
||||
order_by("last_name"_t1, "first_name"_t3) |
|
||||
to<std::vector<ParentAndChild>>;
|
||||
|
||||
const auto people = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(drop<Relationship> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(write(std::ref(relationships)))
|
||||
.and_then(get_people)
|
||||
.value();
|
||||
|
||||
const std::string expected_query =
|
||||
R"(SELECT t1.`last_name` AS `last_name`, t3.`first_name` AS `first_name_child`, AVG((t1.`age`) - (t3.`age`)) AS `avg_parent_age_at_birth` FROM `Person` t1 INNER JOIN `Relationship` t2 ON t1.`id` = t2.`parent_id` LEFT JOIN `Person` t3 ON t3.`id` = t2.`child_id` GROUP BY t1.`last_name`, t3.`first_name` ORDER BY t1.`last_name`, t3.`first_name`)";
|
||||
|
||||
const std::string expected =
|
||||
R"([{"last_name":"Simpson","first_name_child":"Bart","avg_parent_age_at_birth":32.5},{"last_name":"Simpson","first_name_child":"Lisa","avg_parent_age_at_birth":34.5},{"last_name":"Simpson","first_name_child":"Maggie","avg_parent_age_at_birth":42.5}])";
|
||||
|
||||
EXPECT_EQ(mysql::to_sql(get_people), expected_query);
|
||||
EXPECT_EQ(rfl::json::write(people), expected);
|
||||
}
|
||||
|
||||
} // namespace test_joins_two_tables_grouped
|
||||
|
||||
#endif
|
||||
73
tests/mysql/test_like.cpp
Normal file
73
tests/mysql/test_like.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_like {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_like) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{
|
||||
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn = mysql::connect(credentials);
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("H%")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const auto people3 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.not_like("H%")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const auto people4 =
|
||||
conn.and_then(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("O'Reilly")) | order_by("age"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected1 =
|
||||
R"([{"id":4,"first_name":"Hugo","last_name":"Simpson","age":10},{"id":0,"first_name":"Homer","last_name":"Simpson","age":45}])";
|
||||
|
||||
const std::string expected2 =
|
||||
R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])";
|
||||
|
||||
const std::string expected3 = R"([])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected1);
|
||||
EXPECT_EQ(rfl::json::write(people3), expected2);
|
||||
EXPECT_EQ(rfl::json::write(people4), expected3);
|
||||
}
|
||||
|
||||
} // namespace test_like
|
||||
|
||||
#endif
|
||||
32
tests/mysql/test_like_dry.cpp
Normal file
32
tests/mysql/test_like_dry.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_like_dry {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_like_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto sql =
|
||||
mysql::to_sql(sqlgen::read<std::vector<Person>> |
|
||||
where("first_name"_c.like("H%")) | order_by("age"_c));
|
||||
|
||||
const std::string expected =
|
||||
R"(SELECT `id`, `first_name`, `last_name`, `age` FROM `Person` WHERE `first_name` LIKE 'H%' ORDER BY `age`)";
|
||||
|
||||
EXPECT_EQ(sql, expected);
|
||||
}
|
||||
|
||||
} // namespace test_like_dry
|
||||
57
tests/mysql/test_limit.cpp
Normal file
57
tests/mysql/test_limit.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_limit {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_limit) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{
|
||||
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto query =
|
||||
sqlgen::read<std::vector<Person>> | order_by("age"_c) | limit(2);
|
||||
|
||||
const auto people2 = query(conn).value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_limit
|
||||
|
||||
#endif
|
||||
84
tests/mysql/test_operations.cpp
Normal file
84
tests/mysql/test_operations.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_operations {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_operations) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct Children {
|
||||
int id_plus_age;
|
||||
int age_times_2;
|
||||
int id_plus_2_minus_age;
|
||||
int age_mod_3;
|
||||
int abs_age;
|
||||
double exp_age;
|
||||
double sqrt_age;
|
||||
size_t length_first_name;
|
||||
std::string full_name;
|
||||
std::string first_name_lower;
|
||||
std::string first_name_upper;
|
||||
std::string first_name_replaced;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Person>(
|
||||
("id"_c + "age"_c) | as<"id_plus_age">,
|
||||
("age"_c * 2) | as<"age_times_2">,
|
||||
("id"_c + 2 - "age"_c) | as<"id_plus_2_minus_age">,
|
||||
("age"_c % 3) | as<"age_mod_3">, abs("age"_c * (-1)) | as<"abs_age">,
|
||||
round(exp(cast<double>("age"_c)), 2) | as<"exp_age">,
|
||||
round(sqrt(cast<double>("age"_c)), 2) | as<"sqrt_age">,
|
||||
length(trim("first_name"_c)) | as<"length_first_name">,
|
||||
concat(ltrim("first_name"_c), " ", rtrim("last_name"_c)) |
|
||||
as<"full_name">,
|
||||
upper(rtrim(concat("first_name"_c, " "))) | as<"first_name_upper">,
|
||||
lower(ltrim(concat(" ", "first_name"_c))) | as<"first_name_lower">,
|
||||
replace("first_name"_c, "Bart", "Hugo") | as<"first_name_replaced">) |
|
||||
where("age"_c < 18) | order_by("age"_c.desc()) |
|
||||
to<std::vector<Children>>;
|
||||
|
||||
const auto children = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_children)
|
||||
.value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id_plus_age":11,"age_times_2":20,"id_plus_2_minus_age":-7,"age_mod_3":1,"abs_age":10,"exp_age":22026.47,"sqrt_age":3.16,"length_first_name":4,"full_name":"Bart Simpson","first_name_lower":"bart","first_name_upper":"BART","first_name_replaced":"Hugo"},{"id_plus_age":10,"age_times_2":16,"id_plus_2_minus_age":-4,"age_mod_3":2,"abs_age":8,"exp_age":2980.96,"sqrt_age":2.83,"length_first_name":4,"full_name":"Lisa Simpson","first_name_lower":"lisa","first_name_upper":"LISA","first_name_replaced":"Lisa"},{"id_plus_age":3,"age_times_2":0,"id_plus_2_minus_age":5,"age_mod_3":0,"abs_age":0,"exp_age":1.0,"sqrt_age":0.0,"length_first_name":6,"full_name":"Maggie Simpson","first_name_lower":"maggie","first_name_upper":"MAGGIE","first_name_replaced":"Maggie"}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(children), expected);
|
||||
}
|
||||
|
||||
} // namespace test_operations
|
||||
|
||||
#endif
|
||||
70
tests/mysql/test_operations_with_nullable.cpp
Normal file
70
tests/mysql/test_operations_with_nullable.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_operations_with_nullable {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<int> id;
|
||||
std::string first_name;
|
||||
std::optional<std::string> last_name;
|
||||
std::optional<int> age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_operations_with_nullable) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Hugo", .age = 10},
|
||||
Person{.id = 3, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 4, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct Children {
|
||||
std::optional<int> id_plus_age;
|
||||
std::optional<int> age_times_2;
|
||||
std::optional<int> id_plus_2_minus_age;
|
||||
std::optional<std::string> full_name;
|
||||
std::string last_name_or_none;
|
||||
};
|
||||
|
||||
const auto get_children =
|
||||
select_from<Person>(
|
||||
("id"_c + "age"_c) | as<"id_plus_age">,
|
||||
("age"_c * 2) | as<"age_times_2">,
|
||||
("id"_c + 2 - "age"_c) | as<"id_plus_2_minus_age">,
|
||||
concat(upper("last_name"_c), ", ", "first_name"_c) | as<"full_name">,
|
||||
coalesce("last_name"_c, "none") | as<"last_name_or_none">) |
|
||||
where("age"_c < 18) | to<std::vector<Children>>;
|
||||
|
||||
const auto children = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_children)
|
||||
.value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id_plus_age":11,"age_times_2":20,"id_plus_2_minus_age":-7,"full_name":"SIMPSON, Bart","last_name_or_none":"Simpson"},{"id_plus_age":12,"age_times_2":20,"id_plus_2_minus_age":-6,"last_name_or_none":"none"},{"id_plus_age":11,"age_times_2":16,"id_plus_2_minus_age":-3,"full_name":"SIMPSON, Lisa","last_name_or_none":"Simpson"},{"id_plus_age":4,"age_times_2":0,"id_plus_2_minus_age":6,"full_name":"SIMPSON, Maggie","last_name_or_none":"Simpson"}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(children), expected);
|
||||
}
|
||||
|
||||
} // namespace test_operations_with_nullable
|
||||
|
||||
#endif
|
||||
64
tests/mysql/test_range.cpp
Normal file
64
tests/mysql/test_range.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_range {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_range) {
|
||||
static_assert(std::ranges::input_range<sqlgen::Range<Person>>,
|
||||
"Must be an input range.");
|
||||
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
const auto people2 =
|
||||
sqlgen::write(conn, people1)
|
||||
.and_then(sqlgen::read<sqlgen::Range<Person>> | order_by("id"_c))
|
||||
.value();
|
||||
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto first_names =
|
||||
internal::collect::vector(people2 | transform([](const auto& _r) {
|
||||
return _r.value().first_name;
|
||||
}));
|
||||
|
||||
EXPECT_EQ(first_names.at(0), "Homer");
|
||||
EXPECT_EQ(first_names.at(1), "Bart");
|
||||
EXPECT_EQ(first_names.at(2), "Lisa");
|
||||
EXPECT_EQ(first_names.at(3), "Maggie");
|
||||
}
|
||||
|
||||
} // namespace test_range
|
||||
|
||||
#endif
|
||||
64
tests/mysql/test_range_select_from.cpp
Normal file
64
tests/mysql/test_range_select_from.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_range_select_from {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_range_select_from) {
|
||||
static_assert(std::ranges::input_range<sqlgen::Range<Person>>,
|
||||
"Must be an input range.");
|
||||
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
const auto people2 =
|
||||
sqlgen::write(conn, people1)
|
||||
.and_then(select_from<Person>("first_name"_c) | order_by("id"_c))
|
||||
.value();
|
||||
|
||||
using namespace std::ranges::views;
|
||||
|
||||
const auto first_names =
|
||||
internal::collect::vector(people2 | transform([](const auto& _r) {
|
||||
return rfl::get<"first_name">(_r.value());
|
||||
}));
|
||||
|
||||
EXPECT_EQ(first_names.at(0), "Homer");
|
||||
EXPECT_EQ(first_names.at(1), "Bart");
|
||||
EXPECT_EQ(first_names.at(2), "Lisa");
|
||||
EXPECT_EQ(first_names.at(3), "Maggie");
|
||||
}
|
||||
|
||||
} // namespace test_range_select_from
|
||||
|
||||
#endif
|
||||
88
tests/mysql/test_select_from_with_timestamps.cpp
Normal file
88
tests/mysql/test_select_from_with_timestamps.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_select_from_with_timestamps {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
sqlgen::Date birthday;
|
||||
};
|
||||
|
||||
TEST(mysql, test_select_from_with_timestamps) {
|
||||
const auto people1 =
|
||||
std::vector<Person>({Person{.first_name = "Homer",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("1970-01-01")},
|
||||
Person{.first_name = "Bart",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("2000-01-01")},
|
||||
Person{.first_name = "Lisa",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("2002-01-01")},
|
||||
Person{.first_name = "Maggie",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("2010-01-01")}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct Birthday {
|
||||
Date birthday;
|
||||
Date birthday_recreated;
|
||||
double age_in_days;
|
||||
int hour;
|
||||
int minute;
|
||||
int second;
|
||||
int weekday;
|
||||
};
|
||||
|
||||
const auto get_birthdays =
|
||||
select_from<Person>(
|
||||
("birthday"_c + std::chrono::weeks(2) + std::chrono::days(-4)) |
|
||||
as<"birthday">,
|
||||
((cast<Date>(concat(cast<std::string>(year("birthday"_c)), "-",
|
||||
cast<std::string>(month("birthday"_c)), "-",
|
||||
cast<std::string>(day("birthday"_c))))) +
|
||||
std::chrono::days(10)) |
|
||||
as<"birthday_recreated">,
|
||||
days_between("birthday"_c, Date("2011-01-01")) | as<"age_in_days">,
|
||||
hour("birthday"_c) | as<"hour">, minute("birthday"_c) | as<"minute">,
|
||||
second("birthday"_c) | as<"second">,
|
||||
weekday("birthday"_c) | as<"weekday">) |
|
||||
order_by("id"_c) | to<std::vector<Birthday>>;
|
||||
|
||||
const auto birthdays = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(get_birthdays)
|
||||
.value();
|
||||
|
||||
const std::string expected_query =
|
||||
R"(SELECT date_add(date_add(`birthday`, INTERVAL 2 week), INTERVAL -4 day) AS `birthday`, date_add(cast(concat(cast(extract(YEAR from `birthday`) as CHAR), '-', cast(extract(MONTH from `birthday`) as CHAR), '-', cast(extract(DAY from `birthday`) as CHAR)) as DATE), INTERVAL 10 day) AS `birthday_recreated`, datediff('2011-01-01', `birthday`) AS `age_in_days`, extract(HOUR from `birthday`) AS `hour`, extract(MINUTE from `birthday`) AS `minute`, extract(SECOND from `birthday`) AS `second`, dayofweek(`birthday`) AS `weekday` FROM `Person` ORDER BY `id`)";
|
||||
|
||||
const std::string expected =
|
||||
R"([{"birthday":"1970-01-11","birthday_recreated":"1970-01-11","age_in_days":14975.0,"hour":0,"minute":0,"second":0,"weekday":5},{"birthday":"2000-01-11","birthday_recreated":"2000-01-11","age_in_days":4018.0,"hour":0,"minute":0,"second":0,"weekday":7},{"birthday":"2002-01-11","birthday_recreated":"2002-01-11","age_in_days":3287.0,"hour":0,"minute":0,"second":0,"weekday":3},{"birthday":"2010-01-11","birthday_recreated":"2010-01-11","age_in_days":365.0,"hour":0,"minute":0,"second":0,"weekday":6}])";
|
||||
|
||||
EXPECT_EQ(mysql::to_sql(get_birthdays), expected_query);
|
||||
EXPECT_EQ(rfl::json::write(birthdays), expected);
|
||||
}
|
||||
|
||||
} // namespace test_select_from_with_timestamps
|
||||
|
||||
#endif
|
||||
61
tests/mysql/test_select_from_with_to.cpp
Normal file
61
tests/mysql/test_select_from_with_to.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_range_select_from_with_to {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_range_select_from) {
|
||||
static_assert(std::ranges::input_range<sqlgen::Range<Person>>,
|
||||
"Must be an input range.");
|
||||
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
struct FirstName {
|
||||
std::string first_name;
|
||||
};
|
||||
|
||||
const auto people2 =
|
||||
mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(select_from<Person>("first_name"_c) | order_by("id"_c) |
|
||||
to<std::vector<FirstName>>)
|
||||
.value();
|
||||
|
||||
EXPECT_EQ(people2.at(0).first_name, "Homer");
|
||||
EXPECT_EQ(people2.at(1).first_name, "Bart");
|
||||
EXPECT_EQ(people2.at(2).first_name, "Lisa");
|
||||
EXPECT_EQ(people2.at(3).first_name, "Maggie");
|
||||
}
|
||||
|
||||
} // namespace test_range_select_from_with_to
|
||||
|
||||
#endif
|
||||
48
tests/mysql/test_timestamp.cpp
Normal file
48
tests/mysql/test_timestamp.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_timestamp {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
sqlgen::Timestamp<"%Y-%m-%d %H:%M:%S"> ts;
|
||||
};
|
||||
|
||||
TEST(mysql, test_timestamp) {
|
||||
const auto people1 =
|
||||
std::vector<Person>({Person{.id = 0,
|
||||
.first_name = "Homer",
|
||||
.last_name = "Simpson",
|
||||
.ts = "2000-01-01 01:00:00"}});
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
const auto people2 = sqlgen::write(conn, people1)
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_timestamp
|
||||
|
||||
#endif
|
||||
23
tests/mysql/test_timestamp_dry.cpp
Normal file
23
tests/mysql/test_timestamp_dry.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_timestamp_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
sqlgen::Timestamp<"%Y-%m-%d %H:%M:%S"> ts;
|
||||
};
|
||||
|
||||
TEST(mysql, test_timestamp_dry) {
|
||||
const auto query = sqlgen::CreateTable<TestTable>{};
|
||||
|
||||
const auto expected =
|
||||
R"(CREATE TABLE IF NOT EXISTS `TestTable` (`field1` TEXT NOT NULL, `field2` INT NOT NULL, `id` BIGINT NOT NULL, `ts` DATETIME NOT NULL, PRIMARY KEY (`id`));)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_timestamp_dry
|
||||
32
tests/mysql/test_to_select_from2_dry.cpp
Normal file
32
tests/mysql/test_to_select_from2_dry.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_to_select_from2_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_to_select_from2_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query =
|
||||
select_from<TestTable>("field1"_c.as<"field">(),
|
||||
avg("field2"_c).as<"avg_field2">(),
|
||||
"nullable"_c | as<"nullable_field">, as<"one">(1),
|
||||
"hello" | as<"hello">) |
|
||||
where("id"_c > 0) | group_by("field1"_c, "nullable"_c) |
|
||||
order_by("field1"_c) | limit(10);
|
||||
|
||||
const auto expected =
|
||||
R"(SELECT `field1` AS `field`, AVG(`field2`) AS `avg_field2`, `nullable` AS `nullable_field`, 1 AS `one`, 'hello' AS `hello` FROM `TestTable` WHERE `id` > 0 GROUP BY `field1`, `nullable` ORDER BY `field1` LIMIT 10)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_to_select_from2_dry
|
||||
23
tests/mysql/test_to_select_from_dry.cpp
Normal file
23
tests/mysql/test_to_select_from_dry.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_to_select_from_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_to_select_from_dry) {
|
||||
const auto query = sqlgen::read<std::vector<TestTable>>;
|
||||
|
||||
const auto expected =
|
||||
R"(SELECT `field1`, `field2`, `id`, `nullable` FROM `TestTable`)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_to_select_from_dry
|
||||
67
tests/mysql/test_transaction.cpp
Normal file
67
tests/mysql/test_transaction.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_transaction {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_transaction) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{
|
||||
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto delete_hugo =
|
||||
delete_from<Person> | where("first_name"_c == "Hugo");
|
||||
|
||||
const auto update_homers_age =
|
||||
update<Person>("age"_c.set(46)) | where("first_name"_c == "Homer");
|
||||
|
||||
const auto get_data = sqlgen::read<std::vector<Person>> | order_by("id"_c);
|
||||
|
||||
const auto people2 = begin_transaction(conn)
|
||||
.and_then(delete_hugo)
|
||||
.and_then(update_homers_age)
|
||||
.and_then(commit)
|
||||
.and_then(get_data)
|
||||
.value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id":0,"first_name":"Homer","last_name":"Simpson","age":46},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_transaction
|
||||
|
||||
#endif
|
||||
61
tests/mysql/test_update.cpp
Normal file
61
tests/mysql/test_update.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_update {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_update) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{
|
||||
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto update_hugos_age =
|
||||
update<Person>("first_name"_c.set("last_name"_c), "age"_c.set(100)) |
|
||||
where("first_name"_c == "Hugo");
|
||||
|
||||
const auto people2 =
|
||||
conn.and_then(update_hugos_age)
|
||||
.and_then(sqlgen::read<std::vector<Person>> | order_by("id"_c))
|
||||
.value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id":0,"first_name":"Homer","last_name":"Simpson","age":45},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":4,"first_name":"Simpson","last_name":"Simpson","age":100}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_update
|
||||
|
||||
#endif
|
||||
28
tests/mysql/test_update_dry.cpp
Normal file
28
tests/mysql/test_update_dry.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
|
||||
namespace test_update_dry {
|
||||
|
||||
struct TestTable {
|
||||
std::string field1;
|
||||
int32_t field2;
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::optional<std::string> nullable;
|
||||
};
|
||||
|
||||
TEST(mysql, test_update_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query =
|
||||
update<TestTable>("field1"_c.set("Hello"), "nullable"_c.set("field1"_c)) |
|
||||
where("field2"_c > 0);
|
||||
|
||||
const auto expected =
|
||||
R"(UPDATE `TestTable` SET `field1` = 'Hello', `nullable` = `field1` WHERE `field2` > 0;)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
} // namespace test_update_dry
|
||||
53
tests/mysql/test_varchar.cpp
Normal file
53
tests/mysql/test_varchar.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_varchar {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
sqlgen::Varchar<6> first_name;
|
||||
sqlgen::Varchar<7> last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_varchar) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_varchar
|
||||
|
||||
#endif
|
||||
58
tests/mysql/test_where.cpp
Normal file
58
tests/mysql/test_where.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_where {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_where) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{
|
||||
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto query = sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c < 18 and not("first_name"_c == "Hugo")) |
|
||||
order_by("age"_c);
|
||||
|
||||
const auto people2 = query(conn).value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_where
|
||||
|
||||
#endif
|
||||
30
tests/mysql/test_where_dry.cpp
Normal file
30
tests/mysql/test_where_dry.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_where_dry {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_where_dry) {
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto query = sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c < 18 and "first_name"_c != "Hugo") |
|
||||
order_by("age"_c);
|
||||
|
||||
const auto expected =
|
||||
R"(SELECT `id`, `first_name`, `last_name`, `age` FROM `Person` WHERE (`age` < 18) AND (`first_name` != 'Hugo') ORDER BY `age`)";
|
||||
|
||||
EXPECT_EQ(sqlgen::mysql::to_sql(query), expected);
|
||||
}
|
||||
|
||||
} // namespace test_where_dry
|
||||
58
tests/mysql/test_where_with_operations.cpp
Normal file
58
tests/mysql/test_where_with_operations.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_where_with_operations {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_where_with_operations) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0},
|
||||
Person{
|
||||
.id = 4, .first_name = "Hugo", .last_name = "Simpson", .age = 10}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto query = sqlgen::read<std::vector<Person>> |
|
||||
where("age"_c * 2 + 4 < 40 and "first_name"_c != "Hugo") |
|
||||
order_by("age"_c);
|
||||
|
||||
const auto people2 = query(conn).value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id":3,"first_name":"Maggie","last_name":"Simpson","age":0},{"id":2,"first_name":"Lisa","last_name":"Simpson","age":8},{"id":1,"first_name":"Bart","last_name":"Simpson","age":10}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_where_with_operations
|
||||
|
||||
#endif
|
||||
67
tests/mysql/test_where_with_timestamps.cpp
Normal file
67
tests/mysql/test_where_with_timestamps.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_where_with_timestamps {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
sqlgen::Date birthday;
|
||||
};
|
||||
|
||||
TEST(mysql, test_where_with_timestamps) {
|
||||
const auto people1 =
|
||||
std::vector<Person>({Person{.first_name = "Homer",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("1970-01-01")},
|
||||
Person{.first_name = "Bart",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("2000-01-01")},
|
||||
Person{.first_name = "Lisa",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("2002-01-01")},
|
||||
Person{.first_name = "Maggie",
|
||||
.last_name = "Simpson",
|
||||
.birthday = sqlgen::Date("2010-01-01")}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
const auto conn =
|
||||
sqlgen::mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
sqlgen::write(conn, people1).value();
|
||||
|
||||
const auto query =
|
||||
sqlgen::read<std::vector<Person>> |
|
||||
where("birthday"_c + std::chrono::years(11) - std::chrono::weeks(10) +
|
||||
std::chrono::milliseconds(4000000) >
|
||||
Date("2010-01-01")) |
|
||||
order_by("id"_c);
|
||||
|
||||
const auto people2 = query(conn).value();
|
||||
|
||||
const std::string expected =
|
||||
R"([{"id":2,"first_name":"Bart","last_name":"Simpson","birthday":"2000-01-01"},{"id":3,"first_name":"Lisa","last_name":"Simpson","birthday":"2002-01-01"},{"id":4,"first_name":"Maggie","last_name":"Simpson","birthday":"2010-01-01"}])";
|
||||
|
||||
EXPECT_EQ(rfl::json::write(people2), expected);
|
||||
}
|
||||
|
||||
} // namespace test_where_with_timestamps
|
||||
|
||||
#endif
|
||||
51
tests/mysql/test_write_and_read.cpp
Normal file
51
tests/mysql/test_write_and_read.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_write_and_read {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_write_and_read) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
using namespace sqlgen;
|
||||
|
||||
const auto credentials = mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
const auto conn =
|
||||
mysql::connect(credentials).and_then(drop<Person> | if_exists);
|
||||
|
||||
write(conn, people1).value();
|
||||
|
||||
const auto people2 = sqlgen::read<std::vector<Person>>(conn).value();
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_write_and_read
|
||||
|
||||
#endif
|
||||
51
tests/mysql/test_write_and_read_curried.cpp
Normal file
51
tests/mysql/test_write_and_read_curried.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_write_and_read_curried {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_write_and_read_curried) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people2 = mysql::connect(credentials)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_write_and_read_curried
|
||||
|
||||
#endif
|
||||
58
tests/mysql/test_write_and_read_pool.cpp
Normal file
58
tests/mysql/test_write_and_read_pool.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef SQLGEN_BUILD_DRY_TESTS_ONLY
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/json.hpp>
|
||||
#include <sqlgen.hpp>
|
||||
#include <sqlgen/mysql.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace test_write_and_read_pool {
|
||||
|
||||
struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
int age;
|
||||
};
|
||||
|
||||
TEST(mysql, test_write_and_read_pool) {
|
||||
const auto people1 = std::vector<Person>(
|
||||
{Person{
|
||||
.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
|
||||
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
|
||||
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8},
|
||||
Person{
|
||||
.id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}});
|
||||
|
||||
const auto pool_config = sqlgen::ConnectionPoolConfig{.size = 2};
|
||||
|
||||
const auto credentials = sqlgen::mysql::Credentials{.host = "localhost",
|
||||
.user = "sqlgen",
|
||||
.password = "password",
|
||||
.dbname = "mysql"};
|
||||
|
||||
const auto pool = sqlgen::make_connection_pool<sqlgen::mysql::Connection>(
|
||||
pool_config, credentials);
|
||||
|
||||
using namespace sqlgen;
|
||||
using namespace sqlgen::literals;
|
||||
|
||||
const auto people2 = session(pool)
|
||||
.and_then(drop<Person> | if_exists)
|
||||
.and_then(write(std::ref(people1)))
|
||||
.and_then(sqlgen::read<std::vector<Person>>)
|
||||
.value();
|
||||
|
||||
EXPECT_EQ(pool.value().available(), 2);
|
||||
|
||||
const auto json1 = rfl::json::write(people1);
|
||||
const auto json2 = rfl::json::write(people2);
|
||||
|
||||
EXPECT_EQ(json1, json2);
|
||||
}
|
||||
|
||||
} // namespace test_write_and_read_pool
|
||||
|
||||
#endif
|
||||
@@ -16,7 +16,7 @@ struct Person {
|
||||
sqlgen::PrimaryKey<uint32_t, sqlgen::auto_incr> id;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
sqlgen::Timestamp<"%Y-%m-%d"> birthday;
|
||||
sqlgen::Date birthday;
|
||||
};
|
||||
|
||||
TEST(postgres, test_range_select_from_with_timestamps) {
|
||||
@@ -64,8 +64,9 @@ TEST(postgres, test_range_select_from_with_timestamps) {
|
||||
days_between("birthday"_c, Date("2011-01-01")) | as<"age_in_days">,
|
||||
unixepoch("birthday"_c + std::chrono::days(10)) |
|
||||
as<"birthday_unixepoch">,
|
||||
hour("birthday"_c) | as<"hour">, minute("birthday"_c) | as<"minute">,
|
||||
second("birthday"_c) | as<"second">,
|
||||
hour(cast<DateTime>("birthday"_c)) | as<"hour">,
|
||||
minute(cast<DateTime>("birthday"_c)) | as<"minute">,
|
||||
second(cast<DateTime>("birthday"_c)) | as<"second">,
|
||||
weekday("birthday"_c) | as<"weekday">) |
|
||||
order_by("id"_c) | to<std::vector<Birthday>>;
|
||||
|
||||
@@ -76,7 +77,7 @@ TEST(postgres, test_range_select_from_with_timestamps) {
|
||||
.value();
|
||||
|
||||
const std::string expected_query =
|
||||
R"(SELECT "birthday" + INTERVAL '10 days' AS "birthday", cast((cast(extract(YEAR from "birthday") as TEXT) || '-' || cast(extract(MONTH from "birthday") as TEXT) || '-' || cast(extract(DAY from "birthday") as TEXT)) as TIMESTAMP) + INTERVAL '10 days' AS "birthday_recreated", cast('2011-01-01' as DATE) - cast("birthday" as DATE) AS "age_in_days", extract(EPOCH FROM "birthday" + INTERVAL '10 days') AS "birthday_unixepoch", extract(HOUR from "birthday") AS "hour", extract(MINUTE from "birthday") AS "minute", extract(SECOND from "birthday") AS "second", extract(DOW from "birthday") AS "weekday" FROM "Person" ORDER BY "id")";
|
||||
R"(SELECT "birthday" + INTERVAL '10 days' AS "birthday", cast((cast(extract(YEAR from "birthday") as TEXT) || '-' || cast(extract(MONTH from "birthday") as TEXT) || '-' || cast(extract(DAY from "birthday") as TEXT)) as DATE) + INTERVAL '10 days' AS "birthday_recreated", cast('2011-01-01' as DATE) - cast("birthday" as DATE) AS "age_in_days", extract(EPOCH FROM "birthday" + INTERVAL '10 days') AS "birthday_unixepoch", extract(HOUR from cast("birthday" as TIMESTAMP)) AS "hour", extract(MINUTE from cast("birthday" as TIMESTAMP)) AS "minute", extract(SECOND from cast("birthday" as TIMESTAMP)) AS "second", extract(DOW from "birthday") AS "weekday" FROM "Person" ORDER BY "id")";
|
||||
|
||||
const std::string expected =
|
||||
R"([{"birthday":"1970-01-11","birthday_recreated":"1970-01-11","birthday_unixepoch":864000,"age_in_days":14975.0,"hour":0,"minute":0,"second":0,"weekday":4},{"birthday":"2000-01-11","birthday_recreated":"2000-01-11","birthday_unixepoch":947548800,"age_in_days":4018.0,"hour":0,"minute":0,"second":0,"weekday":6},{"birthday":"2002-01-11","birthday_recreated":"2002-01-11","birthday_unixepoch":1010707200,"age_in_days":3287.0,"hour":0,"minute":0,"second":0,"weekday":2},{"birthday":"2010-01-11","birthday_recreated":"2010-01-11","birthday_unixepoch":1263168000,"age_in_days":365.0,"hour":0,"minute":0,"second":0,"weekday":5}])";
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
}
|
||||
],
|
||||
"features": {
|
||||
"mysql": {
|
||||
"description": "Enable MySQL/MariaDB support",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "libmariadb",
|
||||
"version>=": "3.4.5"
|
||||
}
|
||||
]
|
||||
},
|
||||
"postgres": {
|
||||
"description": "Enable PostgreSQL support",
|
||||
"dependencies": [
|
||||
|
||||
Reference in New Issue
Block a user