diff --git a/docs/README.md b/docs/README.md index b5a5624..4536b23 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab - [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 +- [sqlgen::insert, sqlgen::insert_or_replace](insert.md) - How to insert data within transactions - [sqlgen::select_from](select_from.md) - How to read data from a database using more complex queries - [sqlgen::unite and sqlgen::unite_all](unite.md) - How to combine results from multiple SELECT statements - [sqlgen::update](update.md) - How to update data in a table diff --git a/docs/insert.md b/docs/insert.md index c274593..89ca2ea 100644 --- a/docs/insert.md +++ b/docs/insert.md @@ -1,4 +1,4 @@ -# `sqlgen::insert` +# `sqlgen::insert`, `sqlgen::insert_or_replace` The `sqlgen::insert` interface provides a type-safe way to insert data from C++ containers or ranges into a SQL database. Unlike `sqlgen::write`, it does not create tables automatically and is designed to be used within transactions. It's particularly useful when you need fine-grained control over table creation and transaction boundaries. @@ -68,9 +68,35 @@ sqlgen::sqlite::connect("database.db") .value(); ``` -### With Replacement +### With Replacement (`insert_or_replace`) -Replace existing rows: +The `insert_or_replace` helper inserts rows and updates existing rows when a primary key or unique constraint would be violated by the insert. It is a thin wrapper over the same insertion paths used by `insert`, but it sets the internal `or_replace` flag so the transpiler emits backend-specific "upsert" SQL. + +Function signatures (examples): + +```cpp +// Use with an explicit connection (or a Result>) +template +auto insert_or_replace(const auto& conn, const ContainerType& data); + +// Use as a pipeline element (returns a callable that accepts a connection) +template +auto insert_or_replace(const ContainerType& data); +``` + +Compile-time requirement + +- The table type must have a primary key or at least one unique constraint. This is enforced at compile time via a static_assert: + + "The table must have a primary key or unique column for insert_or_replace(...) to work." + +Behavior notes + +- SQLite, PostgreSQL and DuckDB backends emit `ON CONFLICT (...) DO UPDATE ...` (using `excluded.*` to reference the incoming values). +- MySQL backend emits `ON DUPLICATE KEY UPDATE` and uses `VALUES(...)` to reference incoming values. +- The transpilation helper `to_insert_or_write<..., dynamic::Insert>(true)` is used internally to produce the correct SQL. + +Example: ```cpp const auto people1 = std::vector({ @@ -91,16 +117,9 @@ const auto result = sqlite::connect() .value(); ``` -This generates the following SQL: +Generated SQL (SQLite/Postgres/DuckDB style): ```sql -CREATE TABLE IF NOT EXISTS "Person" ( - "id" INTEGER PRIMARY KEY, - "first_name" TEXT NOT NULL, - "last_name" TEXT NOT NULL, - "age" INTEGER NOT NULL -); -INSERT INTO "Person" ("id", "first_name", "last_name", "age") VALUES (?, ?, ?, ?); INSERT INTO "Person" ("id", "first_name", "last_name", "age") VALUES (?, ?, ?, ?) ON CONFLICT (id) DO UPDATE SET id=excluded.id, @@ -109,9 +128,9 @@ ON CONFLICT (id) DO UPDATE SET age=excluded.age; ``` -The SQL generated by the PostgreSQL backend for `insert_or_replace(...)` is similar. The MySQL backend generates `ON DUPLICATE KEY UPDATE` instead: +Generated SQL (MySQL style): + ```sql -... INSERT INTO `Person` (`id`, `first_name`, `last_name`, `age`) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE id=VALUES(id), @@ -247,4 +266,4 @@ While both `insert` and `write` can be used to add data to a database, they serv 4. Takes a connection and a reference wrapper to a container - Unlike `write`, `insert` does not create tables automatically - you must create tables separately using `create_table` - The insert operation is atomic within a transaction -- When using reference wrappers (`std::ref`), the data is not copied, which can be more efficient for large datasets \ No newline at end of file +- When using reference wrappers (`std::ref`), the data is not copied, which can be more efficient for large datasets