Put the literals into a separate namespace; resolves #31 (#32)

This commit is contained in:
Dr. Patrick Urbanke (劉自成)
2025-07-23 22:32:26 +02:00
committed by GitHub
parent 7b5c849e02
commit 1424d29c5d
100 changed files with 394 additions and 124 deletions

View File

@@ -6,6 +6,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
- [Defining Tables](defining_tables.md) - How to define tables using C++ structs
- [sqlgen::col](col.md) - How to represent columns in queries
- [sqlgen::literals](literals.md) - How to use column and table alias literals in queries
- [sqlgen::Flatten](flatten.md) - How to "inherit" fields from other structs
- [sqlgen::PrimaryKey](primary_key.md) - How to define primary keys in sqlgen
- [sqlgen::Result](result.md) - How sqlgen handles errors and results
@@ -24,8 +25,8 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab
- [sqlgen::drop](drop.md) - How to drop a table
- [sqlgen::exec](exec.md) - How to execute raw SQL statements
- [sqlgen::group_by and Aggregations](group_by_and_aggregations.md) - How generate GROUP BY queries and aggregate data
- [sqlgen::inner_join, sqlgen::left_join, sqlgen::right_join, sqlgen::full_join](joins.md) - How to join different tables
- [sqlgen::insert](insert.md) - How to insert data within transactions
- [Joins](joins.md) - How to join different tables
- [sqlgen::update](update.md) - How to update data in a table
## Other Operations

View File

@@ -10,6 +10,7 @@ Reference a column using the string literal operator:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Using string literal operator
const auto age_col = "age"_c;
@@ -26,6 +27,7 @@ Compare columns with values or other columns:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Compare with value
const auto query1 = read<std::vector<Person>> | where("age"_c > 18);
@@ -55,6 +57,7 @@ Check for NULL or NOT NULL values:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Find records where age is NULL
const auto query1 = read<std::vector<Person>> |
@@ -85,6 +88,7 @@ Use LIKE and NOT LIKE for pattern matching:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Find names starting with 'H'
const auto query1 = read<std::vector<Person>> |
@@ -115,6 +119,7 @@ Specify column ordering in queries:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Order by age ascending
const auto query1 = read<std::vector<Person>> |
@@ -154,6 +159,7 @@ Set column values in UPDATE statements:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Update a single column
const auto query1 = update<Person>("age"_c.set(46));
@@ -192,4 +198,4 @@ WHERE "first_name" = 'Hugo';
- All operations are composable and can be chained together
- The class supports both value and column-to-column comparisons
- String literals are automatically converted to the appropriate SQL type
- The class is thread-safe and has no mutable state
- The class is thread-safe and has no mutable state

View File

@@ -105,6 +105,7 @@ Example of thread-safe usage with monadic style:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Create pool
const auto pool = make_connection_pool<postgres::Connection>(config, credentials);
@@ -133,6 +134,7 @@ Sessions are managed through RAII (Resource Acquisition Is Initialization) and s
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Using monadic style for session management with exec
session(pool)

View File

