|
Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
|
This page collects the questions external developers ask most often when adopting database_system. For step-by-step walkthroughs, follow the Quick Start Tutorial, ORM Entity Mapping Tutorial, and Multi-Backend Tutorial pages. For diagnostics, see Troubleshooting Guide.
It depends on the deployment shape and the workload:
| Scenario | Recommended backend |
|---|---|
| Embedded app, CLI tool, single-user desktop | SQLite — zero-configuration, file-based |
| Unit tests for code that talks to a database | SQLite (:memory:) |
| Web/server OLTP, JSON-heavy workloads, analytics | PostgreSQL — most feature-complete |
| Existing MySQL infrastructure | MySQL — drop-in via the MySQL backend |
| Document-shaped or schemaless data | MongoDB (experimental) |
| Cache-style key/value lookups | Redis (experimental) |
PostgreSQL is the safest default for new server applications. SQLite is the safest default for tests and embedded scenarios.
Local in-process connection pooling was removed in Phase 4.3 to make room for the upcoming proxy mode. Today there are two practical options:
database_manager open one logical handle per request.database_server process will centralise pooling and expose it through the same client API. The client code does not change.For high-throughput services, treat the database_manager as a short-lived facade and rely on the driver pool underneath.
database_manager exposes begin_transaction(), commit_transaction(), and rollback_transaction() helpers. These open a transaction at the backend default isolation level:
READ COMMITTED by default; raise per-transaction with "BEGIN @c TRANSACTION @c ISOLATION @c LEVEL @c SERIALIZABLE".REPEATABLE READ by default; change with "SET @c TRANSACTION @c ISOLATION @c LEVEL @c READ @c COMMITTED"."BEGIN @c IMMEDIATE" to acquire the write lock up front and avoid SQLITE_BUSY surprises.To customise, issue the appropriate SET / BEGIN statement before your work, then call commit_transaction() as usual.
database_system does not ship a migration runner today. The recommended patterns are:
"CREATE @c TABLE @c IF @c NOT @c EXISTS" and "ALTER @c TABLE ... @c IF @c NOT @c EXISTS" so the same script can run repeatedly.migrations/0001_init.sql) and run them at startup with a simple loop that records the current version in a schema_versions table.entity::get_metadata().create_table_sql() to bootstrap a schema directly from your ORM declarations. See ORM Entity Mapping Tutorial.| Use the query builder when... | Use raw SQL when... |
|---|---|
| Column lists or filters are dynamic | The statement is fixed and well-understood |
| You target multiple backends | You need a backend-specific feature (e.g. JSONB, LATERAL JOIN) |
| You want compile-time-ish safety on identifier names | You are running a one-off DDL script |
| You need to compose conditions programmatically | The query is short and the SQL is clearer than the builder calls |
The two are not exclusive — many apps build the SELECT clause with the query builder and pass complex CTEs as raw text.
The ORM is metadata-driven, not reflective. ENTITY_FIELD generates a small field_metadata struct at compile time and a static initializer adds it to a per-class entity_metadata instance. There is no runtime parsing, no virtual call per field, and no extra allocation per row read.
Practical guidance:
SELECT only the columns you need) over SELECT *.Each backend supports prepared statements at the driver layer. The database_manager helpers currently accept finished SQL strings, so parameterisation happens via the query builder (which formats values through value_formatter) or by calling backend-specific prepare APIs.
For high-volume queries, prefer the query builder so values are escaped correctly per dialect and so the same code keeps working when you switch backends.
The database/async/ module exposes asynchronous wrappers built on Asio. They run blocking driver calls on a worker pool and return std::future-style handles, so you can integrate database calls with the rest of an Asio-based application without spawning ad-hoc threads.
If you already have a thread pool from thread_system, hand it to the async layer rather than letting it create its own.
Every high-level call returns kcenon::common::Result<T>, so error handling is explicit:
result.is_ok() before reading result.value().result.error().message and result.error().code for diagnostics.database_manager before retrying.See error_handling.cpp for the canonical patterns.
There are three common shapes; pick the one that matches your isolation requirements:
database_manager per tenant. Use the upcoming proxy mode to share connections.tenant_id column to every table and include it in every WHERE clause. Cheapest, but requires discipline; consider enforcing it with row-level security on PostgreSQL.If your question is not answered here, check: