sqlpp23

repository·main·Indexed 41 days ago

https://github.com/rbock/sqlpp23

A type-safe C++23 embedded domain specific language (EDSL) for writing SQL queries. It leverages the C++ type system to catch SQL errors at compile time and provides a vendor-neutral core with connector libraries for backends such as MariaDB, MySQL, PostgreSQL, sqlite3, and sqlcipher.

Tokens
39.8K
Snippets
142
Records
190
Agent score
51%

What's inside sqlpp23

  1. Introduction to sqlpp23

    main

    sqlpp23 is a C++ library that allows you to write SQL queries in C++ using a natural, type-safe syntax. It leverages C++ templates to provide compile-time validation of your SQL statements, catching errors like typos, type mismatches (comparing incompatible types), and missing tables before your code even runs.

    Key features include:

    • Strong Typing: Tables, columns, and functions are strongly typed, allowing the compiler to catch oversight errors.
    • Type-Safe Results: Query results can be iterated as ranges, and rows contain strongly typed, aptly named data members for safe data access.
    • IDE Support: The design enables effective code completion in modern IDEs.
    • Natural Syntax: SQL constructs are mapped to C++ code to feel intuitive to SQL users.
  2. What is sqlpp23?

    main

    sqlpp23 is a type-safe embedded domain specific language (EDSL) for writing SQL queries and handling results directly in C++. It represents tables, columns, and result fields as C++ structs or data members, and constructs statements and clauses using C++ functions.

    Key benefits include:

    • Compile-time safety: The compiler catches typos, type mismatches (comparing incompatible types), missing tables in from() clauses, missing columns in insert_into(), and invalid mixes of aggregate and non-aggregate functions.
    • IDE Support: Because it uses standard C++ expressions, your IDE can provide autocompletion and assistance for your SQL queries.
    • Vendor Neutrality: The core library is vendor-neutral. Database-specific features (like dialect differences or unsupported features) are handled via connector libraries.
    • Connector-driven behavior: Connectors (e.g., for MariaDB, MySQL, PostgreSQL, sqlite3, sqlcipher) interpret expressions specifically for the target backend, such as handling string concatenation via operator|| or concat methods automatically.
  3. Compare Optimistic (OCC) vs Pessimistic (PCC) Concurrency Control

    main

    When deciding how to handle concurrent data access, consider the following comparison between Optimistic Concurrency Control (OCC) and Pessimistic Concurrency Control (PCC):

    FeatureOCCPCC
    Uses transactionsYesYes
    Retries transactionsYesNo
    Uses locksNo (Implicitly by DB)Yes (Explicitly by user/DB)
    Performance (Low Contention)HighMedium
    Performance (High Contention)LowMedium
    Ease of UseHighLow

    Key Takeaways:

    • Use OCC when contention is low. It simplifies architecture by removing the need for manual locks and avoiding deadlocks.
    • Use PCC when contention is high, as OCC performance degrades significantly under high conflict rates.
  4. Understand mapping priorities for custom types

    main

    When multiple mapping mechanisms are used, sqlpp23-ddl2cpp applies them in the following order of precedence (from lowest to highest):

    1. sqlpp23 default mapping
    2. CSV: SQL type $\rightarrow$ C++ type
    3. CSV: column name $\rightarrow$ C++ type
    4. CSV: table.column name $\rightarrow$ C++ type
    5. CSV: schema.table.column $\rightarrow$ C++ type
    6. Comment annotations (of any kind)

    Note: Comment annotations must not contradict each other.

  5. MySQL connector limitations: Joins and Casting

    main

    The MySQL connector has the following specific limitations:

    1. No full_outer_join: Attempting to use .full_outer_join() will result in a compile-time static_assert with the message "MySQL: No support for full outer join".
    2. No Boolean CAST: MySQL does not support casting to or from sqlpp::boolean.
  6. Construct Common Table Expressions (CTEs)

    main

    CTEs are created using sqlpp::cte with a name (via sqlpp::alias) and defined using the .as() operator, which takes a statement (like a select) as its argument.

    Non-recursive CTEs

    const auto x = sqlpp::cte(sqlpp::alias::x)
                   .as(select(foo.id).from(foo));

    Recursive CTEs

    Recursive CTEs are built in two steps: first defining a base case, then unioning it with a recursive statement.

    // 1. Construct the base
    const auto x_base = sqlpp::cte(sqlpp::alias::x).as(select(sqlpp::value(0).as(sqlpp::alias::a)));
    
    // 2. Union with the recursion statement
    const auto x = x_base.union_all(select((x_base.a + 1).as(sqlpp::alias::a))
                                    .from(x_base)
                                    .where(x_base.a < 10));

    To use a CTE in a query, use the with clause.

  7. How NULL is represented in sqlpp23

    main

    In sqlpp23, SQL NULL values are represented using std::optional. Specifically, NULL is mapped to std::nullopt.

    When reading fields from a result row (via select) or assigning values (via insert or update), you should treat these fields as std::optional<T>.

    Important Note on Comparisons: sqlpp23 preserves standard SQL behavior for NULL comparisons. Comparing any value to NULL (including NULL to itself) using standard equality/inequality operators does not return a boolean true or false; instead, it returns NULL.

    NULL != NULL    -> NULL
    NULL = NULL     -> NULL
  8. How connection pools work in sqlpp23

    main
    A connection pool is a centralized cache of database connections. Instead of opening and closing a physical connection to the database server for every query, you fetch a connection object from the pool. When the connection object is destroyed (e.g., it goes out of scope), the physical connection is not closed; instead, it is returned to the pool's cache for reuse. If the cache is empty, the pool creates a new connection.
  9. Configure custom type mappings in sqlpp23-ddl2cpp

    main

    While sqlpp23-ddl2cpp has native mappings for standard SQL types, you can specify custom mappings to map SQL types or specific columns to custom C++ types.

    Available built-in sqlpp types for mapping include:

    • ::sqlpp::blob (Binary Large Object)
    • ::sqlpp::boolean (Boolean)
    • ::sqlpp::date (Date)
    • ::sqlpp::floating_point (Floating-point)
    • ::sqlpp::integral (Integer)
    • ::sqlpp::serial (Auto-incrementing integer, non-nullable)
    • ::sqlpp::text (Text)
    • ::sqlpp::time (Time of day)
    • ::sqlpp::timestamp (Timestamp)

    You can also map to your own user-defined C++ types.

    // Example of target types
    ::sqlpp::integral
    ::sqlpp::text
    ::some::custom_type
  10. Understand thread safety guarantees in sqlpp23

    main

    sqlpp23 does not provide specific thread-safety guarantees and aims to have no influence on thread safety itself. The actual thread safety of your application depends on the underlying database client library being used.

    To avoid crashes, lock-ups, or SQL errors, avoid these two patterns:

    1. Cross-thread connection usage: Creating a connection in one thread and then attempting to use it in a different thread.
    2. Simultaneous multi-thread usage: Attempting to use the same connection instance in multiple threads at the same time.

    Note on specific drivers: For drivers like MySQL, PostgreSQL, and SQLite3, it is often safe to create a connection in one thread and move it to another, but using a single connection simultaneously across multiple threads is generally unsafe.

  11. Improved IDE code completion in sqlpp23

    main

    Compared to sqlpp11, sqlpp23 provides significantly cleaner and more relevant IDE code completion suggestions. The library has been redesigned to reduce the noise of internal/private members appearing in autocomplete lists, allowing developers to focus on the public API surface.

    Key Improvements:

    • Select Statements: Instead of a long list of internal methods (like _get_member or _prepare), the IDE will primarily suggest high-level statement modifiers such as as(), for_update(), union_all(), and union_distinct().
    • Prepared Statements: Autocomplete for prepared statements is streamlined. Instead of seeing internal binding methods, you will see the primary parameters member.
    • Connections: The db connection object provides a cleaner list of public methods. Internal implementation details and low-level serialization methods are hidden from the primary suggestion list.