@@ -37,6 +37,7 @@ Create an index only if it doesn't already exist:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = create_index<"person_ix", Person>("first_name"_c, "last_name"_c) | if_not_exists;
@@ -53,6 +54,7 @@ You can also use monadic error handling here:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = create_index<"person_ix", Person>("first_name"_c, "last_name"_c) | if_not_exists;
@@ -66,6 +68,7 @@ Create a unique index to enforce uniqueness constraints:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = create_unique_index<"person_ix", Person>("first_name"_c, "last_name"_c);
@@ -84,6 +87,7 @@ Create a partial index by adding a WHERE clause to filter which rows are include
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = create_index<"test_table_ix", TestTable>("field1"_c, "field2"_c) |
if_not_exists |
@@ -104,6 +108,7 @@ The WHERE clause can be combined with other modifiers like `if_not_exists` and w
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = create_index<"person_ix", Person>("first_name"_c, "last_name"_c) | if_not_exists;
@@ -120,7 +125,7 @@ This generates the following SQL:
CREATE INDEX IF NOT EXISTS "person_ix" ON "Person" ("first_name", "last_name");
```
It is strongly recommended that you use `using namespace sqlgen`. However,
It is strongly recommended that you use `using namespace sqlgen` and `using namespace sqlgen::literals`. However,
if you do not want to do that, you can rewrite the example above as follows:
```cpp
@@ -134,7 +139,7 @@ const auto result = query(conn);
- The `if_not_exists` clause is optional - if omitted, the query will fail if the index already exists
- The `Result<Ref<Connection>>` type provides error handling; use `.value()` to extract the result (will throw an exception if there's an error) or handle errors as needed or refer to the documentation on `sqlgen::Result<...>` for other forms of error handling.
- `"..."_c` refers to the name of the column. If such a field does not exist on the struct (e.g., `Person`), the code will fail to compile.
- `"..."_c` refers to the name of the column. If such a field does not exist on the struct (e.g., `Person`), the code will fail to compile. It is defined in the namespace `sqlgen::literals`.
- Index names must be unique within a database
- You can create indexes on multiple columns by providing multiple column names
- Use `create_unique_index` when you need to enforce uniqueness constraints on the indexed columns

View File

@@ -37,6 +37,7 @@ Delete specific records using a `where` clause:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = delete_from<Person> |
where("first_name"_c == "Hugo");
@@ -52,12 +53,14 @@ WHERE "first_name" = 'Hugo';
```
Note that `"..."_c` refers to the name of the column. If such a field does not
exist on the struct `Person`, the code will fail to compile.
exist on the struct `Person`, the code will fail to compile. `"..."_c` is
defined in the namespace `sqlgen::literals`.
You can also use monadic error handling here:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = delete_from<Person> |
where("first_name"_c == "Hugo");
@@ -70,6 +73,7 @@ const auto result = sqlite::connect("database.db").and_then(query);
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = delete_from<Person> |
where("age"_c >= 18 and "last_name"_c == "Simpson");
@@ -88,7 +92,7 @@ DELETE FROM "Person"
WHERE ("age" >= 18) AND ("last_name" = 'Simpson');
```
It is strongly recommended that you use `using namespace sqlgen`. However,
It is strongly recommended that you use `using namespace sqlgen` and `using namespace sqlgen::literals`. However,
if you do not want to do that, you can rewrite the example above as follows:
```cpp

View File

@@ -10,6 +10,7 @@ Perform aggregations without grouping:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
struct Children {
int num_children;
@@ -54,6 +55,7 @@ Group data and perform aggregations:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
struct Children {
std::string last_name;

View File

@@ -19,6 +19,7 @@ Each join type can be used with either a table or a subquery, and can be aliased
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
@@ -128,7 +129,9 @@ Where:
## Aliasing and Column References
When joining tables or subqueries, you must use aliases to disambiguate columns. Use the `_t1`, `_t2`, etc. suffixes to refer to columns from different tables or subqueries:
When joining tables or subqueries, you must use aliases to disambiguate columns. Use the `_t1`, `_t2`, etc.
suffixes to refer to columns from different tables or subqueries, which are defined in the
namespace `sqlgen::literals`:
```cpp
"id"_t1, "first_name"_t2, "age"_t3

127
docs/literals.md Normal file
View File

@@ -0,0 +1,127 @@
# `sqlgen::literals`
The `sqlgen::literals` namespace provides user-defined literal operators for referencing columns and table aliases in a type-safe, expressive, and concise way when building SQL queries with sqlgen. These literals are essential for writing readable and maintainable query expressions, especially when working with complex queries involving joins and table aliases.
## Usage
### Column Literals: `_c`
Reference a column by name using the `_c` literal:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Reference the "age" column
const auto age_col = "age"_c;
// Use in a query
const auto query = sqlgen::read<std::vector<Person>> | where("age"_c < 18);
```
This generates SQL like:
```sql
SELECT "id", "first_name", "last_name", "age"
FROM "Person"
WHERE "age" < 18;
```
If the column name does not exist in the struct, the code will fail to compile, ensuring type safety.
### Table Alias Literals: `_t1`, `_t2`, ..., `_t99`
When working with joins or subqueries, you often need to disambiguate columns by table alias. The `sqlgen::literals` namespace provides `_tN` suffixes for this purpose, where `N` ranges from 1 to 99:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Reference the "id" column from table alias t1
const auto id_t1 = "id"_t1;
// Reference the "first_name" column from table alias t2
const auto first_name_t2 = "first_name"_t2;
```
#### Example: Joins with Aliases
```cpp
const auto get_people =
select_from<Person, "t1">(
"last_name"_t1 | as<"last_name">,
"first_name"_t1 | as<"first_name_parent">,
"first_name"_t3 | as<"first_name_child">,
("age"_t1 - "age"_t3) | as<"parent_age_at_birth">
)
| inner_join<Relationship, "t2">("id"_t1 == "parent_id"_t2)
| left_join<Person, "t3">("id"_t3 == "child_id"_t2)
| order_by("id"_t1, "id"_t3)
| to<std::vector<ParentAndChild>>;
```
This produces SQL like:
```sql
SELECT t1."last_name" AS "last_name", t1."first_name" AS "first_name_parent", t3."first_name" AS "first_name_child", (t1."age") - (t3."age") AS "parent_age_at_birth"
FROM "Person" t1
INNER JOIN "Relationship" t2 ON t1."id" = t2."parent_id"
LEFT JOIN "Person" t3 ON t3."id" = t2."child_id"
ORDER BY t1."id", t3."id"
```
### Supported Suffixes
- `_c` — Reference a column by name (e.g., `"age"_c`)
- `_t1`, `_t2`, ..., `_t99` — Reference a column by name and table alias (e.g., `"id"_t2`)
All these literals are defined in the `sqlgen::literals` namespace. It is strongly recommended to use:
```cpp
using namespace sqlgen::literals;
```
in your query files for convenience.
## Type Safety
- Column names are checked at compile time against the struct definition.
- Table aliases are enforced by the literal suffix, preventing ambiguous references in joins.
- If you use a column or alias that does not exist, the code will fail to compile.
## Advanced: Custom Aliases
If you need an alias outside the provided `_tN` range, you can use the `col` template directly:
```cpp
const auto custom_col = col<"id", "custom_alias">;
```
## Notes
- The `_c` and `_tN` literals return `Col` objects, which support all standard SQL operations (comparison, ordering, etc.).
- These literals are composable with all sqlgen query builder functions, such as `where`, `order_by`, `group_by`, and join conditions.
- The use of these literals is required for type-safe, readable, and maintainable query construction in sqlgen.
- All operations are thread-safe and have no mutable state.
## Example: Full Query Composition
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>>
| where("age"_c >= 18 and "last_name"_c == "Simpson")
| order_by("first_name"_c.desc())
| limit(10);
```
This generates:
```sql
SELECT "id", "first_name", "last_name", "age"
FROM "Person"
WHERE ("age" >= 18) AND ("last_name" = 'Simpson')
ORDER BY "first_name" DESC
LIMIT 10;
```

View File

@@ -109,6 +109,7 @@ struct Person {
std::vector<Person> get_people(const auto& conn,
const sqlgen::AlphaNumeric& first_name) {
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
where("first_name"_c == first_name);
return query(conn).value();

View File

@@ -33,6 +33,7 @@ if (!conn) {
}
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
where("age"_c < 18 and "first_name"_c != "Hugo");

View File

@@ -38,6 +38,7 @@ Filter results using a `where` clause:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
where("age"_c < 18 and "first_name"_c != "Hugo");
@@ -56,12 +57,14 @@ WHERE
```
Note that `"..."_c` refers to the name of the column. If such a field does not
exists on the struct `Person`, the code will fail to compile.
exists on the struct `Person`, the code will fail to compile. It is defined in
the namespace `sqlgen::literals`.
You can also use monadic error handling here:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
where("age"_c < 18 and "first_name"_c != "Hugo");
@@ -76,6 +79,7 @@ Sort and limit results:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
order_by("age"_c) |
@@ -125,6 +129,7 @@ const auto adults = people_range | filter([](const sqlgen::Result<Person>& r) {
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
where("age"_c >= 18) |
@@ -147,7 +152,7 @@ ORDER BY
LIMIT 10;
```
It is strongly recommended that you use `using namespace sqlgen`. However,
It is strongly recommended that you use `using namespace sqlgen` and `using namespace sqlgen::literals;`. However,
if you do not want to do that, you can rewrite the example above as follows:
```cpp
@@ -164,4 +169,4 @@ const auto adults = query(conn).value();
- All query clauses (`where`, `order_by`, `limit`) are optional.
- The `Result<ContainerType>` type provides error handling; use `.value()` to extract the result (will throw a exception if the results) or handle errors as needed. Refer to the
- The `sqlgen::Range<T>` type allows for lazy iteration over results.
- `"..."_c` refers to the name of the column.
- `"..."_c` refers to the name of the column.

View File

@@ -27,6 +27,7 @@ if (!conn) {
}
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = sqlgen::read<std::vector<Person>> |
where("age"_c < 18 and "first_name"_c != "Hugo");

View File

@@ -10,6 +10,7 @@ Transpile any SQL operation to a dialect-specific SQL string:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Define a query
const auto query = sqlgen::read<std::vector<Person>> |
@@ -44,6 +45,7 @@ const auto sqlite_sql = sqlite::to_sql(query);
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Basic select
const auto select_query = sqlgen::read<std::vector<Person>>;
@@ -81,6 +83,7 @@ before actually inserting the data.
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Define a table structure
struct TestTable {
@@ -111,6 +114,7 @@ CREATE TABLE IF NOT EXISTS "TestTable" (
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto insert_query = Insert<TestTable>{};
const auto sql = postgres::to_sql(insert_query);
@@ -134,6 +138,7 @@ VALUES (?, ?, ?, ?);
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto delete_query = delete_from<TestTable> |
where("field2"_c > 0);

View File

@@ -14,6 +14,7 @@ Here's a basic example of how to use transactions:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
// Start a transaction and chain operations
// Note that all of these operations return
@@ -37,6 +38,7 @@ Example with error handling:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
auto conn = sqlite::connect("database.db")
.and_then(begin_transaction)

View File

@@ -12,6 +12,7 @@ Update specific columns in a table:
const auto conn = sqlgen::sqlite::connect("database.db");
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = update<Person>("age"_c.set(100), "first_name"_c.set("New Name"));
@@ -42,6 +43,7 @@ Update specific records using a `where` clause:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = update<Person>("age"_c.set(100)) |
where("first_name"_c == "Hugo");
@@ -58,12 +60,14 @@ WHERE "first_name" = 'Hugo';
```
Note that `"..."_c` refers to the name of the column. If such a field does not
exist on the struct `Person`, the code will fail to compile.
exist on the struct `Person`, the code will fail to compile. It is defined
in the namespace `sqlgen::literals`.
You can also use monadic error handling here:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = update<Person>("age"_c.set(100)) |
where("first_name"_c == "Hugo");
@@ -78,6 +82,7 @@ You can set a column's value to another column's value:
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = update<Person>("first_name"_c.set("last_name"_c)) |
where("age"_c > 18);
@@ -97,6 +102,7 @@ WHERE "age" > 18;
```cpp
using namespace sqlgen;
using namespace sqlgen::literals;
const auto query = update<Person>(
"first_name"_c.set("last_name"_c),
@@ -120,7 +126,8 @@ SET
WHERE "age" > 0;
```
It is strongly recommended that you use `using namespace sqlgen`. However,
It is strongly recommended that you use `using namespace sqlgen`
and `using namespace sqlgen::literals`. However,
if you do not want to do that, you can rewrite the example above as follows:
```cpp
@@ -137,7 +144,7 @@ const auto result = query(conn);
- You must specify at least one column to update
- The `where` clause is optional - if omitted, all records will be updated
- The `Result<Ref<Connection>>` type provides error handling; use `.value()` to extract the result (will throw an exception if there's an error) or handle errors as needed or refer to the documentation on `sqlgen::Result<...>` for other forms of error handling
- `"..."_c` refers to the name of the column
- `"..."_c` refers to the name of the column. It is defined in the namespace `sqlgen::literals`.
- You can set columns to either literal values or other column values
- The update operation is atomic - either all specified columns are updated or none are