mirror of
https://github.com/latentPrion/cppbessot.git
synced 2026-06-24 07:38:36 +00:00
Compare commits
7 Commits
f507cda960
...
3d26d89742
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d26d89742 | |||
| e00601c721 | |||
| e09625c466 | |||
| 68724dff8f | |||
| e12ad0ecac | |||
| b16421fd08 | |||
| 7e6e5f5081 |
@@ -0,0 +1,11 @@
|
||||
# Project Instructions
|
||||
|
||||
- Always break functions into logical subfunctions. No long-scrolling functions, in any language. This applies to source code, scripts, build scripts, CMake, Makefiles, and similar project files. Preserve this subfunction splitting discipline during refactors.
|
||||
- Modularity is non-negotiable. Always group logically related functions together into a module. Preserve modularity during refactors.
|
||||
- Reuse or extend existing abstractions instead of duplicating logic wherever possible. Don't repeat yourself. The goal here is to prevent duplication. Not to discourage appropriate logical separation of prior abstractions into new logical abstractions where sensible.
|
||||
- Always isolate configurable behaviour into configuration variables appropriate for the language and framework being used.
|
||||
- Never bake in literals; at minimum, declare them at the top of the file with a semantically meaningful name.
|
||||
- UI should be responsive. Always prefer to use pre-packaged UI toolkit widgets, containers and colour sets harmoniously, instead of writing custom CSS overrides. Write custom CSS only if there's no UI toolkit mechanism available.
|
||||
- Aggressively isolate, split off, deduplicate and reuse code which can be made into common library code. Do the same with UI elements. Do this both when implementing new features and opportunistically while refactoring or changing old code/UI elements.
|
||||
- Names of files, functions, classes, abstractions, database fields, etc should be aimed at disambiguating purpose and function, rather than at brevity.
|
||||
- Whenever a backend invariant changes; or in general whenever a backend feature/change can be automatically tested, add all tests reasonable, erring on the side of complete coverage over incomplete coverage.
|
||||
@@ -9,10 +9,15 @@ if(NOT DEFINED DB_SCHEMA_DIR_TO_GENERATE OR "${DB_SCHEMA_DIR_TO_GENERATE}" STREQ
|
||||
"Set DB_SCHEMA_DIR_TO_GENERATE to the exact schema directory basename to test, for example -DDB_SCHEMA_DIR_TO_GENERATE=v1.2.")
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/dbDependencyCheck.cmake")
|
||||
cppbessot_check_dependencies()
|
||||
|
||||
set(CPPBESSOT_ODB_TEST_SQLITE_CONNSTR "" CACHE STRING
|
||||
"Optional SQLite connection string for ODB runtime tests")
|
||||
set(CPPBESSOT_ODB_TEST_PGSQL_CONNSTR "" CACHE STRING
|
||||
"Optional PostgreSQL conninfo string for ODB runtime tests")
|
||||
set(CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR "" CACHE STRING
|
||||
"Optional PostgreSQL admin conninfo string for real db-action test database lifecycle operations")
|
||||
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/CppBeSSOT.cmake")
|
||||
|
||||
|
||||
@@ -1,110 +1,654 @@
|
||||
# CPPBESSOT (C++ BackEnd Single Source of Truth):
|
||||
# cppbessot
|
||||
|
||||
A framework that uses OpenAPI to maintain a single source of truth for the data model of a software project. It generates C++ headers, JSON serdes, ODB-based ORM headers, DB migrations, Typescript types and Zod schemas. I.e: a type-safe backend-to-frontend data model manager.
|
||||
`cppbessot` is a CMake-driven schema pipeline for projects that keep their application data model in OpenAPI and need consistent generated artifacts across C++, TypeScript, Zod, ODB, SQL DDL, and database migrations.
|
||||
|
||||
Basically, it enables one to write a web application whose backend is written in C++. This C++ web application can communicate seamlessly with a Typescript frontend without losing type-safety. We leverage Zod to enforce type safety. So you get type-safety from end to end. From C++ through to the Typescript frontend.
|
||||
The OpenAPI file under a schema directory is the single source of truth. From that, `cppbessot` can:
|
||||
|
||||
It works by specifying the data model in OpenAPI. Then the OpenAPI model is transpiled into both C++ headers (with JSON serdes and ODB ORM for your database of choice) and Typescript types with Zod schema descriptions.
|
||||
- generate C++ model headers and JSON serdes sources
|
||||
- generate ODB ORM sources for SQLite and PostgreSQL
|
||||
- generate SQL DDL snapshots for SQLite and PostgreSQL
|
||||
- generate TypeScript types
|
||||
- generate Zod schemas
|
||||
- generate SQL migration artifacts between two schema versions
|
||||
- build linkable generated C++ libraries
|
||||
- run live DB actions against `dev`, `prod`, or `proddev`
|
||||
|
||||
## CMake integration
|
||||
## Repository Layout
|
||||
|
||||
Reusable CMake entry point for embedding in a larger project:
|
||||
The embedded module entry point is:
|
||||
|
||||
- `cmake/CppBeSSOT.cmake`
|
||||
|
||||
The primary schema root folder is configurable via:
|
||||
|
||||
- `CPPBESSOT_WORKDIR` (defaults to `db`)
|
||||
|
||||
This repo also carries self-contained schema fixtures for test runs:
|
||||
The repo also ships test fixtures under:
|
||||
|
||||
- `db/test-schema-v1.1`
|
||||
- `db/test-schema-v1.2`
|
||||
|
||||
## Simple Integration Guide
|
||||
Each schema directory is expected to look roughly like this:
|
||||
|
||||
### 1) Add the module from your parent project
|
||||
```text
|
||||
<CPPBESSOT_WORKDIR>/<schema-dir>/
|
||||
openapi/openapi.yaml
|
||||
generated-cpp-source/
|
||||
generated-odb-source/
|
||||
generated-sql-ddl/
|
||||
generated-ts-types/
|
||||
generated-zod/
|
||||
```
|
||||
|
||||
Migration artifacts live under:
|
||||
|
||||
```text
|
||||
<CPPBESSOT_WORKDIR>/migrations/<from>-<to>/
|
||||
sqlite/
|
||||
postgre/
|
||||
pre-structural-backfill.sh # optional
|
||||
post-structural-backfill.sh # optional
|
||||
```
|
||||
|
||||
## Configure-Time Requirements
|
||||
|
||||
`cppbessot` checks its dependencies during configure.
|
||||
|
||||
Always required:
|
||||
|
||||
- `git`
|
||||
- `java`
|
||||
- `npm`
|
||||
- `npx`
|
||||
- `odb`
|
||||
- `sqlite3` CLI
|
||||
- SQLite development headers and client library
|
||||
- PostgreSQL development headers and client library
|
||||
- `nlohmann_json`
|
||||
- npm packages:
|
||||
- `@openapitools/openapi-generator-cli`
|
||||
- `openapi-zod-client`
|
||||
|
||||
Conditionally required:
|
||||
|
||||
- `psql`
|
||||
- required when PostgreSQL live DB actions are configured
|
||||
- required when real PostgreSQL db-action tests are configured
|
||||
|
||||
## Top-Level CMake Configuration
|
||||
|
||||
When configuring the standalone `cppbessot` repo itself, `DB_SCHEMA_DIR_TO_GENERATE` must be set explicitly:
|
||||
|
||||
```bash
|
||||
cmake -S cmake/cppbessot -B build-cppbessot -DDB_SCHEMA_DIR_TO_GENERATE=test-schema-v1.2
|
||||
```
|
||||
|
||||
The standalone top-level file also exposes these test-related cache variables:
|
||||
|
||||
- `CPPBESSOT_ODB_TEST_SQLITE_CONNSTR`
|
||||
- optional SQLite connection string for ODB runtime tests
|
||||
- `CPPBESSOT_ODB_TEST_PGSQL_CONNSTR`
|
||||
- optional PostgreSQL conninfo string for ODB runtime tests
|
||||
- `CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR`
|
||||
- optional PostgreSQL admin conninfo string used only by the real db-action test harness
|
||||
|
||||
## Embedding In A Parent Project
|
||||
|
||||
Minimal parent-project integration:
|
||||
|
||||
```cmake
|
||||
# Parent project CMakeLists.txt
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(MyApp LANGUAGES CXX)
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(my_app LANGUAGES CXX)
|
||||
|
||||
# Optional: where schema directories live (default is "db")
|
||||
set(CPPBESSOT_WORKDIR "db")
|
||||
|
||||
# Required: exact schema directory basename under CPPBESSOT_WORKDIR
|
||||
set(DB_SCHEMA_DIR_TO_GENERATE "v1.1")
|
||||
|
||||
# Optional: only needed if you will run db_gen_migrations
|
||||
# set(DB_SCHEMA_DIR_MIGRATION_FROM "v1.1")
|
||||
# set(DB_SCHEMA_DIR_MIGRATION_TO "v1.2")
|
||||
set(DB_SCHEMA_DIR_TO_GENERATE "v1.2")
|
||||
|
||||
include(path/to/cppbessot/cmake/CppBeSSOT.cmake)
|
||||
```
|
||||
|
||||
### 2) Build generation targets manually
|
||||
By default, including `CppBeSSOT.cmake` auto-registers all generation targets, live DB action targets, and generated libraries.
|
||||
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build --target db_gen_orm_serdes_and_zod
|
||||
cmake --build build --target db_gen_sql_ddl
|
||||
cmake --build build --target db_check_schema_changes
|
||||
```
|
||||
|
||||
Optional migration generation:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_MIGRATION_FROM=v1.1 \
|
||||
-DDB_SCHEMA_DIR_MIGRATION_TO=v1.2
|
||||
cmake --build build --target db_gen_migrations
|
||||
```
|
||||
|
||||
### 2b) Build the bundled serdes tests
|
||||
|
||||
These tests validate that checked-in generated C++ model code can be compiled and used for JSON round trips. They are owned by `cppbessot`, not by any parent project.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive tests/googletest
|
||||
cmake -S . -B build-tests -DBUILD_TESTING=ON -DDB_SCHEMA_DIR_TO_GENERATE=test-schema-v1.2
|
||||
cmake --build build-tests --target cpp_serdes_test_schema_v1_2
|
||||
ctest --test-dir build-tests --output-on-failure
|
||||
```
|
||||
|
||||
The local test fixtures live under `db/test-schema-v1.1` and `db/test-schema-v1.2`. They intentionally differ so migration generation has real additive schema changes to process.
|
||||
|
||||
For ODB runtime tests, also provide backend connection strings:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build-tests \
|
||||
-DBUILD_TESTING=ON \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=test-schema-v1.1 \
|
||||
-DCPPBESSOT_ODB_TEST_SQLITE_CONNSTR=/tmp/cppbessot-odb-test.sqlite \
|
||||
-DCPPBESSOT_ODB_TEST_PGSQL_CONNSTR="host=127.0.0.1 port=5432 dbname=cppbessot_test user=postgres password=postgres"
|
||||
cmake --build build-tests --target cpp_odb_orm_sqlite_test_schema_v1_1
|
||||
cmake --build build-tests --target cpp_odb_orm_pgsql_test_schema_v1_1
|
||||
ctest --test-dir build-tests --output-on-failure
|
||||
```
|
||||
|
||||
Use a dedicated PostgreSQL test database. The ODB runtime tests recreate the schema.
|
||||
The ODB runtime tests also verify hydration from ORM query result sets by persisting multiple rows, querying them back through `odb::result<T>`, and asserting that distinct field values are materialized correctly for both SQLite and PostgreSQL.
|
||||
The configured CMake connstring values are baked into the test binaries as defaults, so direct binary execution works without exporting environment variables first. If the matching environment variable is set at runtime, it still overrides the compiled default.
|
||||
|
||||
### 3) Link generated libraries
|
||||
|
||||
```cmake
|
||||
target_link_libraries(my_app PRIVATE
|
||||
cppbessot::openai_model_gen
|
||||
cppbessot::odb_sqlite # if sqlite odb sources exist
|
||||
cppbessot::odb_pgsql # if postgre odb sources exist
|
||||
)
|
||||
```
|
||||
|
||||
### Optional manual enable mode
|
||||
If you want manual control:
|
||||
|
||||
```cmake
|
||||
set(CPPBESSOT_AUTO_ENABLE OFF)
|
||||
include(path/to/cppbessot/cmake/CppBeSSOT.cmake)
|
||||
cppbessot_enable()
|
||||
```
|
||||
|
||||
## CMake Variables
|
||||
|
||||
### Core Schema Variables
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- default: `db`
|
||||
- schema root folder, relative to `PROJECT_SOURCE_DIR` or absolute
|
||||
|
||||
- `DB_SCHEMA_DIR_TO_GENERATE`
|
||||
- default in module mode: `v1.1`
|
||||
- required in standalone top-level configure
|
||||
- basename of the schema directory to generate artifacts for
|
||||
|
||||
- `DB_SCHEMA_DIR_MIGRATION_FROM`
|
||||
- default: empty
|
||||
- source schema basename for `db_gen_migrations`
|
||||
|
||||
- `DB_SCHEMA_DIR_MIGRATION_TO`
|
||||
- default: empty
|
||||
- target schema basename for `db_gen_migrations`
|
||||
|
||||
- `DB_SCHEMA_CHANGES_ARE_ERROR`
|
||||
- default: `OFF`
|
||||
- used by schema-drift checking logic
|
||||
|
||||
- `CPPBESSOT_AUTO_ENABLE`
|
||||
- default: `ON`
|
||||
- when `ON`, including `CppBeSSOT.cmake` immediately registers targets and libraries
|
||||
|
||||
### Live DB Action Variables
|
||||
|
||||
- `DB_TARGET`
|
||||
- default: `dev`
|
||||
- selected live DB target for `db_createfrom` and `db_migrate`
|
||||
- allowed values: `prod`, `proddev`, `dev`
|
||||
|
||||
- `DB_CREATEFROM_SCHEMA_DIR`
|
||||
- default: `DB_SCHEMA_DIR_TO_GENERATE`
|
||||
- schema basename whose generated SQL DDL should be used by `db_createfrom`
|
||||
|
||||
- `DB_MIGRATE_WITH`
|
||||
- default: empty
|
||||
- migration directory basename under `<CPPBESSOT_WORKDIR>/migrations`
|
||||
- example: `v1.1-v1.2`
|
||||
|
||||
- `DB_MIGRATE_PRODDEV_USE_STALE`
|
||||
- default: `OFF`
|
||||
- when `ON` and `DB_TARGET=proddev`, reuse the existing proddev target instead of recloning from prod
|
||||
|
||||
### SQLite Live DB Mapping Variables
|
||||
|
||||
Exactly one backend mapping must be set for the selected `DB_TARGET`.
|
||||
|
||||
- `CPPBESSOT_DB_SQLITE_PROD_PATH`
|
||||
- `CPPBESSOT_DB_SQLITE_DEV_PATH`
|
||||
- `CPPBESSOT_DB_SQLITE_PRODDEV_PATH`
|
||||
|
||||
These point at the SQLite DB file to act on for `prod`, `dev`, or `proddev`.
|
||||
|
||||
### PostgreSQL Live DB Mapping Variables
|
||||
|
||||
- `CPPBESSOT_DB_PGSQL_PROD_CONNSTR`
|
||||
- `CPPBESSOT_DB_PGSQL_DEV_CONNSTR`
|
||||
- `CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR`
|
||||
|
||||
These are `psql`-compatible PostgreSQL connection strings for `prod`, `dev`, or `proddev`.
|
||||
|
||||
### Prod-To-Proddev Clone Hook Variables
|
||||
|
||||
These are only needed for `DB_TARGET=proddev` when `DB_MIGRATE_PRODDEV_USE_STALE=OFF`.
|
||||
|
||||
- `CPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND`
|
||||
- `CPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND`
|
||||
|
||||
They must be shell command strings that clone the `prod` database into `proddev` for the chosen backend.
|
||||
|
||||
## Command Targets
|
||||
|
||||
All custom targets are `EXCLUDE_FROM_ALL`, so they run only when explicitly requested or when a dependent library is built.
|
||||
|
||||
### `db_check_schema_changes`
|
||||
|
||||
Purpose:
|
||||
|
||||
- checks for git-tracked schema changes under `CPPBESSOT_WORKDIR`
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- `DB_SCHEMA_CHANGES_ARE_ERROR`
|
||||
|
||||
Output:
|
||||
|
||||
- no generated files
|
||||
- emits success/failure diagnostics about dirty schema state
|
||||
|
||||
### `db_gen_ts`
|
||||
|
||||
Purpose:
|
||||
|
||||
- generates TypeScript types from `openapi/openapi.yaml`
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- `DB_SCHEMA_DIR_TO_GENERATE`
|
||||
|
||||
Output:
|
||||
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-ts-types/`
|
||||
|
||||
### `db_gen_zod`
|
||||
|
||||
Purpose:
|
||||
|
||||
- generates Zod schemas from `openapi/openapi.yaml`
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- `DB_SCHEMA_DIR_TO_GENERATE`
|
||||
|
||||
Output:
|
||||
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-zod/schemas.ts`
|
||||
|
||||
### `db_gen_cpp_headers`
|
||||
|
||||
Purpose:
|
||||
|
||||
- generates C++ model headers and JSON serdes/model sources from OpenAPI
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- `DB_SCHEMA_DIR_TO_GENERATE`
|
||||
|
||||
Output:
|
||||
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-cpp-source/include/`
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-cpp-source/src/`
|
||||
|
||||
### `db_gen_odb_logic`
|
||||
|
||||
Purpose:
|
||||
|
||||
- generates ODB ORM sources for both SQLite and PostgreSQL
|
||||
- depends on `db_gen_cpp_headers`
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- `DB_SCHEMA_DIR_TO_GENERATE`
|
||||
|
||||
Output:
|
||||
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-odb-source/sqlite/`
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-odb-source/postgre/`
|
||||
|
||||
### `db_gen_sql_ddl`
|
||||
|
||||
Purpose:
|
||||
|
||||
- generates SQL DDL snapshots for both SQLite and PostgreSQL
|
||||
- depends on `db_gen_cpp_headers`
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- `DB_SCHEMA_DIR_TO_GENERATE`
|
||||
|
||||
Output:
|
||||
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-sql-ddl/sqlite/`
|
||||
- `<CPPBESSOT_WORKDIR>/<schema>/generated-sql-ddl/postgre/`
|
||||
|
||||
### `db_gen_migrations`
|
||||
|
||||
Purpose:
|
||||
|
||||
- generates migration SQL artifacts between two schema versions
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `CPPBESSOT_WORKDIR`
|
||||
- `DB_SCHEMA_DIR_MIGRATION_FROM`
|
||||
- `DB_SCHEMA_DIR_MIGRATION_TO`
|
||||
|
||||
Output:
|
||||
|
||||
- `<CPPBESSOT_WORKDIR>/migrations/<from>-<to>/sqlite/`
|
||||
- `<CPPBESSOT_WORKDIR>/migrations/<from>-<to>/postgre/`
|
||||
|
||||
Notes:
|
||||
|
||||
- `from` and `to` must differ
|
||||
- if either migration variable is empty, `db_gen_migrations` is still registered but intentionally fails with guidance
|
||||
|
||||
### `db_gen_orm_serdes_and_zod`
|
||||
|
||||
Purpose:
|
||||
|
||||
- aggregate generation target
|
||||
|
||||
Runs:
|
||||
|
||||
- `db_gen_ts`
|
||||
- `db_gen_zod`
|
||||
- `db_gen_cpp_headers`
|
||||
- `db_gen_odb_logic`
|
||||
- `db_gen_sql_ddl`
|
||||
|
||||
Output:
|
||||
|
||||
- no unique output of its own
|
||||
- produces the union of the five generation targets above
|
||||
|
||||
### `db_createfrom`
|
||||
|
||||
Purpose:
|
||||
|
||||
- recreates a live `dev` or `prod` database from pre-generated SQL DDL artifacts
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `DB_TARGET`
|
||||
- `DB_CREATEFROM_SCHEMA_DIR`
|
||||
- one backend mapping for the selected target
|
||||
|
||||
Behavior:
|
||||
|
||||
- `DB_TARGET=proddev` is illegal and aborts
|
||||
- validates that `DB_CREATEFROM_SCHEMA_DIR` exists and has `openapi/openapi.yaml`
|
||||
- chooses backend by inspecting the selected target mapping
|
||||
- requires non-empty SQL files under:
|
||||
- `<schema>/generated-sql-ddl/sqlite/`, or
|
||||
- `<schema>/generated-sql-ddl/postgre/`
|
||||
- SQLite path:
|
||||
- deletes the current DB file if it exists
|
||||
- recreates parent directories
|
||||
- applies non-empty `.sql` files in sorted order using `sqlite3`
|
||||
- PostgreSQL path:
|
||||
- resets the `public` schema in the target DB
|
||||
- applies non-empty `.sql` files in sorted order using `psql`
|
||||
|
||||
Output:
|
||||
|
||||
- a recreated live database matching the chosen schema snapshot
|
||||
|
||||
### `db_migrate`
|
||||
|
||||
Purpose:
|
||||
|
||||
- applies a generated migration directory, plus optional backfill hooks, to the selected live DB target
|
||||
|
||||
Primary variables:
|
||||
|
||||
- `DB_TARGET`
|
||||
- `DB_MIGRATE_WITH`
|
||||
- `DB_MIGRATE_PRODDEV_USE_STALE`
|
||||
- one backend mapping for the selected target
|
||||
- optional clone hook variable for `proddev`
|
||||
|
||||
Behavior:
|
||||
|
||||
- validates that `<CPPBESSOT_WORKDIR>/migrations/<DB_MIGRATE_WITH>` exists
|
||||
- chooses backend by inspecting the selected target mapping
|
||||
- for `DB_TARGET=proddev`:
|
||||
- if `DB_MIGRATE_PRODDEV_USE_STALE=OFF`, runs the configured clone command first
|
||||
- if `DB_MIGRATE_PRODDEV_USE_STALE=ON`, requires that the stale proddev target already exists
|
||||
- hook order:
|
||||
1. `pre-structural-backfill.sh` if present
|
||||
2. non-empty structural SQL files for the selected backend if present
|
||||
3. `post-structural-backfill.sh` if present
|
||||
- hooks run with these environment variables:
|
||||
- `CPPBESSOT_DB_TARGET`
|
||||
- `CPPBESSOT_DB_BACKEND`
|
||||
- `CPPBESSOT_DB_MIGRATION_DIR`
|
||||
- `CPPBESSOT_DB_MIGRATE_WITH`
|
||||
- `CPPBESSOT_DB_SCHEMA_DIR_TO_GENERATE`
|
||||
- `CPPBESSOT_DB_CREATEFROM_SCHEMA_DIR`
|
||||
- `CPPBESSOT_DB_SQLITE_PATH`
|
||||
- `CPPBESSOT_DB_PGSQL_CONNSTR`
|
||||
|
||||
Output:
|
||||
|
||||
- a migrated live database for the selected target
|
||||
|
||||
## Generated Library Targets
|
||||
|
||||
### `cppbessot_add_generated_cpp_model_libraries()`
|
||||
|
||||
Registers:
|
||||
|
||||
- `cppBeSsotOpenAiModelGen`
|
||||
- alias `cppbessot::openai_model_gen`
|
||||
|
||||
Behavior:
|
||||
|
||||
- depends on `db_gen_cpp_headers`
|
||||
- building a consumer that links `cppbessot::openai_model_gen` forces model generation first
|
||||
|
||||
Consumes generated output from:
|
||||
|
||||
- `<schema>/generated-cpp-source/include/`
|
||||
- `<schema>/generated-cpp-source/src/`
|
||||
|
||||
### `cppbessot_add_generated_odb_libraries()`
|
||||
|
||||
Registers:
|
||||
|
||||
- `cppBeSsotOdbSqlite`
|
||||
- `cppBeSsotOdbPgSql`
|
||||
- aliases:
|
||||
- `cppbessot::odb_sqlite`
|
||||
- `cppbessot::odb_pgsql`
|
||||
|
||||
Behavior:
|
||||
|
||||
- depends on `db_gen_odb_logic`
|
||||
- also depends on `db_gen_sql_ddl` so ORM libs stay aligned with the same schema generation pass
|
||||
- building a consumer that links these libs forces ODB generation first
|
||||
|
||||
Consumes generated output from:
|
||||
|
||||
- `<schema>/generated-cpp-source/include/`
|
||||
- `<schema>/generated-odb-source/sqlite/`
|
||||
- `<schema>/generated-odb-source/postgre/`
|
||||
|
||||
### `cppbessot_add_generated_libraries()`
|
||||
|
||||
Registers all three generated libraries above.
|
||||
|
||||
This is the umbrella library-registration entry point used by `cppbessot_enable()`.
|
||||
|
||||
## Sample Workflows
|
||||
|
||||
### 1. Generate Everything For One Schema
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2
|
||||
|
||||
cmake --build build --target db_gen_orm_serdes_and_zod
|
||||
```
|
||||
|
||||
This produces:
|
||||
|
||||
- `generated-ts-types`
|
||||
- `generated-zod`
|
||||
- `generated-cpp-source`
|
||||
- `generated-odb-source`
|
||||
- `generated-sql-ddl`
|
||||
|
||||
### 2. Generate Migration Artifacts Between Two Schemas
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2 \
|
||||
-DDB_SCHEMA_DIR_MIGRATION_FROM=v1.1 \
|
||||
-DDB_SCHEMA_DIR_MIGRATION_TO=v1.2
|
||||
|
||||
cmake --build build --target db_gen_migrations
|
||||
```
|
||||
|
||||
This writes migration SQL under:
|
||||
|
||||
- `db/migrations/v1.1-v1.2/sqlite/`
|
||||
- `db/migrations/v1.1-v1.2/postgre/`
|
||||
|
||||
### 3. Link Only The Generated C++ Model Library
|
||||
|
||||
```cmake
|
||||
target_link_libraries(my_app PRIVATE cppbessot::openai_model_gen)
|
||||
```
|
||||
|
||||
That is valid if your application wants generated C++ models and JSON serdes but does not want ODB ORM libraries.
|
||||
|
||||
### 4. Link The Full Generated C++ Stack
|
||||
|
||||
```cmake
|
||||
target_link_libraries(my_app PRIVATE
|
||||
cppbessot::openai_model_gen
|
||||
cppbessot::odb_sqlite
|
||||
cppbessot::odb_pgsql
|
||||
)
|
||||
```
|
||||
|
||||
### 5. Create A Fresh SQLite `dev` Database From The Current Schema
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2 \
|
||||
-DDB_TARGET=dev \
|
||||
-DCPPBESSOT_DB_SQLITE_DEV_PATH=/tmp/myapp-dev.sqlite
|
||||
|
||||
cmake --build build --target db_gen_sql_ddl
|
||||
cmake --build build --target db_createfrom
|
||||
```
|
||||
|
||||
This recreates `/tmp/myapp-dev.sqlite` from:
|
||||
|
||||
- `db/v1.2/generated-sql-ddl/sqlite/`
|
||||
|
||||
### 6. Create A Fresh PostgreSQL `prod` Database Schema Snapshot
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2 \
|
||||
-DDB_TARGET=prod \
|
||||
-DCPPBESSOT_DB_PGSQL_PROD_CONNSTR="host=127.0.0.1 port=5432 dbname=myapp_prod user=postgres password=postgres"
|
||||
|
||||
cmake --build build --target db_gen_sql_ddl
|
||||
cmake --build build --target db_createfrom
|
||||
```
|
||||
|
||||
This resets the `public` schema in the target PostgreSQL DB, then reapplies the generated DDL.
|
||||
|
||||
### 7. Migrate A SQLite `dev` Database With Optional Backfills
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2 \
|
||||
-DDB_TARGET=dev \
|
||||
-DDB_MIGRATE_WITH=v1.1-v1.2 \
|
||||
-DCPPBESSOT_DB_SQLITE_DEV_PATH=/tmp/myapp-dev.sqlite
|
||||
|
||||
cmake --build build --target db_migrate
|
||||
```
|
||||
|
||||
If present, these run in order:
|
||||
|
||||
1. `db/migrations/v1.1-v1.2/pre-structural-backfill.sh`
|
||||
2. non-empty SQL files under `db/migrations/v1.1-v1.2/sqlite/`
|
||||
3. `db/migrations/v1.1-v1.2/post-structural-backfill.sh`
|
||||
|
||||
### 8. Migrate A `proddev` Clone From `prod`
|
||||
|
||||
SQLite example:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2 \
|
||||
-DDB_TARGET=proddev \
|
||||
-DDB_MIGRATE_WITH=v1.1-v1.2 \
|
||||
-DCPPBESSOT_DB_SQLITE_PROD_PATH=/srv/myapp/prod.sqlite \
|
||||
-DCPPBESSOT_DB_SQLITE_PRODDEV_PATH=/srv/myapp/proddev.sqlite \
|
||||
-DCPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND='cp /srv/myapp/prod.sqlite /srv/myapp/proddev.sqlite'
|
||||
|
||||
cmake --build build --target db_migrate
|
||||
```
|
||||
|
||||
PostgreSQL example:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2 \
|
||||
-DDB_TARGET=proddev \
|
||||
-DDB_MIGRATE_WITH=v1.1-v1.2 \
|
||||
-DCPPBESSOT_DB_PGSQL_PROD_CONNSTR="host=127.0.0.1 port=5432 dbname=myapp_prod user=postgres password=postgres" \
|
||||
-DCPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR="host=127.0.0.1 port=5432 dbname=myapp_proddev user=postgres password=postgres" \
|
||||
-DCPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND='psql "host=127.0.0.1 port=5432 dbname=postgres user=postgres password=postgres" -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS myapp_proddev;" -c "CREATE DATABASE myapp_proddev TEMPLATE myapp_prod;"'
|
||||
|
||||
cmake --build build --target db_migrate
|
||||
```
|
||||
|
||||
### 9. Reuse An Existing Stale `proddev`
|
||||
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.2 \
|
||||
-DDB_TARGET=proddev \
|
||||
-DDB_MIGRATE_WITH=v1.1-v1.2 \
|
||||
-DDB_MIGRATE_PRODDEV_USE_STALE=ON \
|
||||
-DCPPBESSOT_DB_SQLITE_PRODDEV_PATH=/srv/myapp/proddev.sqlite
|
||||
|
||||
cmake --build build --target db_migrate
|
||||
```
|
||||
|
||||
This skips the clone step and aborts if the stale target does not already exist.
|
||||
|
||||
## Testing
|
||||
|
||||
### Configure The Standalone Repo For Tests
|
||||
|
||||
```bash
|
||||
git -C cmake/cppbessot submodule update --init --recursive tests/googletest
|
||||
|
||||
cmake -S cmake/cppbessot -B build-cppbessot-tests \
|
||||
-DBUILD_TESTING=ON \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=test-schema-v1.2
|
||||
```
|
||||
|
||||
### Run All Registered Tests
|
||||
|
||||
```bash
|
||||
ctest --test-dir build-cppbessot-tests --output-on-failure
|
||||
```
|
||||
|
||||
### ODB Runtime Tests
|
||||
|
||||
Provide test DB connection strings:
|
||||
|
||||
```bash
|
||||
cmake -S cmake/cppbessot -B build-cppbessot-tests \
|
||||
-DBUILD_TESTING=ON \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=test-schema-v1.2 \
|
||||
-DCPPBESSOT_ODB_TEST_SQLITE_CONNSTR=/tmp/cppbessot-odb.sqlite \
|
||||
-DCPPBESSOT_ODB_TEST_PGSQL_CONNSTR="host=127.0.0.1 port=5432 dbname=cppbessot_odb_test user=postgres password=postgres"
|
||||
```
|
||||
|
||||
### Real PostgreSQL DB-Action Tests
|
||||
|
||||
These tests are only registered when all of the following are true:
|
||||
|
||||
- `BUILD_TESTING=ON`
|
||||
- `psql` is available
|
||||
- `CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR` is non-empty
|
||||
- the required target connstr variables for the individual test are non-empty
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
cmake -S cmake/cppbessot -B build-cppbessot-tests \
|
||||
-DBUILD_TESTING=ON \
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=test-schema-v1.2 \
|
||||
-DCPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR="host=127.0.0.1 port=5432 dbname=postgres user=postgres password=postgres" \
|
||||
-DCPPBESSOT_DB_PGSQL_DEV_CONNSTR="host=127.0.0.1 port=5432 dbname=cppbessot_dev user=postgres password=postgres" \
|
||||
-DCPPBESSOT_DB_PGSQL_PROD_CONNSTR="host=127.0.0.1 port=5432 dbname=cppbessot_prod user=postgres password=postgres" \
|
||||
-DCPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR="host=127.0.0.1 port=5432 dbname=cppbessot_proddev user=postgres password=postgres"
|
||||
|
||||
ctest --test-dir build-cppbessot-tests -R 'cppbessot_db_action_pgsql_.*_real' --output-on-failure
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Schema directory names must be basenames, not paths.
|
||||
- `DB_TARGET` must resolve to exactly one backend mapping.
|
||||
- `db_createfrom` and `db_migrate` operate on pre-generated SQL artifacts. If those artifacts are stale, regenerate them first.
|
||||
- The PostgreSQL live-action path resets the `public` schema. Use dedicated databases and be deliberate with `prod` mappings.
|
||||
|
||||
+112
-29
@@ -1,6 +1,5 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(CMakeParseArguments)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbGenerationCommon.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbDependencyCheck.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbSchemaCheck.cmake")
|
||||
@@ -10,6 +9,9 @@ include("${CMAKE_CURRENT_LIST_DIR}/dbGenCpp.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbGenODB.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbGenSqlDDL.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbGenMigrations.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbActionCommon.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbActionCreateFrom.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbActionMigrate.cmake")
|
||||
|
||||
if(NOT DEFINED CPPBESSOT_WORKDIR)
|
||||
set(CPPBESSOT_WORKDIR "db" CACHE STRING "CppBeSSOT schema root folder")
|
||||
@@ -33,6 +35,41 @@ if(NOT DEFINED DB_SCHEMA_CHANGES_ARE_ERROR)
|
||||
option(DB_SCHEMA_CHANGES_ARE_ERROR "Treat dirty schema changes as hard CMake error" OFF)
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED DB_TARGET)
|
||||
set(DB_TARGET "dev" CACHE STRING "Live DB target to act on: prod, proddev, or dev")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED DB_CREATEFROM_SCHEMA_DIR)
|
||||
set(DB_CREATEFROM_SCHEMA_DIR "${DB_SCHEMA_DIR_TO_GENERATE}" CACHE STRING
|
||||
"Schema directory basename under CPPBESSOT_WORKDIR to create a live DB from")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED DB_MIGRATE_WITH)
|
||||
set(DB_MIGRATE_WITH "" CACHE STRING
|
||||
"Migration directory basename under CPPBESSOT_WORKDIR/migrations to apply to the selected DB target")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED DB_MIGRATE_PRODDEV_USE_STALE)
|
||||
option(DB_MIGRATE_PRODDEV_USE_STALE "Reuse an existing stale proddev clone instead of recloning from prod" OFF)
|
||||
endif()
|
||||
|
||||
set(CPPBESSOT_DB_SQLITE_PROD_PATH "${CPPBESSOT_DB_SQLITE_PROD_PATH}" CACHE STRING
|
||||
"Parent-supplied SQLite DB path for DB_TARGET=prod")
|
||||
set(CPPBESSOT_DB_SQLITE_DEV_PATH "${CPPBESSOT_DB_SQLITE_DEV_PATH}" CACHE STRING
|
||||
"Parent-supplied SQLite DB path for DB_TARGET=dev")
|
||||
set(CPPBESSOT_DB_SQLITE_PRODDEV_PATH "${CPPBESSOT_DB_SQLITE_PRODDEV_PATH}" CACHE STRING
|
||||
"Parent-supplied SQLite DB path for DB_TARGET=proddev")
|
||||
set(CPPBESSOT_DB_PGSQL_PROD_CONNSTR "${CPPBESSOT_DB_PGSQL_PROD_CONNSTR}" CACHE STRING
|
||||
"Parent-supplied PostgreSQL connection string for DB_TARGET=prod")
|
||||
set(CPPBESSOT_DB_PGSQL_DEV_CONNSTR "${CPPBESSOT_DB_PGSQL_DEV_CONNSTR}" CACHE STRING
|
||||
"Parent-supplied PostgreSQL connection string for DB_TARGET=dev")
|
||||
set(CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR "${CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR}" CACHE STRING
|
||||
"Parent-supplied PostgreSQL connection string for DB_TARGET=proddev")
|
||||
set(CPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND "${CPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND}" CACHE STRING
|
||||
"Parent-supplied command string that clones the prod SQLite DB into proddev")
|
||||
set(CPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND "${CPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND}" CACHE STRING
|
||||
"Parent-supplied command string that clones the prod PostgreSQL DB into proddev")
|
||||
|
||||
if(NOT DEFINED CPPBESSOT_AUTO_ENABLE)
|
||||
option(CPPBESSOT_AUTO_ENABLE "Auto-register CppBeSSOT targets when this file is included" ON)
|
||||
endif()
|
||||
@@ -48,6 +85,20 @@ function(_cppbessot_try_link_nlohmann target_name)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_assert_generation_targets_registered consumer_name)
|
||||
# Purpose: Ensure split public library registration functions are only used
|
||||
# after generation targets are registered (typically via cppbessot_enable()).
|
||||
# Inputs:
|
||||
# - consumer_name: Human-readable function name for diagnostics.
|
||||
# Outputs:
|
||||
# - No return value; raises FATAL_ERROR when prerequisites are missing.
|
||||
if(NOT TARGET db_gen_cpp_headers OR NOT TARGET db_gen_odb_logic OR NOT TARGET db_gen_sql_ddl)
|
||||
message(FATAL_ERROR
|
||||
"${consumer_name} requires generation targets to be registered first. "
|
||||
"Call cppbessot_enable() before invoking split generated-library registration functions.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_add_generated_model_library cpp_include_dir expected_model_headers expected_model_sources)
|
||||
set_source_files_properties(${expected_model_headers} ${expected_model_sources}
|
||||
PROPERTIES GENERATED TRUE)
|
||||
@@ -111,42 +162,52 @@ function(_cppbessot_add_generated_pgsql_library
|
||||
add_library(cppbessot::odb_pgsql ALIAS cppBeSsotOdbPgSql)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_add_generated_libraries)
|
||||
# Purpose: Create consumable libraries from generated model and ODB sources.
|
||||
function(cppbessot_add_generated_cpp_model_libraries)
|
||||
# Purpose: Create consumable C++ model library from generated model sources.
|
||||
# Inputs:
|
||||
# - SCHEMA_DIR (optional named arg): Schema directory basename to consume.
|
||||
# - DB_SCHEMA_DIR_TO_GENERATE (fallback): Default schema directory basename.
|
||||
# - DB_SCHEMA_DIR_TO_GENERATE: Schema directory basename under CPPBESSOT_WORKDIR.
|
||||
# Outputs:
|
||||
# - Library targets (when sources exist):
|
||||
# - Library target:
|
||||
# - cppBeSsotOpenAiModelGen
|
||||
# - cppBeSsotOdbSqlite
|
||||
# - cppBeSsotOdbPgSql
|
||||
# - Alias targets:
|
||||
# - Alias target:
|
||||
# - cppbessot::openai_model_gen
|
||||
# - cppbessot::odb_sqlite
|
||||
# - cppbessot::odb_pgsql
|
||||
# - Emits warnings if expected source sets are missing.
|
||||
set(options)
|
||||
set(one_value_args SCHEMA_DIR)
|
||||
set(multi_value_args)
|
||||
cmake_parse_arguments(CPPB "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
|
||||
|
||||
if(NOT CPPB_SCHEMA_DIR)
|
||||
set(CPPB_SCHEMA_DIR "${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
endif()
|
||||
|
||||
cppbessot_validate_schema_dir_name("${CPPB_SCHEMA_DIR}")
|
||||
cppbessot_get_schema_dir_path(_version_dir "${CPPB_SCHEMA_DIR}")
|
||||
|
||||
_cppbessot_assert_generation_targets_registered("cppbessot_add_generated_cpp_model_libraries")
|
||||
cppbessot_validate_schema_dir_name("${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
cppbessot_get_schema_dir_path(_version_dir "${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
set(_cpp_include_dir "${_version_dir}/generated-cpp-source/include")
|
||||
set(_model_leaf_include_dir "${_cpp_include_dir}/cppbessot/model")
|
||||
cppbessot_get_expected_cpp_model_outputs(_expected_model_headers _expected_model_sources "${CPPB_SCHEMA_DIR}")
|
||||
cppbessot_get_expected_odb_outputs(_expected_sqlite_odb_sources _expected_pgsql_odb_sources "${CPPB_SCHEMA_DIR}")
|
||||
cppbessot_get_expected_cpp_model_outputs(
|
||||
_expected_model_headers
|
||||
_expected_model_sources
|
||||
"${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
|
||||
_cppbessot_add_generated_model_library(
|
||||
"${_cpp_include_dir}"
|
||||
"${_expected_model_headers}"
|
||||
"${_expected_model_sources}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_add_generated_odb_libraries)
|
||||
# Purpose: Create consumable ODB libraries from generated ODB sources.
|
||||
# Inputs:
|
||||
# - DB_SCHEMA_DIR_TO_GENERATE: Schema directory basename under CPPBESSOT_WORKDIR.
|
||||
# Outputs:
|
||||
# - Library targets:
|
||||
# - cppBeSsotOdbSqlite
|
||||
# - cppBeSsotOdbPgSql
|
||||
# - Alias targets:
|
||||
# - cppbessot::odb_sqlite
|
||||
# - cppbessot::odb_pgsql
|
||||
_cppbessot_assert_generation_targets_registered("cppbessot_add_generated_odb_libraries")
|
||||
cppbessot_validate_schema_dir_name("${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
cppbessot_get_schema_dir_path(_version_dir "${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
|
||||
set(_cpp_include_dir "${_version_dir}/generated-cpp-source/include")
|
||||
set(_model_leaf_include_dir "${_cpp_include_dir}/cppbessot/model")
|
||||
cppbessot_get_expected_odb_outputs(
|
||||
_expected_sqlite_odb_sources
|
||||
_expected_pgsql_odb_sources
|
||||
"${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
|
||||
_cppbessot_add_generated_sqlite_library(
|
||||
"${_cpp_include_dir}"
|
||||
"${_model_leaf_include_dir}"
|
||||
@@ -159,6 +220,23 @@ function(cppbessot_add_generated_libraries)
|
||||
"${_expected_pgsql_odb_sources}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_add_generated_libraries)
|
||||
# Purpose: Create consumable libraries from generated model and ODB sources.
|
||||
# Inputs:
|
||||
# - DB_SCHEMA_DIR_TO_GENERATE: Schema directory basename under CPPBESSOT_WORKDIR.
|
||||
# Outputs:
|
||||
# - Library targets (when sources exist):
|
||||
# - cppBeSsotOpenAiModelGen
|
||||
# - cppBeSsotOdbSqlite
|
||||
# - cppBeSsotOdbPgSql
|
||||
# - Alias targets:
|
||||
# - cppbessot::openai_model_gen
|
||||
# - cppbessot::odb_sqlite
|
||||
# - cppbessot::odb_pgsql
|
||||
cppbessot_add_generated_cpp_model_libraries()
|
||||
cppbessot_add_generated_odb_libraries()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_enable)
|
||||
# Purpose: Entry-point orchestration for dependency checks, custom generation
|
||||
# targets, aggregate targets, and generated library registration.
|
||||
@@ -178,11 +256,14 @@ function(cppbessot_enable)
|
||||
# - db_gen_sql_ddl
|
||||
# - db_gen_migrations
|
||||
# - db_gen_orm_serdes_and_zod
|
||||
# - db_createfrom
|
||||
# - db_migrate
|
||||
# - Generated library targets for selected schema version.
|
||||
cppbessot_initialize_paths()
|
||||
|
||||
cppbessot_validate_schema_dir_name("${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
cppbessot_assert_schema_dir_exists("${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
cppbessot_assert_openapi_exists("${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
|
||||
cppbessot_check_dependencies()
|
||||
|
||||
@@ -206,7 +287,7 @@ function(cppbessot_enable)
|
||||
COMMAND "${CMAKE_COMMAND}" -E echo
|
||||
"Set DB_SCHEMA_DIR_MIGRATION_FROM and DB_SCHEMA_DIR_MIGRATION_TO to enable migration generation."
|
||||
COMMAND "${CMAKE_COMMAND}" -E false
|
||||
VERBATIM
|
||||
VERBATIM
|
||||
)
|
||||
set_target_properties(db_gen_migrations PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
endif()
|
||||
@@ -220,7 +301,9 @@ function(cppbessot_enable)
|
||||
db_gen_sql_ddl)
|
||||
set_target_properties(db_gen_orm_serdes_and_zod PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
|
||||
cppbessot_add_generated_libraries(SCHEMA_DIR "${DB_SCHEMA_DIR_TO_GENERATE}")
|
||||
cppbessot_add_db_createfrom_target()
|
||||
cppbessot_add_db_migrate_target()
|
||||
cppbessot_add_generated_libraries()
|
||||
endfunction()
|
||||
|
||||
if(CPPBESSOT_AUTO_ENABLE)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbGenerationCommon.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbActionShared.cmake")
|
||||
|
||||
function(cppbessot_validate_db_target db_target)
|
||||
_cppbessot_db_action_validate_db_target_impl("${db_target}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_validate_migration_dir_name migration_dir)
|
||||
_cppbessot_db_action_validate_basename(
|
||||
"${migration_dir}"
|
||||
"Migration directory name"
|
||||
"CPPBESSOT_WORKDIR/migrations")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_get_migration_dir_path out_var migration_dir)
|
||||
cppbessot_validate_migration_dir_name("${migration_dir}")
|
||||
cppbessot_abs_path(_workdir "${CPPBESSOT_WORKDIR}")
|
||||
set(${out_var} "${_workdir}/migrations/${migration_dir}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_assert_migration_dir_exists migration_dir)
|
||||
cppbessot_get_migration_dir_path(_migration_dir_path "${migration_dir}")
|
||||
if(NOT IS_DIRECTORY "${_migration_dir_path}")
|
||||
message(FATAL_ERROR "Migration directory does not exist: ${_migration_dir_path}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_db_action_common_cache_args out_var)
|
||||
set(_args
|
||||
"-DCPPBESSOT_PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}"
|
||||
"-DCPPBESSOT_WORKDIR=${CPPBESSOT_WORKDIR}"
|
||||
"-DDB_SCHEMA_DIR_TO_GENERATE=${DB_SCHEMA_DIR_TO_GENERATE}"
|
||||
"-DDB_CREATEFROM_SCHEMA_DIR=${DB_CREATEFROM_SCHEMA_DIR}"
|
||||
"-DDB_TARGET=${DB_TARGET}"
|
||||
"-DCPPBESSOT_DB_SQLITE_PROD_PATH=${CPPBESSOT_DB_SQLITE_PROD_PATH}"
|
||||
"-DCPPBESSOT_DB_SQLITE_DEV_PATH=${CPPBESSOT_DB_SQLITE_DEV_PATH}"
|
||||
"-DCPPBESSOT_DB_SQLITE_PRODDEV_PATH=${CPPBESSOT_DB_SQLITE_PRODDEV_PATH}"
|
||||
"-DCPPBESSOT_DB_PGSQL_PROD_CONNSTR=${CPPBESSOT_DB_PGSQL_PROD_CONNSTR}"
|
||||
"-DCPPBESSOT_DB_PGSQL_DEV_CONNSTR=${CPPBESSOT_DB_PGSQL_DEV_CONNSTR}"
|
||||
"-DCPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR=${CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR}")
|
||||
set(${out_var} "${_args}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_add_db_action_target target_name script_path comment_text)
|
||||
add_custom_target(${target_name}
|
||||
COMMAND "${CMAKE_COMMAND}" ${ARGN} -P "${script_path}"
|
||||
COMMENT "${comment_text}"
|
||||
VERBATIM
|
||||
)
|
||||
set_target_properties(${target_name} PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
endfunction()
|
||||
@@ -0,0 +1,13 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbActionCommon.cmake")
|
||||
set(_CPPBESSOT_DB_ACTION_CREATEFROM_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
function(cppbessot_add_db_createfrom_target)
|
||||
_cppbessot_db_action_common_cache_args(_common_args)
|
||||
_cppbessot_add_db_action_target(
|
||||
db_createfrom
|
||||
"${_CPPBESSOT_DB_ACTION_CREATEFROM_DIR}/scripts/run_db_createfrom.cmake"
|
||||
"Creating live DB target `${DB_TARGET}` from schema `${DB_CREATEFROM_SCHEMA_DIR}`"
|
||||
${_common_args})
|
||||
endfunction()
|
||||
@@ -0,0 +1,17 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dbActionCommon.cmake")
|
||||
set(_CPPBESSOT_DB_ACTION_MIGRATE_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
function(cppbessot_add_db_migrate_target)
|
||||
_cppbessot_db_action_common_cache_args(_common_args)
|
||||
_cppbessot_add_db_action_target(
|
||||
db_migrate
|
||||
"${_CPPBESSOT_DB_ACTION_MIGRATE_DIR}/scripts/run_db_migrate.cmake"
|
||||
"Migrating live DB target `${DB_TARGET}` using migration `${DB_MIGRATE_WITH}`"
|
||||
${_common_args}
|
||||
"-DDB_MIGRATE_WITH=${DB_MIGRATE_WITH}"
|
||||
"-DDB_MIGRATE_PRODDEV_USE_STALE=${DB_MIGRATE_PRODDEV_USE_STALE}"
|
||||
"-DCPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND=${CPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND}"
|
||||
"-DCPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND=${CPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND}")
|
||||
endfunction()
|
||||
@@ -0,0 +1,20 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
function(_cppbessot_db_action_validate_basename value kind relative_root)
|
||||
if("${value}" STREQUAL "")
|
||||
message(FATAL_ERROR "${kind} must not be empty.")
|
||||
endif()
|
||||
|
||||
if("${value}" MATCHES "[/\\\\]")
|
||||
message(FATAL_ERROR
|
||||
"${kind} `${value}` must be a basename under ${relative_root}, not a path.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_db_action_validate_db_target_impl db_target)
|
||||
if(NOT "${db_target}" STREQUAL "prod"
|
||||
AND NOT "${db_target}" STREQUAL "proddev"
|
||||
AND NOT "${db_target}" STREQUAL "dev")
|
||||
message(FATAL_ERROR "DB_TARGET must be one of: prod, proddev, dev.")
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -4,6 +4,24 @@ include("${CMAKE_CURRENT_LIST_DIR}/dbGenerationCommon.cmake")
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
function(_cppbessot_publish_dependency_outputs)
|
||||
set(CPPBESSOT_ODB_EXECUTABLE "${CPPBESSOT_ODB_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_NPX_EXECUTABLE "${CPPBESSOT_NPX_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_NPM_EXECUTABLE "${CPPBESSOT_NPM_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_JAVA_EXECUTABLE "${CPPBESSOT_JAVA_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_GIT_EXECUTABLE "${CPPBESSOT_GIT_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_SQLITE3_EXECUTABLE "${CPPBESSOT_SQLITE3_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_PSQL_EXECUTABLE "${CPPBESSOT_PSQL_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_ODB_RUNTIME_LIB "${CPPBESSOT_ODB_RUNTIME_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_ODB_SQLITE_RUNTIME_LIB "${CPPBESSOT_ODB_SQLITE_RUNTIME_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_ODB_PGSQL_RUNTIME_LIB "${CPPBESSOT_ODB_PGSQL_RUNTIME_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_SQLITE_INCLUDE_DIR "${CPPBESSOT_SQLITE_INCLUDE_DIR}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_PGSQL_INCLUDE_DIR "${CPPBESSOT_PGSQL_INCLUDE_DIR}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_SQLITE_CLIENT_LIB "${CPPBESSOT_SQLITE_CLIENT_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_PGSQL_CLIENT_LIB "${CPPBESSOT_PGSQL_CLIENT_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_OPENAPI_ZOD_AVAILABLE TRUE PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_require_program var_name program_name hint)
|
||||
# Purpose: Locate an executable and fail with a clear install hint if missing.
|
||||
# Inputs:
|
||||
@@ -20,6 +38,17 @@ function(_cppbessot_require_program var_name program_name hint)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_any_nonempty_var out_var)
|
||||
foreach(_var_name IN LISTS ARGN)
|
||||
if(DEFINED ${_var_name} AND NOT "${${_var_name}}" STREQUAL "")
|
||||
set(${out_var} TRUE PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${out_var} FALSE PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(_cppbessot_require_npm_package npm_executable package_name)
|
||||
# Purpose: Ensure an npm package exists either locally in PROJECT_SOURCE_DIR
|
||||
# or globally in the active npm installation.
|
||||
@@ -64,6 +93,7 @@ function(_cppbessot_require_npx_package_executable npx_executable package_execut
|
||||
# - No return value; raises FATAL_ERROR if execution fails.
|
||||
execute_process(
|
||||
COMMAND "${npx_executable}" --no-install "${package_executable}" --help
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE _help_result
|
||||
OUTPUT_QUIET
|
||||
ERROR_VARIABLE _help_stderr
|
||||
@@ -76,6 +106,7 @@ function(_cppbessot_require_npx_package_executable npx_executable package_execut
|
||||
# Some CLIs return non-zero for --help; verify with version as fallback.
|
||||
execute_process(
|
||||
COMMAND "${npx_executable}" --no-install "${package_executable}" version
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE _version_result
|
||||
OUTPUT_QUIET
|
||||
ERROR_VARIABLE _version_stderr
|
||||
@@ -101,6 +132,12 @@ function(cppbessot_check_dependencies)
|
||||
# - CPPBESSOT_GIT_EXECUTABLE (PARENT_SCOPE)
|
||||
# - CPPBESSOT_OPENAPI_ZOD_AVAILABLE (PARENT_SCOPE)
|
||||
# - No return value; raises FATAL_ERROR on missing dependencies.
|
||||
get_property(_cppbessot_dependencies_checked GLOBAL PROPERTY CPPBESSOT_DEPENDENCIES_CHECKED)
|
||||
if(_cppbessot_dependencies_checked)
|
||||
_cppbessot_publish_dependency_outputs()
|
||||
return()
|
||||
endif()
|
||||
|
||||
_cppbessot_require_program(CPPBESSOT_ODB_EXECUTABLE odb
|
||||
"Install ODB compiler and ensure `odb` is in PATH.")
|
||||
_cppbessot_require_program(CPPBESSOT_NPX_EXECUTABLE npx
|
||||
@@ -111,6 +148,20 @@ function(cppbessot_check_dependencies)
|
||||
"Install a Java runtime (OpenAPI generator uses Java).")
|
||||
_cppbessot_require_program(CPPBESSOT_GIT_EXECUTABLE git
|
||||
"Install Git and ensure it is available in PATH.")
|
||||
_cppbessot_require_program(CPPBESSOT_SQLITE3_EXECUTABLE sqlite3
|
||||
"Install the SQLite CLI so live SQLite DB actions can apply SQL files.")
|
||||
_cppbessot_any_nonempty_var(
|
||||
_cppbessot_require_psql
|
||||
CPPBESSOT_DB_PGSQL_PROD_CONNSTR
|
||||
CPPBESSOT_DB_PGSQL_DEV_CONNSTR
|
||||
CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR
|
||||
CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR)
|
||||
if(_cppbessot_require_psql)
|
||||
_cppbessot_require_program(CPPBESSOT_PSQL_EXECUTABLE psql
|
||||
"Install the PostgreSQL client CLI so live PostgreSQL DB actions can run.")
|
||||
else()
|
||||
set(CPPBESSOT_PSQL_EXECUTABLE "")
|
||||
endif()
|
||||
|
||||
_cppbessot_require_npm_package("${CPPBESSOT_NPM_EXECUTABLE}" "@openapitools/openapi-generator-cli")
|
||||
_cppbessot_require_npx_package_executable("${CPPBESSOT_NPX_EXECUTABLE}" "@openapitools/openapi-generator-cli")
|
||||
@@ -187,17 +238,6 @@ function(cppbessot_check_dependencies)
|
||||
"PostgreSQL client library was not found. On Ubuntu/Debian install package `libpq-dev`.")
|
||||
endif()
|
||||
|
||||
set(CPPBESSOT_ODB_EXECUTABLE "${CPPBESSOT_ODB_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_NPX_EXECUTABLE "${CPPBESSOT_NPX_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_NPM_EXECUTABLE "${CPPBESSOT_NPM_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_JAVA_EXECUTABLE "${CPPBESSOT_JAVA_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_GIT_EXECUTABLE "${CPPBESSOT_GIT_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_ODB_RUNTIME_LIB "${CPPBESSOT_ODB_RUNTIME_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_ODB_SQLITE_RUNTIME_LIB "${CPPBESSOT_ODB_SQLITE_RUNTIME_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_ODB_PGSQL_RUNTIME_LIB "${CPPBESSOT_ODB_PGSQL_RUNTIME_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_SQLITE_INCLUDE_DIR "${CPPBESSOT_SQLITE_INCLUDE_DIR}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_PGSQL_INCLUDE_DIR "${CPPBESSOT_PGSQL_INCLUDE_DIR}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_SQLITE_CLIENT_LIB "${CPPBESSOT_SQLITE_CLIENT_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_PGSQL_CLIENT_LIB "${CPPBESSOT_PGSQL_CLIENT_LIB}" PARENT_SCOPE)
|
||||
set(CPPBESSOT_OPENAPI_ZOD_AVAILABLE TRUE PARENT_SCOPE)
|
||||
set_property(GLOBAL PROPERTY CPPBESSOT_DEPENDENCIES_CHECKED TRUE)
|
||||
_cppbessot_publish_dependency_outputs()
|
||||
endfunction()
|
||||
|
||||
@@ -92,6 +92,19 @@ function(cppbessot_assert_schema_dir_exists schema_dir)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_assert_openapi_exists schema_dir)
|
||||
# Purpose: Assert that a schema directory's SSOT OpenAPI file exists on disk.
|
||||
# Inputs:
|
||||
# - schema_dir: Schema directory basename.
|
||||
# Outputs:
|
||||
# - No return value; raises FATAL_ERROR if file is missing.
|
||||
cppbessot_get_schema_dir_path(_schema_dir_path "${schema_dir}")
|
||||
set(_openapi_file "${_schema_dir_path}/openapi/openapi.yaml")
|
||||
if(NOT EXISTS "${_openapi_file}")
|
||||
message(FATAL_ERROR "OpenAPI file does not exist: ${_openapi_file}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_get_model_headers_glob out_var schema_dir)
|
||||
# Purpose: Build a model-header glob expression for a schema directory.
|
||||
# Inputs:
|
||||
@@ -111,10 +124,8 @@ function(cppbessot_get_openapi_schema_names out_var schema_dir)
|
||||
# Outputs:
|
||||
# - <out_var> (PARENT_SCOPE): List of top-level component schema names.
|
||||
cppbessot_get_schema_dir_path(_schema_dir_path "${schema_dir}")
|
||||
cppbessot_assert_openapi_exists("${schema_dir}")
|
||||
set(_openapi_file "${_schema_dir_path}/openapi/openapi.yaml")
|
||||
if(NOT EXISTS "${_openapi_file}")
|
||||
message(FATAL_ERROR "OpenAPI file does not exist: ${_openapi_file}")
|
||||
endif()
|
||||
|
||||
file(STRINGS "${_openapi_file}" _openapi_lines)
|
||||
set(_schema_names)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
function(cppbessot_db_action_get_hook_path out_var migration_dir hook_name)
|
||||
set(_hook_path "${migration_dir}/${hook_name}")
|
||||
if(EXISTS "${_hook_path}")
|
||||
set(${out_var} "${_hook_path}" PARENT_SCOPE)
|
||||
else()
|
||||
set(${out_var} "" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_run_hook
|
||||
hook_path
|
||||
db_target
|
||||
backend
|
||||
migration_dir
|
||||
migrate_with
|
||||
schema_dir_to_generate
|
||||
createfrom_schema_dir
|
||||
sqlite_path
|
||||
pgsql_connstr)
|
||||
if("${hook_path}" STREQUAL "")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(_env_args
|
||||
"CPPBESSOT_DB_TARGET=${db_target}"
|
||||
"CPPBESSOT_DB_BACKEND=${backend}"
|
||||
"CPPBESSOT_DB_MIGRATION_DIR=${migration_dir}"
|
||||
"CPPBESSOT_DB_MIGRATE_WITH=${migrate_with}"
|
||||
"CPPBESSOT_DB_SCHEMA_DIR_TO_GENERATE=${schema_dir_to_generate}"
|
||||
"CPPBESSOT_DB_CREATEFROM_SCHEMA_DIR=${createfrom_schema_dir}"
|
||||
"CPPBESSOT_DB_SQLITE_PATH=${sqlite_path}"
|
||||
"CPPBESSOT_DB_PGSQL_CONNSTR=${pgsql_connstr}")
|
||||
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" -E env ${_env_args} sh "${hook_path}"
|
||||
WORKING_DIRECTORY "${migration_dir}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Backfill hook failed: ${hook_path}\n${_stdout}\n${_stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,77 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
function(cppbessot_db_action_target_exists out_var backend sqlite_path pgsql_connstr)
|
||||
if("${backend}" STREQUAL "sqlite")
|
||||
if(EXISTS "${sqlite_path}")
|
||||
set(${out_var} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out_var} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
cppbessot_db_action_find_program_or_fail(_psql psql
|
||||
"PostgreSQL live actions require `psql` to be available in PATH.")
|
||||
execute_process(
|
||||
COMMAND "${_psql}" "${pgsql_connstr}" -v ON_ERROR_STOP=1 -c "SELECT 1;"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_QUIET
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(_result EQUAL 0)
|
||||
set(${out_var} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out_var} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_get_clone_command out_var backend)
|
||||
if("${backend}" STREQUAL "sqlite")
|
||||
set(_command "${CPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND}")
|
||||
else()
|
||||
set(_command "${CPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND}")
|
||||
endif()
|
||||
set(${out_var} "${_command}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_invoke_clone_hook backend)
|
||||
cppbessot_db_action_get_clone_command(_clone_command "${backend}")
|
||||
if("${_clone_command}" STREQUAL "")
|
||||
message(FATAL_ERROR
|
||||
"No clone command is configured for backend `${backend}` while preparing proddev.")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND sh -c "${_clone_command}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"Proddev clone command failed for backend `${backend}`.\n${_stdout}\n${_stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_prepare_proddev target backend use_stale sqlite_path pgsql_connstr)
|
||||
if(NOT "${target}" STREQUAL "proddev")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(use_stale)
|
||||
cppbessot_db_action_target_exists(_exists "${backend}" "${sqlite_path}" "${pgsql_connstr}")
|
||||
if(NOT _exists)
|
||||
message(FATAL_ERROR
|
||||
"DB_MIGRATE_PRODDEV_USE_STALE is ON, but no current stale proddev target exists.")
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
cppbessot_db_action_invoke_clone_hook("${backend}")
|
||||
cppbessot_db_action_target_exists(_exists "${backend}" "${sqlite_path}" "${pgsql_connstr}")
|
||||
if(NOT _exists)
|
||||
message(FATAL_ERROR
|
||||
"Proddev clone command completed, but the proddev target still does not appear to exist.")
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -0,0 +1,120 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../dbActionShared.cmake")
|
||||
|
||||
function(cppbessot_db_action_require_var var_name)
|
||||
if(NOT DEFINED ${var_name} OR "${${var_name}}" STREQUAL "")
|
||||
message(FATAL_ERROR "Required variable `${var_name}` is missing.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_resolve_project_path out_var input_path)
|
||||
cppbessot_db_action_require_var(CPPBESSOT_PROJECT_SOURCE_DIR)
|
||||
if(IS_ABSOLUTE "${input_path}")
|
||||
set(_resolved "${input_path}")
|
||||
else()
|
||||
set(_resolved "${CPPBESSOT_PROJECT_SOURCE_DIR}/${input_path}")
|
||||
endif()
|
||||
|
||||
get_filename_component(_resolved "${_resolved}" ABSOLUTE)
|
||||
set(${out_var} "${_resolved}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_validate_schema_dir_name schema_dir)
|
||||
_cppbessot_db_action_validate_basename(
|
||||
"${schema_dir}"
|
||||
"Schema directory name"
|
||||
"CPPBESSOT_WORKDIR")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_get_schema_dir_path out_var schema_dir)
|
||||
cppbessot_db_action_validate_schema_dir_name("${schema_dir}")
|
||||
cppbessot_db_action_resolve_project_path(_workdir "${CPPBESSOT_WORKDIR}")
|
||||
set(${out_var} "${_workdir}/${schema_dir}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_assert_schema_dir_ready schema_dir)
|
||||
cppbessot_db_action_get_schema_dir_path(_schema_dir_path "${schema_dir}")
|
||||
if(NOT IS_DIRECTORY "${_schema_dir_path}")
|
||||
message(FATAL_ERROR "Schema directory does not exist: ${_schema_dir_path}")
|
||||
endif()
|
||||
|
||||
set(_openapi_file "${_schema_dir_path}/openapi/openapi.yaml")
|
||||
if(NOT EXISTS "${_openapi_file}")
|
||||
message(FATAL_ERROR "OpenAPI file does not exist: ${_openapi_file}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_validate_migration_dir_name migration_dir)
|
||||
_cppbessot_db_action_validate_basename(
|
||||
"${migration_dir}"
|
||||
"Migration directory name"
|
||||
"CPPBESSOT_WORKDIR/migrations")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_get_migration_dir_path out_var migration_dir)
|
||||
cppbessot_db_action_validate_migration_dir_name("${migration_dir}")
|
||||
cppbessot_db_action_resolve_project_path(_workdir "${CPPBESSOT_WORKDIR}")
|
||||
set(${out_var} "${_workdir}/migrations/${migration_dir}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_assert_migration_dir_exists migration_dir)
|
||||
cppbessot_db_action_get_migration_dir_path(_migration_dir_path "${migration_dir}")
|
||||
if(NOT IS_DIRECTORY "${_migration_dir_path}")
|
||||
message(FATAL_ERROR "Migration directory does not exist: ${_migration_dir_path}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_validate_db_target db_target)
|
||||
_cppbessot_db_action_validate_db_target_impl("${db_target}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_backend_subdir out_var backend)
|
||||
if("${backend}" STREQUAL "sqlite")
|
||||
set(${out_var} "sqlite" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if("${backend}" STREQUAL "postgre")
|
||||
set(${out_var} "postgre" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
message(FATAL_ERROR "Unsupported backend `${backend}`.")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_collect_nonempty_sql_files out_var sql_dir)
|
||||
if(NOT IS_DIRECTORY "${sql_dir}")
|
||||
set(${out_var} "" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
file(GLOB _candidate_files "${sql_dir}/*.sql")
|
||||
list(SORT _candidate_files)
|
||||
|
||||
set(_sql_files)
|
||||
foreach(_candidate IN LISTS _candidate_files)
|
||||
file(READ "${_candidate}" _contents)
|
||||
string(STRIP "${_contents}" _trimmed)
|
||||
if(NOT "${_trimmed}" STREQUAL "")
|
||||
list(APPEND _sql_files "${_candidate}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${out_var} "${_sql_files}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_require_nonempty_sql_dir sql_dir failure_prefix)
|
||||
cppbessot_db_action_collect_nonempty_sql_files(_sql_files "${sql_dir}")
|
||||
if(NOT _sql_files)
|
||||
message(FATAL_ERROR "${failure_prefix}: no non-empty SQL files found under ${sql_dir}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_find_program_or_fail out_var program_name hint)
|
||||
find_program(_program "${program_name}")
|
||||
if(NOT _program)
|
||||
message(FATAL_ERROR "Missing required program `${program_name}`. ${hint}")
|
||||
endif()
|
||||
set(${out_var} "${_program}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -0,0 +1,78 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
function(cppbessot_db_action_reset_sqlite_db sqlite_path)
|
||||
if(EXISTS "${sqlite_path}")
|
||||
file(REMOVE "${sqlite_path}")
|
||||
endif()
|
||||
|
||||
get_filename_component(_sqlite_parent "${sqlite_path}" DIRECTORY)
|
||||
if(NOT "${_sqlite_parent}" STREQUAL "")
|
||||
file(MAKE_DIRECTORY "${_sqlite_parent}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_apply_sqlite_files sqlite_path)
|
||||
set(_sql_files "${ARGN}")
|
||||
if(NOT _sql_files)
|
||||
return()
|
||||
endif()
|
||||
|
||||
cppbessot_db_action_find_program_or_fail(_sqlite3 sqlite3
|
||||
"SQLite live actions require the `sqlite3` CLI to be available in PATH.")
|
||||
|
||||
foreach(_sql_file IN LISTS _sql_files)
|
||||
string(REPLACE "\"" "\\\"" _sqlite_read_file "${_sql_file}")
|
||||
execute_process(
|
||||
COMMAND "${_sqlite3}" "${sqlite_path}" ".read \"${_sqlite_read_file}\""
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"SQLite SQL apply failed for `${_sql_file}` against `${sqlite_path}`.\n${_stdout}\n${_stderr}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_reset_pgsql_schema pgsql_connstr)
|
||||
cppbessot_db_action_find_program_or_fail(_psql psql
|
||||
"PostgreSQL live actions require `psql` to be available in PATH.")
|
||||
execute_process(
|
||||
COMMAND "${_psql}" "${pgsql_connstr}" -v ON_ERROR_STOP=1
|
||||
-c "DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"PostgreSQL schema reset failed.\n${_stdout}\n${_stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_apply_pgsql_files pgsql_connstr)
|
||||
set(_sql_files "${ARGN}")
|
||||
if(NOT _sql_files)
|
||||
return()
|
||||
endif()
|
||||
|
||||
cppbessot_db_action_find_program_or_fail(_psql psql
|
||||
"PostgreSQL live actions require `psql` to be available in PATH.")
|
||||
|
||||
foreach(_sql_file IN LISTS _sql_files)
|
||||
execute_process(
|
||||
COMMAND "${_psql}" "${pgsql_connstr}" -v ON_ERROR_STOP=1 -f "${_sql_file}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR
|
||||
"PostgreSQL SQL apply failed for `${_sql_file}`.\n${_stdout}\n${_stderr}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
@@ -0,0 +1,50 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
function(cppbessot_db_action_target_upper out_var db_target)
|
||||
string(TOUPPER "${db_target}" _upper)
|
||||
set(${out_var} "${_upper}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_db_action_resolve_backend_for_target
|
||||
out_backend
|
||||
out_sqlite_path
|
||||
out_pgsql_connstr
|
||||
db_target)
|
||||
cppbessot_db_action_validate_db_target("${db_target}")
|
||||
cppbessot_db_action_target_upper(_target_upper "${db_target}")
|
||||
|
||||
set(_sqlite_var "CPPBESSOT_DB_SQLITE_${_target_upper}_PATH")
|
||||
set(_pgsql_var "CPPBESSOT_DB_PGSQL_${_target_upper}_CONNSTR")
|
||||
|
||||
set(_has_sqlite FALSE)
|
||||
set(_has_pgsql FALSE)
|
||||
|
||||
if(DEFINED ${_sqlite_var} AND NOT "${${_sqlite_var}}" STREQUAL "")
|
||||
set(_has_sqlite TRUE)
|
||||
endif()
|
||||
|
||||
if(DEFINED ${_pgsql_var} AND NOT "${${_pgsql_var}}" STREQUAL "")
|
||||
set(_has_pgsql TRUE)
|
||||
endif()
|
||||
|
||||
if(_has_sqlite AND _has_pgsql)
|
||||
message(FATAL_ERROR
|
||||
"DB target `${db_target}` is ambiguous: both `${_sqlite_var}` and `${_pgsql_var}` are set.")
|
||||
endif()
|
||||
|
||||
if(NOT _has_sqlite AND NOT _has_pgsql)
|
||||
message(FATAL_ERROR
|
||||
"DB target `${db_target}` is not mapped: set exactly one of `${_sqlite_var}` or `${_pgsql_var}`.")
|
||||
endif()
|
||||
|
||||
if(_has_sqlite)
|
||||
set(${out_backend} "sqlite" PARENT_SCOPE)
|
||||
set(${out_sqlite_path} "${${_sqlite_var}}" PARENT_SCOPE)
|
||||
set(${out_pgsql_connstr} "" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(${out_backend} "postgre" PARENT_SCOPE)
|
||||
set(${out_sqlite_path} "" PARENT_SCOPE)
|
||||
set(${out_pgsql_connstr} "${${_pgsql_var}}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -0,0 +1,38 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionCommon.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionTargetResolution.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionSqlApply.cmake")
|
||||
|
||||
cppbessot_db_action_require_var(CPPBESSOT_PROJECT_SOURCE_DIR)
|
||||
cppbessot_db_action_require_var(CPPBESSOT_WORKDIR)
|
||||
cppbessot_db_action_require_var(DB_CREATEFROM_SCHEMA_DIR)
|
||||
cppbessot_db_action_require_var(DB_TARGET)
|
||||
|
||||
cppbessot_db_action_validate_db_target("${DB_TARGET}")
|
||||
if("${DB_TARGET}" STREQUAL "proddev")
|
||||
message(FATAL_ERROR "db_createfrom does not support DB_TARGET=proddev.")
|
||||
endif()
|
||||
|
||||
cppbessot_db_action_assert_schema_dir_ready("${DB_CREATEFROM_SCHEMA_DIR}")
|
||||
cppbessot_db_action_resolve_backend_for_target(
|
||||
_backend
|
||||
_sqlite_path
|
||||
_pgsql_connstr
|
||||
"${DB_TARGET}")
|
||||
cppbessot_db_action_backend_subdir(_backend_subdir "${_backend}")
|
||||
cppbessot_db_action_get_schema_dir_path(_schema_dir "${DB_CREATEFROM_SCHEMA_DIR}")
|
||||
set(_ddl_dir "${_schema_dir}/generated-sql-ddl/${_backend_subdir}")
|
||||
cppbessot_db_action_require_nonempty_sql_dir(
|
||||
"${_ddl_dir}"
|
||||
"db_createfrom cannot continue")
|
||||
cppbessot_db_action_collect_nonempty_sql_files(_sql_files "${_ddl_dir}")
|
||||
|
||||
if("${_backend}" STREQUAL "sqlite")
|
||||
cppbessot_db_action_reset_sqlite_db("${_sqlite_path}")
|
||||
cppbessot_db_action_apply_sqlite_files("${_sqlite_path}" ${_sql_files})
|
||||
return()
|
||||
endif()
|
||||
|
||||
cppbessot_db_action_reset_pgsql_schema("${_pgsql_connstr}")
|
||||
cppbessot_db_action_apply_pgsql_files("${_pgsql_connstr}" ${_sql_files})
|
||||
@@ -0,0 +1,64 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionCommon.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionTargetResolution.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionBackfill.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionSqlApply.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cppbessotDbActionClone.cmake")
|
||||
|
||||
cppbessot_db_action_require_var(CPPBESSOT_PROJECT_SOURCE_DIR)
|
||||
cppbessot_db_action_require_var(CPPBESSOT_WORKDIR)
|
||||
cppbessot_db_action_require_var(DB_MIGRATE_WITH)
|
||||
cppbessot_db_action_require_var(DB_TARGET)
|
||||
cppbessot_db_action_require_var(DB_SCHEMA_DIR_TO_GENERATE)
|
||||
cppbessot_db_action_require_var(DB_CREATEFROM_SCHEMA_DIR)
|
||||
|
||||
cppbessot_db_action_validate_db_target("${DB_TARGET}")
|
||||
cppbessot_db_action_assert_migration_dir_exists("${DB_MIGRATE_WITH}")
|
||||
cppbessot_db_action_resolve_backend_for_target(
|
||||
_backend
|
||||
_sqlite_path
|
||||
_pgsql_connstr
|
||||
"${DB_TARGET}")
|
||||
cppbessot_db_action_prepare_proddev(
|
||||
"${DB_TARGET}"
|
||||
"${_backend}"
|
||||
"${DB_MIGRATE_PRODDEV_USE_STALE}"
|
||||
"${_sqlite_path}"
|
||||
"${_pgsql_connstr}")
|
||||
cppbessot_db_action_backend_subdir(_backend_subdir "${_backend}")
|
||||
cppbessot_db_action_get_migration_dir_path(_migration_dir "${DB_MIGRATE_WITH}")
|
||||
set(_sql_dir "${_migration_dir}/${_backend_subdir}")
|
||||
cppbessot_db_action_collect_nonempty_sql_files(_sql_files "${_sql_dir}")
|
||||
cppbessot_db_action_get_hook_path(_pre_hook "${_migration_dir}" "pre-structural-backfill.sh")
|
||||
cppbessot_db_action_get_hook_path(_post_hook "${_migration_dir}" "post-structural-backfill.sh")
|
||||
|
||||
cppbessot_db_action_run_hook(
|
||||
"${_pre_hook}"
|
||||
"${DB_TARGET}"
|
||||
"${_backend}"
|
||||
"${_migration_dir}"
|
||||
"${DB_MIGRATE_WITH}"
|
||||
"${DB_SCHEMA_DIR_TO_GENERATE}"
|
||||
"${DB_CREATEFROM_SCHEMA_DIR}"
|
||||
"${_sqlite_path}"
|
||||
"${_pgsql_connstr}")
|
||||
|
||||
if(_sql_files)
|
||||
if("${_backend}" STREQUAL "sqlite")
|
||||
cppbessot_db_action_apply_sqlite_files("${_sqlite_path}" ${_sql_files})
|
||||
else()
|
||||
cppbessot_db_action_apply_pgsql_files("${_pgsql_connstr}" ${_sql_files})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
cppbessot_db_action_run_hook(
|
||||
"${_post_hook}"
|
||||
"${DB_TARGET}"
|
||||
"${_backend}"
|
||||
"${_migration_dir}"
|
||||
"${DB_MIGRATE_WITH}"
|
||||
"${DB_SCHEMA_DIR_TO_GENERATE}"
|
||||
"${DB_CREATEFROM_SCHEMA_DIR}"
|
||||
"${_sqlite_path}"
|
||||
"${_pgsql_connstr}")
|
||||
@@ -0,0 +1,23 @@
|
||||
Ok. Now we need to add the concept of migration action targets. Add new vars: these control the global migration action target. These don't determine the generated source oas, or the target dir to generate that oas into. Rather, they determine the target DB to apply the current DB_SCHEMA_DIR_TO_GENERATE schema against.
|
||||
|
||||
The new var name is: DB_TARGET=<prod|proddev|dev>.
|
||||
It's expected by the parent source dir that DB_TARGET either already is already migrated to DB_SCHEMA_DIR_TO_GENERATE; or DB_TARGET will be migrated to DB_SCHEMA_DIR_TO_GENERATE before the application is compiled and executed. It is in fact the parent application that sets DB_SCHEMA_DIR_TO_GENERATE, after all.
|
||||
|
||||
Prod and dev are self-sufficient targets. Migration actions applied to them are applied to them qua independent, persistent db.
|
||||
* Action: target db_createfrom. Accepts option DB_CREATEFROM_SCHEMA_DIR=<schema dir>. Respects and uses CPPBESSOT_WORKDIR. Will use the pre-generated sql_ddl artifacts in <schema dir> to idempotently create a new db with the name prod or dev, depending on the value of DB_TARGET. Default ON_TARGET=dev. Default value of DB_CREATEFROM_SCHEMA_DIR is DB_SCHEMA_DIR_TO_GENERATE. Obv a non-existent DB_CREATEFROM_SCHEMA_DIR or one whose oas spec doesn't exist, will result in an abort.
|
||||
|
||||
Proddev is copied from prod. It is a sandbox target used to apply migrations against a clone of prod.
|
||||
* Proddev does not support the db_createfrom target. It is always cloned from prod.
|
||||
* Action: target db_migrate with DB_TARGET=proddev will always automatically and idempotently clone prod to a copy called "proddev", unless the DB_MIGRATE_PRODDEV_USE_STALE=ON flag is set. This flag simply means "Don't clone. Re-run the current target action against the most recent, stale cloned proddev, which is itself a prior clone of prod". Specifying DB_TARGET=proddev and running db_createfrom is illegal and should abort. Speciying DB_MIGRATE_PRODDEV_USE_STALE=ON and running target db_migrate when no current stale prod DB exists results in an abort.
|
||||
|
||||
Action: Target db_migrate: Option DB_TARGET=<prod|proddev|dev>. Option DB_MIGRATE_WITH=<migration from-to-dir>. If the <migration from-to-dir> doesn't exist, this aborts.
|
||||
|
||||
Each DB migration from-to-dir may have two shell scripts: pre-structural-backfill.sh and post-structural-backfill.sh. The db_migrate target should check to see if these scripts exist, and if the pre-structural-backfill.sh script exists, it should be executed before the structural migrations, if any. If the structural migrations generated by the db_gen_migrations have content, then they should be applied next. Then if the post-structural-backfill.sh script exists, it should be run last.
|
||||
|
||||
Think of as many tests as you can to give coverage of all of these new commands as well as the sqlite and pgsql variants' behaviour and write them first before implementing this plan.
|
||||
|
||||
Be very aggressive about splitting and isolating code into common subfunctions and modules, and reusing wherever possible. Don't duplicate code. No long-scrolling functions.
|
||||
|
||||
----
|
||||
That sqlite_apply.py script: can it be done as a bash script instead?
|
||||
I'd prefer to add the sqlite3 cli to the project's CMake find_* targets, than to add python unnecessarily.
|
||||
@@ -0,0 +1,12 @@
|
||||
Ok. Let's keep the cppbessot_add_generated_libraries target, but make it actually depend on two new targets:
|
||||
cppbessot_add_generated_cpp_model_libraries and cppbessot_add_generated_odb_libraries.
|
||||
|
||||
cppbessot_add_generated_odb_libraries adds the two cppbessot::odb_* lib targets.
|
||||
|
||||
The cppbessot_add_generated_cpp_model_libraries target should depend on the db_gen_cpp_headers target, and so a program that links against cppbessot::openai_model_gen should build the db_gen_cpp_headers target for the current DB_SCHEMA_DIR_TO_GENERATE first, during the build stage.
|
||||
|
||||
Similarly the two cppbessot::odb_* library targets should depend on the db_gen_odb_logic target, and a program that depends on that cppbessot::odb_* targets should automatically build db_gen_odb_logic target for the current DB_SCHEMA_DIR_TO_GENERATE first, at build time.
|
||||
|
||||
Eradicate SCHEMA_DIR completely, and leave behind only DB_SCHEMA_DIR_TO_GENERATE.
|
||||
|
||||
cmake configure step should fail if DB_SCHEMA_DIR_TO_GENERATE doesn't exist, or if the openapi model which it uses as its SSOT doesn't exist.
|
||||
@@ -6,3 +6,4 @@ endif()
|
||||
add_subdirectory(googletest)
|
||||
add_subdirectory(cpp-serdes)
|
||||
add_subdirectory(odb-orm)
|
||||
add_subdirectory(db-actions)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
function(_cppbessot_db_action_test_common_args out_var test_name script_name)
|
||||
set(_args
|
||||
"-DCPPBESSOT_TEST_NAME=${test_name}"
|
||||
"-DCPPBESSOT_TEST_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"-DCPPBESSOT_TEST_MODULE_SOURCE_DIR=${PROJECT_SOURCE_DIR}"
|
||||
"-DCPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR=${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}"
|
||||
"-DCPPBESSOT_DB_PGSQL_DEV_CONNSTR=${CPPBESSOT_DB_PGSQL_DEV_CONNSTR}"
|
||||
"-DCPPBESSOT_DB_PGSQL_PROD_CONNSTR=${CPPBESSOT_DB_PGSQL_PROD_CONNSTR}"
|
||||
"-DCPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR=${CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR}"
|
||||
"-DCPPBESSOT_DB_ACTION_TEST_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/scripts/${script_name}")
|
||||
set(${out_var} "${_args}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_add_db_action_test test_name script_name)
|
||||
_cppbessot_db_action_test_common_args(_test_args "${test_name}" "${script_name}")
|
||||
add_test(
|
||||
NAME ${test_name}
|
||||
COMMAND "${CMAKE_COMMAND}" ${_test_args} -P "${CMAKE_CURRENT_SOURCE_DIR}/scripts/${script_name}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_add_real_pgsql_db_action_test test_name script_name)
|
||||
find_program(_psql psql)
|
||||
if(NOT _psql)
|
||||
message(STATUS "Skipping real PostgreSQL db-action test `${test_name}` because `psql` is not available.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if("${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}" STREQUAL "")
|
||||
message(STATUS "Skipping real PostgreSQL db-action test `${test_name}` because CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR is empty.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
foreach(_required_var IN LISTS ARGN)
|
||||
if("${${_required_var}}" STREQUAL "")
|
||||
message(STATUS "Skipping real PostgreSQL db-action test `${test_name}` because ${_required_var} is empty.")
|
||||
return()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
_cppbessot_db_action_test_common_args(_test_args "${test_name}" "${script_name}")
|
||||
add_test(
|
||||
NAME ${test_name}
|
||||
COMMAND "${CMAKE_COMMAND}" ${_test_args} -P "${CMAKE_CURRENT_SOURCE_DIR}/scripts/${script_name}")
|
||||
set_tests_properties(${test_name} PROPERTIES RUN_SERIAL TRUE)
|
||||
endfunction()
|
||||
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_invalid_target invalid_target.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_missing_mapping missing_mapping.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_ambiguous_mapping ambiguous_mapping.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_missing_migrate_with missing_migrate_with.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_missing_migration_dir missing_migration_dir.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_proddev_createfrom_illegal proddev_createfrom_illegal.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_sqlite_createfrom sqlite_createfrom.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_sqlite_invalid_sql sqlite_invalid_sql.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_sqlite_migrate sqlite_migrate.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_sqlite_proddev_stale sqlite_proddev_stale.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_sqlite_proddev_clone sqlite_proddev_clone.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_pgsql_createfrom_mock pgsql_createfrom_mock.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_pgsql_migrate_order pgsql_migrate_order.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_pgsql_stale_abort pgsql_stale_abort.cmake)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_backfill_env_no_structural backfill_env_no_structural.cmake)
|
||||
cppbessot_add_real_pgsql_db_action_test(
|
||||
cppbessot_db_action_pgsql_createfrom_real
|
||||
pgsql_createfrom_real.cmake
|
||||
CPPBESSOT_DB_PGSQL_DEV_CONNSTR)
|
||||
cppbessot_add_real_pgsql_db_action_test(
|
||||
cppbessot_db_action_pgsql_migrate_real
|
||||
pgsql_migrate_real.cmake
|
||||
CPPBESSOT_DB_PGSQL_DEV_CONNSTR)
|
||||
cppbessot_add_real_pgsql_db_action_test(
|
||||
cppbessot_db_action_pgsql_proddev_clone_real
|
||||
pgsql_proddev_clone_real.cmake
|
||||
CPPBESSOT_DB_PGSQL_PROD_CONNSTR
|
||||
CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR)
|
||||
cppbessot_add_real_pgsql_db_action_test(
|
||||
cppbessot_db_action_pgsql_proddev_stale_real
|
||||
pgsql_proddev_stale_real.cmake
|
||||
CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR)
|
||||
cppbessot_add_real_pgsql_db_action_test(
|
||||
cppbessot_db_action_pgsql_backfill_real
|
||||
pgsql_backfill_real.cmake
|
||||
CPPBESSOT_DB_PGSQL_DEV_CONNSTR)
|
||||
cppbessot_add_db_action_test(cppbessot_db_action_regression regression_targets.cmake)
|
||||
@@ -0,0 +1,425 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
function(cppbessot_test_require_var var_name)
|
||||
if(NOT DEFINED ${var_name} OR "${${var_name}}" STREQUAL "")
|
||||
message(FATAL_ERROR "Missing required test variable `${var_name}`.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_reset_dir path)
|
||||
file(REMOVE_RECURSE "${path}")
|
||||
file(MAKE_DIRECTORY "${path}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_write_file path)
|
||||
get_filename_component(_parent "${path}" DIRECTORY)
|
||||
if(NOT "${_parent}" STREQUAL "")
|
||||
file(MAKE_DIRECTORY "${_parent}")
|
||||
endif()
|
||||
string(JOIN "" _content ${ARGN})
|
||||
file(WRITE "${path}" "${_content}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_cache_string_setting out_var var_name value)
|
||||
set(${out_var} "set(${var_name} \"${value}\" CACHE STRING \"\")\n" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_write_shell_script path)
|
||||
cppbessot_test_write_file("${path}" ${ARGN})
|
||||
execute_process(COMMAND chmod +x "${path}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_case_dir out_var)
|
||||
cppbessot_test_require_var(CPPBESSOT_TEST_BINARY_DIR)
|
||||
cppbessot_test_require_var(CPPBESSOT_TEST_NAME)
|
||||
set(_case_dir "${CPPBESSOT_TEST_BINARY_DIR}/cases/${CPPBESSOT_TEST_NAME}")
|
||||
cppbessot_test_reset_dir("${_case_dir}")
|
||||
set(${out_var} "${_case_dir}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_write_project root_dir settings_text)
|
||||
cppbessot_test_require_var(CPPBESSOT_TEST_MODULE_SOURCE_DIR)
|
||||
string(REPLACE "\\" "\\\\" _module_dir "${CPPBESSOT_TEST_MODULE_SOURCE_DIR}")
|
||||
set(_settings_parts "${settings_text}" ${ARGN})
|
||||
set(_post_include_text
|
||||
"cppbessot_add_db_createfrom_target()\n"
|
||||
"cppbessot_add_db_migrate_target()\n")
|
||||
list(LENGTH _settings_parts _settings_len)
|
||||
if(_settings_len GREATER 0)
|
||||
math(EXPR _last_index "${_settings_len} - 1")
|
||||
list(GET _settings_parts "${_last_index}" _last_part)
|
||||
if("${_last_part}" STREQUAL "FULL_ENABLE")
|
||||
list(REMOVE_AT _settings_parts "${_last_index}")
|
||||
set(_post_include_text "cppbessot_enable()\n")
|
||||
endif()
|
||||
endif()
|
||||
string(JOIN "" _settings_text ${_settings_parts})
|
||||
cppbessot_test_write_file(
|
||||
"${root_dir}/CMakeLists.txt"
|
||||
"cmake_minimum_required(VERSION 3.20)\n"
|
||||
"project(cppbessot_db_action_fixture LANGUAGES CXX)\n"
|
||||
"set(CPPBESSOT_WORKDIR \"db\" CACHE STRING \"\")\n"
|
||||
"set(DB_SCHEMA_DIR_TO_GENERATE \"v1.1\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_AUTO_ENABLE OFF CACHE BOOL \"\")\n"
|
||||
"${_settings_text}\n"
|
||||
"include(\"${_module_dir}/cmake/CppBeSSOT.cmake\")\n"
|
||||
"${_post_include_text}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_add_schema root_dir schema_dir)
|
||||
cppbessot_test_write_file(
|
||||
"${root_dir}/db/${schema_dir}/openapi/openapi.yaml"
|
||||
"openapi: 3.0.0\n"
|
||||
"info:\n"
|
||||
" title: test-${schema_dir}\n"
|
||||
" version: 1.0.0\n"
|
||||
"paths: {}\n"
|
||||
"components:\n"
|
||||
" schemas:\n"
|
||||
" Agent:\n"
|
||||
" type: object\n"
|
||||
" properties:\n"
|
||||
" id:\n"
|
||||
" type: string\n")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_add_sql_file path content)
|
||||
cppbessot_test_write_file("${path}" "${content}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_configure_project root_dir build_dir result_var stdout_var stderr_var)
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" -S "${root_dir}" -B "${build_dir}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
set(${result_var} "${_result}" PARENT_SCOPE)
|
||||
set(${stdout_var} "${_stdout}" PARENT_SCOPE)
|
||||
set(${stderr_var} "${_stderr}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_build_target build_dir target_name result_var stdout_var stderr_var)
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" --target "${target_name}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
set(${result_var} "${_result}" PARENT_SCOPE)
|
||||
set(${stdout_var} "${_stdout}" PARENT_SCOPE)
|
||||
set(${stderr_var} "${_stderr}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_build_target_dry_run build_dir target_name result_var stdout_var stderr_var)
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" --target "${target_name}" -- -n
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
set(${result_var} "${_result}" PARENT_SCOPE)
|
||||
set(${stdout_var} "${_stdout}" PARENT_SCOPE)
|
||||
set(${stderr_var} "${_stderr}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_assert_success result stderr context)
|
||||
if(NOT "${result}" STREQUAL "0")
|
||||
message(FATAL_ERROR "${context} unexpectedly failed.\n${stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_assert_failure_contains result stderr needle)
|
||||
if("${result}" STREQUAL "0")
|
||||
message(FATAL_ERROR "Expected failure containing `${needle}`, but command succeeded.")
|
||||
endif()
|
||||
string(FIND "${stderr}" "${needle}" _match_index)
|
||||
if(_match_index EQUAL -1)
|
||||
message(FATAL_ERROR "Expected failure containing `${needle}`.\nActual stderr:\n${stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_assert_contains haystack needle context)
|
||||
string(FIND "${haystack}" "${needle}" _match_index)
|
||||
if(_match_index EQUAL -1)
|
||||
message(FATAL_ERROR "${context}: expected to find `${needle}` in:\n${haystack}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_assert_file_exists path)
|
||||
if(NOT EXISTS "${path}")
|
||||
message(FATAL_ERROR "Expected file to exist: ${path}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_set_path_with_tool_dir tool_dir)
|
||||
if(DEFINED ENV{PATH} AND NOT "$ENV{PATH}" STREQUAL "")
|
||||
set(ENV{PATH} "${tool_dir}:$ENV{PATH}")
|
||||
else()
|
||||
set(ENV{PATH} "${tool_dir}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_has_real_pgsql_support out_var)
|
||||
find_program(_psql psql)
|
||||
if(_psql AND DEFINED CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR
|
||||
AND NOT "${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}" STREQUAL "")
|
||||
set(${out_var} TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${out_var} FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_require_real_pgsql_support)
|
||||
cppbessot_test_has_real_pgsql_support(_has_support)
|
||||
if(NOT _has_support)
|
||||
message(FATAL_ERROR
|
||||
"Real PostgreSQL db-action test support requires `psql` and CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_exec connstr sql_text)
|
||||
find_program(_psql psql REQUIRED)
|
||||
execute_process(
|
||||
COMMAND "${_psql}" "${connstr}" -v ON_ERROR_STOP=1 -c "${sql_text}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR "Failed PostgreSQL SQL execution.\n${_stdout}\n${_stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_exec_file connstr sql_file)
|
||||
find_program(_psql psql REQUIRED)
|
||||
execute_process(
|
||||
COMMAND "${_psql}" "${connstr}" -v ON_ERROR_STOP=1 -f "${sql_file}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR "Failed PostgreSQL SQL file execution for `${sql_file}`.\n${_stdout}\n${_stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_query_scalar out_var connstr query)
|
||||
find_program(_psql psql REQUIRED)
|
||||
execute_process(
|
||||
COMMAND "${_psql}" "${connstr}" -v ON_ERROR_STOP=1 -t -A -c "${query}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR "Failed PostgreSQL query.\n${_stderr}")
|
||||
endif()
|
||||
string(STRIP "${_stdout}" _value)
|
||||
set(${out_var} "${_value}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_connstr_dbname out_var connstr)
|
||||
string(REGEX MATCH "(^|[ \t])dbname=([^ \t]+)" _match " ${connstr}")
|
||||
if("${CMAKE_MATCH_2}" STREQUAL "")
|
||||
message(FATAL_ERROR "Expected PostgreSQL connstr to include dbname=... but got `${connstr}`.")
|
||||
endif()
|
||||
set(${out_var} "${CMAKE_MATCH_2}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_connstr_replace_dbname out_var connstr new_dbname)
|
||||
cppbessot_test_pgsql_connstr_dbname(_old_dbname "${connstr}")
|
||||
string(REGEX REPLACE "(^|[ \t])dbname=[^ \t]+" "\\1dbname=${new_dbname}" _updated " ${connstr}")
|
||||
string(STRIP "${_updated}" _updated)
|
||||
set(${out_var} "${_updated}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_unique_dbname out_var role_suffix)
|
||||
cppbessot_test_require_var(CPPBESSOT_TEST_NAME)
|
||||
cppbessot_test_require_var(CPPBESSOT_TEST_BINARY_DIR)
|
||||
string(TOLOWER "${CPPBESSOT_TEST_NAME}" _base)
|
||||
string(REGEX REPLACE "[^a-z0-9]+" "_" _base "${_base}")
|
||||
string(REGEX REPLACE "^_+|_+$" "" _base "${_base}")
|
||||
if("${_base}" STREQUAL "")
|
||||
set(_base "db_action_test")
|
||||
endif()
|
||||
string(TOLOWER "${role_suffix}" _role)
|
||||
string(REGEX REPLACE "[^a-z0-9]+" "_" _role "${_role}")
|
||||
string(REGEX REPLACE "^_+|_+$" "" _role "${_role}")
|
||||
string(SHA256 _scope_hash "${CPPBESSOT_TEST_BINARY_DIR}")
|
||||
string(SUBSTRING "${_scope_hash}" 0 8 _scope_suffix)
|
||||
set(${out_var} "cppbessot_${_base}_${_role}_${_scope_suffix}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_isolated_connstr out_var base_connstr role_suffix)
|
||||
cppbessot_test_pgsql_unique_dbname(_dbname "${role_suffix}")
|
||||
cppbessot_test_pgsql_connstr_replace_dbname(_updated "${base_connstr}" "${_dbname}")
|
||||
set(${out_var} "${_updated}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_escape_identifier out_var identifier)
|
||||
string(REPLACE "\"" "\"\"" _escaped "${identifier}")
|
||||
set(${out_var} "\"${_escaped}\"" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_escape_literal out_var literal)
|
||||
string(REPLACE "'" "''" _escaped "${literal}")
|
||||
set(${out_var} "'${_escaped}'" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_drop_database connstr)
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_pgsql_connstr_dbname(_dbname "${connstr}")
|
||||
cppbessot_test_pgsql_escape_identifier(_db_ident "${_dbname}")
|
||||
cppbessot_test_pgsql_escape_literal(_db_lit "${_dbname}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}"
|
||||
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ${_db_lit} AND pid <> pg_backend_pid();")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}"
|
||||
"DROP DATABASE IF EXISTS ${_db_ident};")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_create_database connstr)
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_pgsql_connstr_dbname(_dbname "${connstr}")
|
||||
cppbessot_test_pgsql_escape_identifier(_db_ident "${_dbname}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}"
|
||||
"CREATE DATABASE ${_db_ident};")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_reset_database connstr)
|
||||
cppbessot_test_pgsql_drop_database("${connstr}")
|
||||
cppbessot_test_pgsql_create_database("${connstr}")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_clone_database source_connstr target_connstr)
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_pgsql_connstr_dbname(_source_db "${source_connstr}")
|
||||
cppbessot_test_pgsql_connstr_dbname(_target_db "${target_connstr}")
|
||||
cppbessot_test_pgsql_escape_identifier(_source_ident "${_source_db}")
|
||||
cppbessot_test_pgsql_escape_identifier(_target_ident "${_target_db}")
|
||||
cppbessot_test_pgsql_drop_database("${target_connstr}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}"
|
||||
"CREATE DATABASE ${_target_ident} TEMPLATE ${_source_ident};")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_shell_single_quote out_var text)
|
||||
string(REPLACE "'" "'\"'\"'" _quoted "${text}")
|
||||
set(${out_var} "'${_quoted}'" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_pgsql_clone_command out_var source_connstr target_connstr)
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_pgsql_connstr_dbname(_source_db "${source_connstr}")
|
||||
cppbessot_test_pgsql_connstr_dbname(_target_db "${target_connstr}")
|
||||
cppbessot_test_pgsql_escape_identifier(_source_ident "${_source_db}")
|
||||
cppbessot_test_pgsql_escape_identifier(_target_ident "${_target_db}")
|
||||
cppbessot_test_pgsql_escape_literal(_source_lit "${_source_db}")
|
||||
cppbessot_test_pgsql_escape_literal(_target_lit "${_target_db}")
|
||||
cppbessot_test_shell_single_quote(_admin_shell "${CPPBESSOT_DB_ACTION_TEST_PGSQL_ADMIN_CONNSTR}")
|
||||
cppbessot_test_shell_single_quote(
|
||||
_term_target_shell
|
||||
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ${_target_lit} AND pid <> pg_backend_pid();")
|
||||
cppbessot_test_shell_single_quote(
|
||||
_term_source_shell
|
||||
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ${_source_lit} AND pid <> pg_backend_pid();")
|
||||
cppbessot_test_shell_single_quote(_drop_shell "DROP DATABASE IF EXISTS ${_target_ident};")
|
||||
cppbessot_test_shell_single_quote(_create_shell "CREATE DATABASE ${_target_ident} TEMPLATE ${_source_ident};")
|
||||
set(${out_var}
|
||||
"psql ${_admin_shell} -v ON_ERROR_STOP=1 -c ${_term_target_shell} && "
|
||||
"psql ${_admin_shell} -v ON_ERROR_STOP=1 -c ${_term_source_shell} && "
|
||||
"psql ${_admin_shell} -v ON_ERROR_STOP=1 -c ${_drop_shell} && "
|
||||
"psql ${_admin_shell} -v ON_ERROR_STOP=1 -c ${_create_shell}"
|
||||
PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_sqlite_exec db_path sql_text)
|
||||
find_program(_sqlite3 sqlite3 REQUIRED)
|
||||
get_filename_component(_parent "${db_path}" DIRECTORY)
|
||||
if(NOT "${_parent}" STREQUAL "")
|
||||
file(MAKE_DIRECTORY "${_parent}")
|
||||
endif()
|
||||
set(_sql_file "${CMAKE_CURRENT_BINARY_DIR}/cppbessot-test-sqlite-exec.sql")
|
||||
file(WRITE "${_sql_file}" "${sql_text}")
|
||||
string(REPLACE "\"" "\\\"" _sqlite_read_file "${_sql_file}")
|
||||
execute_process(
|
||||
COMMAND "${_sqlite3}" "${db_path}" ".read \"${_sqlite_read_file}\""
|
||||
RESULT_VARIABLE _result
|
||||
ERROR_VARIABLE _stderr
|
||||
)
|
||||
file(REMOVE "${_sql_file}")
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR "Failed to execute SQLite SQL.\n${_stderr}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_sqlite_query_scalar out_var db_path query)
|
||||
find_program(_sqlite3 sqlite3 REQUIRED)
|
||||
execute_process(
|
||||
COMMAND "${_sqlite3}" -batch -noheader "${db_path}" "${query}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_VARIABLE _stdout
|
||||
ERROR_VARIABLE _stderr
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if(NOT _result EQUAL 0)
|
||||
message(FATAL_ERROR "Failed SQLite query.\n${_stderr}")
|
||||
endif()
|
||||
set(${out_var} "${_stdout}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_write_mock_psql path)
|
||||
cppbessot_test_write_shell_script(
|
||||
"${path}"
|
||||
"#!/bin/sh\n"
|
||||
"set -eu\n"
|
||||
"log_file=\"${CPPBESSOT_TEST_LOG}\"\n"
|
||||
"conn=\"$1\"\n"
|
||||
"shift\n"
|
||||
"printf 'conn:%s\\n' \"$conn\" >> \"$log_file\"\n"
|
||||
"if [ \"\${CPPBESSOT_TEST_PSQL_FAIL_SELECT:-0}\" = \"1\" ]\n"
|
||||
"then\n"
|
||||
" if printf '%s ' \"$@\" | grep -F \"SELECT 1\" >/dev/null 2>&1\n"
|
||||
" then\n"
|
||||
" echo 'simulated select failure' >&2\n"
|
||||
" exit 1\n"
|
||||
" fi\n"
|
||||
"fi\n"
|
||||
"if [ \"\${CPPBESSOT_TEST_PSQL_FAIL_ALL:-0}\" = \"1\" ]\n"
|
||||
"then\n"
|
||||
" echo 'simulated psql failure' >&2\n"
|
||||
" exit 1\n"
|
||||
"fi\n"
|
||||
"while [ \"$#\" -gt 0 ]\n"
|
||||
"do\n"
|
||||
" if [ \"$1\" = \"-c\" ]\n"
|
||||
" then\n"
|
||||
" shift\n"
|
||||
" printf 'sqlcmd:%s\\n' \"$1\" >> \"$log_file\"\n"
|
||||
" elif [ \"$1\" = \"-f\" ]\n"
|
||||
" then\n"
|
||||
" shift\n"
|
||||
" printf 'sqlfile:%s\\n' \"$(basename \"$1\")\" >> \"$log_file\"\n"
|
||||
" fi\n"
|
||||
" shift\n"
|
||||
"done\n")
|
||||
endfunction()
|
||||
|
||||
function(cppbessot_test_assert_log_order log_path)
|
||||
file(READ "${log_path}" _contents)
|
||||
set(_cursor -1)
|
||||
foreach(_needle IN LISTS ARGN)
|
||||
string(FIND "${_contents}" "${_needle}" _index)
|
||||
if(_index EQUAL -1)
|
||||
message(FATAL_ERROR "Missing log entry `${_needle}`.\nLog contents:\n${_contents}")
|
||||
endif()
|
||||
if(_index LESS_EQUAL _cursor)
|
||||
message(FATAL_ERROR "Log entry `${_needle}` appeared out of order.\nLog contents:\n${_contents}")
|
||||
endif()
|
||||
set(_cursor "${_index}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
@@ -0,0 +1,15 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_case_dir}/live/dev.sqlite\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_PGSQL_DEV_CONNSTR \"dbname=test\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/sqlite/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT);\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "is ambiguous")
|
||||
@@ -0,0 +1,28 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_log_file "${_case_dir}/events.log")
|
||||
cppbessot_test_write_file("${_log_file}" "")
|
||||
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_case_dir}/live/dev.sqlite\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_write_shell_script(
|
||||
"${_case_dir}/db/migrations/v1.0-v1.1/pre-structural-backfill.sh"
|
||||
"#!/bin/sh\n"
|
||||
"set -eu\n"
|
||||
"printf 'pre:%s:%s:%s:%s\\n' \"$CPPBESSOT_DB_TARGET\" \"$CPPBESSOT_DB_BACKEND\" \"$CPPBESSOT_DB_MIGRATE_WITH\" \"$CPPBESSOT_DB_SCHEMA_DIR_TO_GENERATE\" >> \"${_log_file}\"\n"
|
||||
"printf 'sqlite:%s\\n' \"$CPPBESSOT_DB_SQLITE_PATH\" >> \"${_log_file}\"\n")
|
||||
cppbessot_test_write_shell_script(
|
||||
"${_case_dir}/db/migrations/v1.0-v1.1/post-structural-backfill.sh"
|
||||
"#!/bin/sh\n"
|
||||
"set -eu\n"
|
||||
"printf 'post:%s\\n' \"$CPPBESSOT_DB_CREATEFROM_SCHEMA_DIR\" >> \"${_log_file}\"\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate hook-only")
|
||||
cppbessot_test_assert_log_order("${_log_file}" "pre:dev:sqlite:v1.0-v1.1:v1.1" "sqlite:${_case_dir}/live/dev.sqlite" "post:v1.1")
|
||||
@@ -0,0 +1,15 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"bogus\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_case_dir}/live/dev.sqlite\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/sqlite/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT);\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "DB_TARGET must be one of")
|
||||
@@ -0,0 +1,12 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_write_project("${_case_dir}" "")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/sqlite/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT);\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "is not mapped")
|
||||
@@ -0,0 +1,12 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_case_dir}/live/dev.sqlite\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "Required variable `DB_MIGRATE_WITH` is missing")
|
||||
@@ -0,0 +1,13 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_case_dir}/live/dev.sqlite\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "Migration directory does not exist")
|
||||
@@ -0,0 +1,49 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_require_var(CPPBESSOT_DB_PGSQL_DEV_CONNSTR)
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_log_file "${_case_dir}/events.log")
|
||||
cppbessot_test_write_file("${_log_file}" "")
|
||||
cppbessot_test_pgsql_isolated_connstr(_dev_connstr "${CPPBESSOT_DB_PGSQL_DEV_CONNSTR}" "dev")
|
||||
|
||||
cppbessot_test_pgsql_reset_database("${_dev_connstr}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${_dev_connstr}"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY); INSERT INTO sample(id) VALUES ('row-1');")
|
||||
|
||||
cppbessot_test_cache_string_setting(_dev_setting "CPPBESSOT_DB_PGSQL_DEV_CONNSTR" "${_dev_connstr}")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"${_dev_setting}")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_write_shell_script(
|
||||
"${_case_dir}/db/migrations/v1.0-v1.1/pre-structural-backfill.sh"
|
||||
"#!/bin/sh\n"
|
||||
"set -eu\n"
|
||||
"printf 'pre:%s:%s:%s\\n' \"$CPPBESSOT_DB_TARGET\" \"$CPPBESSOT_DB_BACKEND\" \"$CPPBESSOT_DB_PGSQL_CONNSTR\" >> \"${_log_file}\"\n")
|
||||
cppbessot_test_write_shell_script(
|
||||
"${_case_dir}/db/migrations/v1.0-v1.1/post-structural-backfill.sh"
|
||||
"#!/bin/sh\n"
|
||||
"set -eu\n"
|
||||
"printf 'post:%s:%s\\n' \"$CPPBESSOT_DB_MIGRATE_WITH\" \"$CPPBESSOT_DB_SCHEMA_DIR_TO_GENERATE\" >> \"${_log_file}\"\n")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/postgre/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT;\n"
|
||||
"UPDATE sample SET note = 'hooked' WHERE id = 'row-1';\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate pgsql backfill real")
|
||||
|
||||
cppbessot_test_assert_log_order(
|
||||
"${_log_file}"
|
||||
"pre:dev:postgre:${_dev_connstr}"
|
||||
"post:v1.0-v1.1:v1.1")
|
||||
cppbessot_test_pgsql_query_scalar(_note "${_dev_connstr}" "SELECT note FROM sample WHERE id='row-1';")
|
||||
if(NOT "${_note}" STREQUAL "hooked")
|
||||
message(FATAL_ERROR "Expected PostgreSQL backfill migration to update note column, got `${_note}`.")
|
||||
endif()
|
||||
|
||||
cppbessot_test_pgsql_drop_database("${_dev_connstr}")
|
||||
@@ -0,0 +1,31 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_tool_dir "${_case_dir}/tools")
|
||||
set(_log_file "${_case_dir}/psql.log")
|
||||
set(CPPBESSOT_TEST_LOG "${_log_file}")
|
||||
cppbessot_test_write_file("${_log_file}" "")
|
||||
cppbessot_test_write_mock_psql("${_tool_dir}/psql")
|
||||
cppbessot_test_set_path_with_tool_dir("${_tool_dir}")
|
||||
set(ENV{CPPBESSOT_TEST_LOG} "${_log_file}")
|
||||
set(ENV{CPPBESSOT_TEST_PSQL_FAIL_ALL} "0")
|
||||
set(ENV{CPPBESSOT_TEST_PSQL_FAIL_SELECT} "0")
|
||||
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(CPPBESSOT_DB_PGSQL_DEV_CONNSTR \"dbname=dev_db\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/postgre/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY);\n")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/postgre/02-seed.sql"
|
||||
"INSERT INTO sample(id) VALUES ('row-1');\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_createfrom pgsql mock")
|
||||
|
||||
file(READ "${_log_file}" _log_contents)
|
||||
cppbessot_test_assert_contains("${_log_contents}" "sqlcmd:DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;" "pgsql reset log")
|
||||
cppbessot_test_assert_contains("${_log_contents}" "sqlfile:01-schema.sql" "pgsql schema log")
|
||||
cppbessot_test_assert_contains("${_log_contents}" "sqlfile:02-seed.sql" "pgsql seed log")
|
||||
@@ -0,0 +1,38 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_require_var(CPPBESSOT_DB_PGSQL_DEV_CONNSTR)
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_pgsql_isolated_connstr(_dev_connstr "${CPPBESSOT_DB_PGSQL_DEV_CONNSTR}" "dev")
|
||||
|
||||
cppbessot_test_pgsql_reset_database("${_dev_connstr}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${_dev_connstr}"
|
||||
"CREATE TABLE legacy_data(id TEXT PRIMARY KEY); INSERT INTO legacy_data(id) VALUES ('old-row');")
|
||||
|
||||
cppbessot_test_cache_string_setting(_dev_setting "CPPBESSOT_DB_PGSQL_DEV_CONNSTR" "${_dev_connstr}")
|
||||
cppbessot_test_write_project("${_case_dir}" "${_dev_setting}")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/postgre/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY, note TEXT);\n")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/postgre/02-seed.sql"
|
||||
"INSERT INTO sample(id, note) VALUES ('row-1', 'seeded');\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_createfrom pgsql real")
|
||||
|
||||
cppbessot_test_pgsql_query_scalar(_row_count "${_dev_connstr}" "SELECT COUNT(*) FROM sample;")
|
||||
if(NOT "${_row_count}" STREQUAL "1")
|
||||
message(FATAL_ERROR "Expected seeded sample row after PostgreSQL createfrom, got `${_row_count}`.")
|
||||
endif()
|
||||
cppbessot_test_pgsql_query_scalar(
|
||||
_legacy_count
|
||||
"${_dev_connstr}"
|
||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'legacy_data';")
|
||||
if(NOT "${_legacy_count}" STREQUAL "0")
|
||||
message(FATAL_ERROR "Expected legacy_data table to be removed during PostgreSQL createfrom.")
|
||||
endif()
|
||||
|
||||
cppbessot_test_pgsql_drop_database("${_dev_connstr}")
|
||||
@@ -0,0 +1,34 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_tool_dir "${_case_dir}/tools")
|
||||
set(_log_file "${_case_dir}/events.log")
|
||||
set(CPPBESSOT_TEST_LOG "${_log_file}")
|
||||
cppbessot_test_write_file("${_log_file}" "")
|
||||
cppbessot_test_write_mock_psql("${_tool_dir}/psql")
|
||||
cppbessot_test_set_path_with_tool_dir("${_tool_dir}")
|
||||
set(ENV{CPPBESSOT_TEST_LOG} "${_log_file}")
|
||||
set(ENV{CPPBESSOT_TEST_PSQL_FAIL_ALL} "0")
|
||||
set(ENV{CPPBESSOT_TEST_PSQL_FAIL_SELECT} "0")
|
||||
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"proddev\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR \"dbname=proddev_db\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND \"printf 'clone\\\\n' >> '${_log_file}'\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_write_shell_script(
|
||||
"${_case_dir}/db/migrations/v1.0-v1.1/pre-structural-backfill.sh"
|
||||
"#!/bin/sh\nset -eu\nprintf 'pre\\n' >> \"$CPPBESSOT_TEST_LOG\"\n")
|
||||
cppbessot_test_write_shell_script(
|
||||
"${_case_dir}/db/migrations/v1.0-v1.1/post-structural-backfill.sh"
|
||||
"#!/bin/sh\nset -eu\nprintf 'post\\n' >> \"$CPPBESSOT_TEST_LOG\"\n")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/postgre/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT;\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate pgsql order")
|
||||
cppbessot_test_assert_log_order("${_log_file}" "clone" "pre" "sqlfile:01-migrate.sql" "post")
|
||||
@@ -0,0 +1,33 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_require_var(CPPBESSOT_DB_PGSQL_DEV_CONNSTR)
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_pgsql_isolated_connstr(_dev_connstr "${CPPBESSOT_DB_PGSQL_DEV_CONNSTR}" "dev")
|
||||
|
||||
cppbessot_test_pgsql_reset_database("${_dev_connstr}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${_dev_connstr}"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY); INSERT INTO sample(id) VALUES ('row-1');")
|
||||
|
||||
cppbessot_test_cache_string_setting(_dev_setting "CPPBESSOT_DB_PGSQL_DEV_CONNSTR" "${_dev_connstr}")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"${_dev_setting}")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/postgre/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT;\n"
|
||||
"UPDATE sample SET note = 'migrated' WHERE id = 'row-1';\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate pgsql real")
|
||||
|
||||
cppbessot_test_pgsql_query_scalar(_note "${_dev_connstr}" "SELECT note FROM sample WHERE id='row-1';")
|
||||
if(NOT "${_note}" STREQUAL "migrated")
|
||||
message(FATAL_ERROR "Expected PostgreSQL migration to set note column, got `${_note}`.")
|
||||
endif()
|
||||
|
||||
cppbessot_test_pgsql_drop_database("${_dev_connstr}")
|
||||
@@ -0,0 +1,53 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_require_var(CPPBESSOT_DB_PGSQL_PROD_CONNSTR)
|
||||
cppbessot_test_require_var(CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR)
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_pgsql_isolated_connstr(_prod_connstr "${CPPBESSOT_DB_PGSQL_PROD_CONNSTR}" "prod")
|
||||
cppbessot_test_pgsql_isolated_connstr(_proddev_connstr "${CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR}" "proddev")
|
||||
|
||||
cppbessot_test_pgsql_reset_database("${_prod_connstr}")
|
||||
cppbessot_test_pgsql_drop_database("${_proddev_connstr}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${_prod_connstr}"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY); INSERT INTO sample(id) VALUES ('prod-row');")
|
||||
|
||||
cppbessot_test_pgsql_clone_command(
|
||||
_clone_command
|
||||
"${_prod_connstr}"
|
||||
"${_proddev_connstr}")
|
||||
cppbessot_test_cache_string_setting(_prod_setting "CPPBESSOT_DB_PGSQL_PROD_CONNSTR" "${_prod_connstr}")
|
||||
cppbessot_test_cache_string_setting(_proddev_setting "CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR" "${_proddev_connstr}")
|
||||
cppbessot_test_cache_string_setting(_clone_setting "CPPBESSOT_DB_PGSQL_CLONE_PROD_TO_PRODDEV_COMMAND" "${_clone_command}")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"proddev\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"${_prod_setting}"
|
||||
"${_proddev_setting}"
|
||||
"${_clone_setting}")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/postgre/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT;\n"
|
||||
"UPDATE sample SET note = 'cloned' WHERE id = 'prod-row';\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate pgsql proddev clone real")
|
||||
|
||||
cppbessot_test_pgsql_query_scalar(_proddev_note "${_proddev_connstr}" "SELECT note FROM sample WHERE id='prod-row';")
|
||||
if(NOT "${_proddev_note}" STREQUAL "cloned")
|
||||
message(FATAL_ERROR "Expected migrated proddev clone to contain note column, got `${_proddev_note}`.")
|
||||
endif()
|
||||
cppbessot_test_pgsql_query_scalar(
|
||||
_prod_note_column_count
|
||||
"${_prod_connstr}"
|
||||
"SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'sample' AND column_name = 'note';")
|
||||
if(NOT "${_prod_note_column_count}" STREQUAL "0")
|
||||
message(FATAL_ERROR "Expected prod database to remain unchanged after proddev migration.")
|
||||
endif()
|
||||
|
||||
cppbessot_test_pgsql_drop_database("${_proddev_connstr}")
|
||||
cppbessot_test_pgsql_drop_database("${_prod_connstr}")
|
||||
@@ -0,0 +1,35 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_require_real_pgsql_support()
|
||||
cppbessot_test_require_var(CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR)
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_pgsql_isolated_connstr(_proddev_connstr "${CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR}" "proddev")
|
||||
|
||||
cppbessot_test_pgsql_reset_database("${_proddev_connstr}")
|
||||
cppbessot_test_pgsql_exec(
|
||||
"${_proddev_connstr}"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY); INSERT INTO sample(id) VALUES ('stale-row');")
|
||||
|
||||
cppbessot_test_cache_string_setting(_proddev_setting "CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR" "${_proddev_connstr}")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"proddev\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_PRODDEV_USE_STALE ON CACHE BOOL \"\")\n"
|
||||
"${_proddev_setting}")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/postgre/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT;\n"
|
||||
"UPDATE sample SET note = 'stale' WHERE id = 'stale-row';\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate pgsql proddev stale real")
|
||||
|
||||
cppbessot_test_pgsql_query_scalar(_note "${_proddev_connstr}" "SELECT note FROM sample WHERE id='stale-row';")
|
||||
if(NOT "${_note}" STREQUAL "stale")
|
||||
message(FATAL_ERROR "Expected PostgreSQL stale proddev migration to update note column, got `${_note}`.")
|
||||
endif()
|
||||
|
||||
cppbessot_test_pgsql_drop_database("${_proddev_connstr}")
|
||||
@@ -0,0 +1,27 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_tool_dir "${_case_dir}/tools")
|
||||
set(_log_file "${_case_dir}/psql.log")
|
||||
set(CPPBESSOT_TEST_LOG "${_log_file}")
|
||||
cppbessot_test_write_file("${_log_file}" "")
|
||||
cppbessot_test_write_mock_psql("${_tool_dir}/psql")
|
||||
cppbessot_test_set_path_with_tool_dir("${_tool_dir}")
|
||||
set(ENV{CPPBESSOT_TEST_LOG} "${_log_file}")
|
||||
set(ENV{CPPBESSOT_TEST_PSQL_FAIL_SELECT} "1")
|
||||
set(ENV{CPPBESSOT_TEST_PSQL_FAIL_ALL} "0")
|
||||
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"proddev\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_PRODDEV_USE_STALE ON CACHE BOOL \"\")\n"
|
||||
"set(CPPBESSOT_DB_PGSQL_PRODDEV_CONNSTR \"dbname=proddev_db\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/postgre/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT;\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "no current stale proddev target")
|
||||
@@ -0,0 +1,15 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"proddev\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_PRODDEV_PATH \"${_case_dir}/live/proddev.sqlite\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/sqlite/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT);\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "does not support DB_TARGET=proddev")
|
||||
@@ -0,0 +1,25 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_require_var(CPPBESSOT_TEST_BINARY_DIR)
|
||||
cppbessot_test_require_var(CPPBESSOT_TEST_MODULE_SOURCE_DIR)
|
||||
set(_build_dir "${CPPBESSOT_TEST_BINARY_DIR}/regression-build")
|
||||
cppbessot_test_reset_dir("${_build_dir}")
|
||||
get_filename_component(_repo_root "${CPPBESSOT_TEST_MODULE_SOURCE_DIR}/../.." ABSOLUTE)
|
||||
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-S "${_repo_root}"
|
||||
-B "${_build_dir}"
|
||||
-DDB_SCHEMA_DIR_TO_GENERATE=v1.1
|
||||
-DDB_SCHEMA_DIR_MIGRATION_FROM=v1.1
|
||||
-DDB_SCHEMA_DIR_MIGRATION_TO=v1.2
|
||||
-DCPPBESSOT_AUTO_ENABLE=ON
|
||||
RESULT_VARIABLE _cfg_result
|
||||
OUTPUT_VARIABLE _cfg_stdout
|
||||
ERROR_VARIABLE _cfg_stderr
|
||||
)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target_dry_run("${_build_dir}" "cppBeSsotOpenAiModelGen" _model_result _model_stdout _model_stderr)
|
||||
cppbessot_test_assert_success("${_model_result}" "${_model_stderr}" "dry-run openai model lib build")
|
||||
cppbessot_test_build_target_dry_run("${_build_dir}" "db_gen_migrations" _mig_result _mig_stdout _mig_stderr)
|
||||
cppbessot_test_assert_success("${_mig_result}" "${_mig_stderr}" "dry-run migration generation build")
|
||||
@@ -0,0 +1,29 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_db_path "${_case_dir}/live/dev.sqlite")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_db_path}\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/sqlite/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY, note TEXT);\n")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/sqlite/02-seed.sql"
|
||||
"INSERT INTO sample(id, note) VALUES ('seed-1', 'created-second');\n")
|
||||
cppbessot_test_sqlite_exec("${_db_path}"
|
||||
"CREATE TABLE old_data(id TEXT); INSERT INTO old_data(id) VALUES ('legacy');")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_createfrom sqlite")
|
||||
|
||||
cppbessot_test_sqlite_query_scalar(_row_count "${_db_path}" "SELECT COUNT(*) FROM sample;")
|
||||
if(NOT "${_row_count}" STREQUAL "1")
|
||||
message(FATAL_ERROR "Expected seeded sample row after recreate, got `${_row_count}`.")
|
||||
endif()
|
||||
cppbessot_test_sqlite_query_scalar(_old_table_count "${_db_path}"
|
||||
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='old_data';")
|
||||
if(NOT "${_old_table_count}" STREQUAL "0")
|
||||
message(FATAL_ERROR "Expected old_data table to be removed during recreate.")
|
||||
endif()
|
||||
@@ -0,0 +1,14 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_case_dir}/live/dev.sqlite\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/v1.1/generated-sql-ddl/sqlite/01-schema.sql"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY);\nBROKEN SQL;\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_createfrom" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "SQLite SQL apply failed")
|
||||
@@ -0,0 +1,23 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_db_path "${_case_dir}/live/dev.sqlite")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_DEV_PATH \"${_db_path}\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_write_file("${_case_dir}/db/migrations/v1.0-v1.1/README.txt" "fixture\n")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/sqlite/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT DEFAULT 'migrated';\n")
|
||||
cppbessot_test_sqlite_exec("${_db_path}"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY); INSERT INTO sample(id) VALUES ('row-1');")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate sqlite")
|
||||
cppbessot_test_sqlite_query_scalar(_note "${_db_path}" "SELECT note FROM sample WHERE id='row-1';")
|
||||
if(NOT "${_note}" STREQUAL "migrated")
|
||||
message(FATAL_ERROR "Expected migration to add note column with default value, got `${_note}`.")
|
||||
endif()
|
||||
@@ -0,0 +1,28 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_prod_db "${_case_dir}/live/prod.sqlite")
|
||||
set(_proddev_db "${_case_dir}/live/proddev.sqlite")
|
||||
set(_log_file "${_case_dir}/clone.log")
|
||||
cppbessot_test_sqlite_exec("${_prod_db}"
|
||||
"CREATE TABLE sample(id TEXT PRIMARY KEY); INSERT INTO sample(id) VALUES ('prod-row');")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"proddev\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_PROD_PATH \"${_prod_db}\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_PRODDEV_PATH \"${_proddev_db}\" CACHE STRING \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_CLONE_PROD_TO_PRODDEV_COMMAND \"cp '${_prod_db}' '${_proddev_db}' && printf 'clone\\\\n' >> '${_log_file}'\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/sqlite/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT DEFAULT 'cloned';\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_success("${_build_result}" "${_build_stderr}" "db_migrate sqlite proddev clone")
|
||||
cppbessot_test_assert_file_exists("${_proddev_db}")
|
||||
cppbessot_test_sqlite_query_scalar(_note "${_proddev_db}" "SELECT note FROM sample WHERE id='prod-row';")
|
||||
if(NOT "${_note}" STREQUAL "cloned")
|
||||
message(FATAL_ERROR "Expected migrated proddev clone to contain note column, got `${_note}`.")
|
||||
endif()
|
||||
@@ -0,0 +1,18 @@
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../cmake/TestCommon.cmake")
|
||||
|
||||
cppbessot_test_case_dir(_case_dir)
|
||||
set(_proddev_db "${_case_dir}/live/proddev.sqlite")
|
||||
cppbessot_test_write_project(
|
||||
"${_case_dir}"
|
||||
"set(DB_TARGET \"proddev\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_WITH \"v1.0-v1.1\" CACHE STRING \"\")\n"
|
||||
"set(DB_MIGRATE_PRODDEV_USE_STALE ON CACHE BOOL \"\")\n"
|
||||
"set(CPPBESSOT_DB_SQLITE_PRODDEV_PATH \"${_proddev_db}\" CACHE STRING \"\")\n")
|
||||
cppbessot_test_add_schema("${_case_dir}" "v1.1")
|
||||
cppbessot_test_add_sql_file("${_case_dir}/db/migrations/v1.0-v1.1/sqlite/01-migrate.sql"
|
||||
"ALTER TABLE sample ADD COLUMN note TEXT DEFAULT 'ok';\n")
|
||||
|
||||
cppbessot_test_configure_project("${_case_dir}" "${_case_dir}/build" _cfg_result _cfg_stdout _cfg_stderr)
|
||||
cppbessot_test_assert_success("${_cfg_result}" "${_cfg_stderr}" "fixture configure")
|
||||
cppbessot_test_build_target("${_case_dir}/build" "db_migrate" _build_result _build_stdout _build_stderr)
|
||||
cppbessot_test_assert_failure_contains("${_build_result}" "${_build_stderr}" "no current stale proddev target")
|
||||
Reference in New Issue
Block a user