Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 33 additions & 14 deletions docs/insert.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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<Ref<Connection>>)
template <class ContainerType>
auto insert_or_replace(const auto& conn, const ContainerType& data);

// Use as a pipeline element (returns a callable that accepts a connection)
template <class ContainerType>
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<Person>({
Expand All @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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
- When using reference wrappers (`std::ref`), the data is not copied, which can be more efficient for large datasets
Loading