Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e2b880a
Added the DuckDB dependency
liuzicheng1987 Sep 16, 2025
41b9412
Added the DuckDBConnection
liuzicheng1987 Sep 16, 2025
ed38ddb
Started developing the Connection object
liuzicheng1987 Sep 17, 2025
8987fcb
Added the iterator
liuzicheng1987 Sep 21, 2025
9ba38f4
Merge branch 'main' into f/duckdb
liuzicheng1987 Oct 11, 2025
26f5ea6
Adapted DuckDB to the new structure
liuzicheng1987 Oct 11, 2025
076324d
Started writing the append_value function
liuzicheng1987 Oct 11, 2025
62bace8
Added error handling
liuzicheng1987 Oct 11, 2025
d2e5d0a
Writing data into DuckDB appears to work
liuzicheng1987 Oct 11, 2025
831855e
Began developing the Iterator
liuzicheng1987 Oct 11, 2025
36b9b2e
Continued writing the iterator
liuzicheng1987 Oct 12, 2025
17d1ac4
Merge branch 'main' into f/duckdb
liuzicheng1987 Nov 8, 2025
83b891d
Adapted DuckDB to recent changes
liuzicheng1987 Nov 8, 2025
fcade3d
Implemented the first reader
liuzicheng1987 Nov 8, 2025
4b8f58d
Added the raw type
liuzicheng1987 Nov 8, 2025
65f1bb7
Revert "Added the raw type"
liuzicheng1987 Nov 8, 2025
a8ed2d1
Made sure we can check more complex types
liuzicheng1987 Nov 8, 2025
9d2d17b
Added type checks
liuzicheng1987 Nov 9, 2025
b20f211
Added NULL checks
liuzicheng1987 Nov 9, 2025
b833423
More sophisticated approach for appender_create
liuzicheng1987 Nov 9, 2025
a56edd6
More efficient (and more idiomatic) approach to write
liuzicheng1987 Nov 9, 2025
1db68cd
Made sure the INSERT statement works as expected
liuzicheng1987 Nov 9, 2025
8a69cad
Began developing the autoincr primary key
liuzicheng1987 Nov 9, 2025
a818c6f
Use RAII for the appender and results
liuzicheng1987 Nov 9, 2025
be595a7
Reworked the appender
liuzicheng1987 Nov 11, 2025
a8038d2
Added auto incr primary keys
liuzicheng1987 Nov 11, 2025
9b3fc65
Merge branch 'f/duckdb' of github.com:getml/sqlgen into f/duckdb
liuzicheng1987 Nov 11, 2025
d4e7d25
Fixed typos
liuzicheng1987 Nov 11, 2025
777e7fa
Add tests
liuzicheng1987 Nov 11, 2025
7318b03
Some fixes
liuzicheng1987 Nov 15, 2025
c78bdaf
Added ColName
liuzicheng1987 Nov 15, 2025
c8b5f5a
Added numeric casting
liuzicheng1987 Nov 15, 2025
46d7f8d
Destroy chunk pointers
liuzicheng1987 Nov 15, 2025
b122e3c
Handle duckdb_hugeint
liuzicheng1987 Nov 15, 2025
0434d0f
Fixed some tests
liuzicheng1987 Nov 15, 2025
d67d4c3
Fixed table schema
liuzicheng1987 Nov 15, 2025
930e28b
Fixed more tests
liuzicheng1987 Nov 15, 2025
8ad7591
Handling timestamps and dates
liuzicheng1987 Nov 15, 2025
7b57586
Cast date to timestamp and vice-versa
liuzicheng1987 Nov 15, 2025
82d0c5f
Fixed yet more tests
liuzicheng1987 Nov 15, 2025
f291067
Make sure we properly handle contraint errors
liuzicheng1987 Nov 15, 2025
a4399a5
Started adding enums
liuzicheng1987 Nov 16, 2025
0655076
Added more tests for enums
liuzicheng1987 Nov 17, 2025
525d297
Added support for struct flattening
liuzicheng1987 Nov 17, 2025
088d64a
Added support for JSON
liuzicheng1987 Nov 18, 2025
3f85a9b
Added a test for dynamic types
liuzicheng1987 Nov 18, 2025
2244aa2
Added documentation
liuzicheng1987 Nov 18, 2025
11f810e
Updated the documentation for sqlgen::Dynamic
liuzicheng1987 Nov 18, 2025
b66e128
Removed relative includes
liuzicheng1987 Nov 19, 2025
9bee2c5
Get rid of the headers on Windows
liuzicheng1987 Nov 19, 2025
b1009d1
Add SQLGEN_API tag
google-labs-jules[bot] Nov 19, 2025
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
7 changes: 0 additions & 7 deletions .github/workflows/windows-cxx20-vcpkg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ jobs:
- db: postgres
- db: sqlite
- db: mysql
- db: headers
name: "(windows-${{ matrix.db }})"
concurrency:
group: ci-${{ github.ref }}-windows-${{ matrix.db }}
Expand All @@ -34,11 +33,6 @@ jobs:
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- uses: ilammy/msvc-dev-cmd@v1
- uses: lukka/run-vcpkg@v11
- name: Compile
if: matrix.db == 'headers'
run: |
cmake -S . -B build -DCMAKE_CXX_STANDARD=20 -DSQLGEN_CHECK_HEADERS=ON
cmake --build build --config Release -j4
- name: Compile
if: matrix.db == 'postgres'
run: |
Expand All @@ -55,6 +49,5 @@ jobs:
cmake -S . -B build -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-windows-release
cmake --build build --config Release -j4
- name: Run tests
if: matrix.db != 'headers'
run: |
ctest --test-dir build --output-on-failure
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

option(SQLGEN_BUILD_SHARED "Build shared library" ${BUILD_SHARED_LIBS})

option(SQLGEN_DUCKDB "Enable DuckDB support" OFF)

option(SQLGEN_MYSQL "Enable MySQL support" OFF)

option(SQLGEN_POSTGRES "Enable PostgreSQL support" ON) # enabled by default
Expand All @@ -30,6 +32,10 @@ if (SQLGEN_USE_VCPKG)
list(APPEND VCPKG_MANIFEST_FEATURES "tests")
endif()

if (SQLGEN_DUCKDB OR SQLGEN_CHECK_HEADERS)
list(APPEND VCPKG_MANIFEST_FEATURES "duckdb")
endif()

if (SQLGEN_MYSQL OR SQLGEN_CHECK_HEADERS)
list(APPEND VCPKG_MANIFEST_FEATURES "mysql")
endif()
Expand Down Expand Up @@ -89,6 +95,16 @@ target_include_directories(
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)


if (SQLGEN_DUCKDB OR SQLGEN_CHECK_HEADERS)
list(APPEND SQLGEN_SOURCES src/sqlgen_duckdb.cpp)
if (NOT TARGET DuckDB)
find_package(DuckDB REQUIRED)
endif()
target_link_libraries(sqlgen PUBLIC $<IF:$<TARGET_EXISTS:duckdb>,duckdb,duckdb_static>)
endif()


if(SQLGEN_MYSQL OR SQLGEN_CHECK_HEADERS)
list(APPEND SQLGEN_SOURCES src/sqlgen_mysql.cpp)
if (SQLGEN_USE_VCPKG)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The following table lists the databases currently supported by sqlgen and the un

| Database | Library | Version | License | Remarks |
|---------------|--------------------------------------------------------------------------|--------------|---------------| -----------------------------------------------------|
| DuckDB | [duckdb](https://github.com/duckdb/duckdb) | >= 1.4.1 | MIT | |
| MySQL/MariaDB | [libmariadb](https://github.com/mariadb-corporation/mariadb-connector-c) | >= 3.4.5 | LGPL | |
| PostgreSQL | [libpq](https://github.com/postgres/postgres) | >= 16.4 | PostgreSQL | Will work for all libpq-compatible databases |
| sqlite | [sqlite](https://sqlite.org/index.html) | >= 3.49.1 | Public Domain | |
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Welcome to the sqlgen documentation. This guide provides detailed information ab

## Supported Databases

- [DuckDB](duckdb.md) - How to interact with DuckDB
- [MySQL](mysql.md) - How to interact with MariaDB and MySQL
- [PostgreSQL](postgres.md) - How to interact with PostgreSQL and compatible databases (Redshift, Aurora, Greenplum, CockroachDB, ...)
- [SQLite](sqlite.md) - How to interact with SQLite3
Expand Down
142 changes: 142 additions & 0 deletions docs/duckdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
∂# `sqlgen::duckdb`

The `sqlgen::duckdb` module provides a type-safe and efficient interface for interacting with DuckDB databases. It implements the core database operations through a connection-based API with support for prepared statements, transactions, and efficient data iteration.

## Usage

### Basic Connection

Create a connection to a DuckDB database:

```cpp
// Connect to an in-memory database
const auto conn = sqlgen::duckdb::connect();

// Connect to a file-based database
const auto conn = sqlgen::duckdb::connect("database.db");
```

The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::duckdb::Connection>>`, which is useful for error handling:

```cpp
// Handle connection errors
const auto conn = sqlgen::duckdb::connect("database.db");
if (!conn) {
// Handle error...
return;
}

using namespace sqlgen;
using namespace sqlgen::literals;

const auto query = sqlgen::read<std::vector<Person>> |
where("age"_c < 18 and "first_name"_c != "Hugo");

// Use the connection
const auto minors = query(conn);
```

### Basic Operations

Write data to the database:

```cpp
struct Person {
sqlgen::PrimaryKey<uint32_t> id;
std::string first_name;
std::string last_name;
int age;
};

const auto people = std::vector<Person>{
Person{.id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45},
Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10},
Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}
};

// Write data to database
const auto result = sqlgen::write(conn, people);
```

Read data with filtering and ordering:

```cpp
using namespace sqlgen;
using namespace sqlgen::literals;

// Read all people ordered by age
const auto all_people = sqlgen::read<std::vector<Person>> |
order_by("age"_c);

// Read minors only
const auto minors = sqlgen::read<std::vector<Person>> |
where("age"_c < 18) |
order_by("age"_c);

// Use the queries
const auto result1 = all_people(conn);
const auto result2 = minors(conn);
```

### Transactions

Perform operations within transactions:

```cpp
using namespace sqlgen;
using namespace sqlgen::literals;

// Delete a person and update another in a transaction
const auto delete_hugo = delete_from<Person> |
where("first_name"_c == "Hugo");

const auto update_homer = update<Person>("age"_c.set(46)) |
where("first_name"_c == "Homer");

const auto result = begin_transaction(conn)
.and_then(delete_hugo)
.and_then(update_homer)
.and_then(commit)
.value();
```

### Update Operations

Update data in a table:

```cpp
using namespace sqlgen;
using namespace sqlgen::literals;

// Update multiple columns
const auto query = update<Person>("first_name"_c.set("last_name"_c), "age"_c.set(100)) |
where("first_name"_c == "Hugo");

query(conn).value();
```

## Notes

- The module provides a type-safe interface for DuckDB operations
- All operations return `sqlgen::Result<T>` for error handling
- Prepared statements are used for efficient query execution
- The iterator interface supports batch processing of results
- SQL generation adapts to DuckDB's dialect
- The module supports:
- In-memory and file-based databases
- Transactions (begin, commit, rollback)
- Efficient batch operations
- Type-safe SQL generation
- Error handling through `Result<T>`
- Resource management through `Ref<T>`
- Auto-incrementing primary keys
- Various data types including VARCHAR, TIMESTAMP, DATE
- Complex queries with WHERE clauses, ORDER BY, LIMIT, JOINs
- LIKE and pattern matching operations
- Mathematical operations and string functions
- JSON data types
- Foreign keys and referential integrity
- Unique constraints
- Views and materialized views
- Indexes
```
54 changes: 54 additions & 0 deletions docs/dynamic.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,50 @@ struct Parser<boost::uuids::uuid> {
} // namespace sqlgen::parsing
```

### DuckDB parser specialization

**Important:** If you're using DuckDB, you must also implement a separate parser specialization in the `sqlgen::duckdb::parsing` namespace. This is required for performance reasons, as DuckDB uses its own native types and appender interface.

The DuckDB parser has a different interface than the generic parser:

```cpp
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <sqlgen/duckdb.hpp>
#include <exception>

namespace sqlgen::duckdb::parsing {

template <>
struct Parser<boost::uuids::uuid> {
using ResultingType = duckdb_string_t;

static Result<boost::uuids::uuid> read(const ResultingType* _r) noexcept {
return Parser<std::string>::read(_r).and_then(
[&](const std::string& _str) -> Result<boost::uuids::uuid> {
try {
return boost::lexical_cast<boost::uuids::uuid>(_str);
} catch (const std::exception& e) {
return error(e.what());
}
});
}

static Result<Nothing> write(const boost::uuids::uuid& _u,
duckdb_appender _appender) noexcept {
return Parser<std::string>::write(boost::uuids::to_string(_u), _appender);
}
};

} // namespace sqlgen::duckdb::parsing
```

Key differences from the generic parser:
- `read` takes `const ResultingType*` (where `ResultingType = duckdb_string_t`) instead of `const std::optional<std::string>&`
- `write` takes a `duckdb_appender` parameter and returns `Result<Nothing>` instead of `std::optional<std::string>`
- No `to_type()` method is required (the generic parser's `to_type()` is used for schema generation)

The second step is to specialize `sqlgen::transpilation::ToValue` for `boost::uuids::uuid` and implement `operator()`:

```cpp
Expand Down Expand Up @@ -157,6 +201,15 @@ static dynamic::Type to_type() noexcept {
}
```

- DuckDB:
```cpp
static dynamic::Type to_type() noexcept {
return sqlgen::dynamic::types::Dynamic{"TEXT"};
}
```

Note: For DuckDB, you must also implement the `sqlgen::duckdb::parsing::Parser` specialization as shown in the DuckDB parser specialization section above.

## Parser specialization requirements

Specializing `sqlgen::parsing::Parser<T>` requires three methods. These guidelines help ensure correctness and portability:
Expand Down Expand Up @@ -200,4 +253,5 @@ Additional best practices:
- Works with all operations: `create_table`, `insert`, `select`, `update`, `delete`
- The type name is passed directly to the database; ensure it is valid for the target dialect
- Keep specializations in the `sqlgen::parsing` namespace
- **DuckDB users:** You must implement both `sqlgen::parsing::Parser` and `sqlgen::duckdb::parsing::Parser` specializations for your custom type

2 changes: 1 addition & 1 deletion docs/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const auto creds = sqlgen::mysql::Credentials{
const auto conn = sqlgen::mysql::connect(creds);
```

The connection is wrapped in a `sqlgen::Result<Ref<Connection>>` for error handling:
The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::mysql::Connection>>`, which is useful for error handling:

```cpp
// Handle connection errors
Expand Down
2 changes: 1 addition & 1 deletion docs/postgres.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const auto creds = sqlgen::postgres::Credentials{
const auto conn = sqlgen::postgres::connect(creds);
```

The connection is wrapped in a `sqlgen::Result<Ref<Connection>>` for error handling:
The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::postgres::Connection>>`, which is useful for error handling:

```cpp
// Handle connection errors
Expand Down
2 changes: 1 addition & 1 deletion docs/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const auto conn = sqlgen::sqlite::connect();
const auto conn = sqlgen::sqlite::connect("database.db");
```

The connection is wrapped in a `sqlgen::Result<Ref<Connection>>` for error handling:
The type of `conn` is `sqlgen::Result<sqlgen::Ref<sqlgen::sqlite::Connection>>`, which is useful for error handling:

```cpp
// Handle connection errors
Expand Down
8 changes: 8 additions & 0 deletions include/sqlgen/duckdb.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef SQLGEN_DUCKDB_HPP_
#define SQLGEN_DUCKDB_HPP_

#include "../sqlgen.hpp"
#include "duckdb/connect.hpp"
#include "duckdb/to_sql.hpp"

#endif
33 changes: 33 additions & 0 deletions include/sqlgen/duckdb/ColumnData.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef SQLGEN_DUCKDB_COLUMNDATA_HPP_
#define SQLGEN_DUCKDB_COLUMNDATA_HPP_

#include <duckdb.h>

#include <memory>
#include <rfl.hpp>
#include <rfl/internal/StringLiteral.hpp>
#include <vector>

namespace sqlgen::duckdb {

template <class T, class _ColName>
struct ColumnData {
using ColName = _ColName;

duckdb_vector vec;
T *data;
uint64_t *validity;

// This is only needed if the data returned by DuckDB is not of the
// same type as T, but can be converted to T. In this case,
// data actually points to ptr->data(). Otherwise, ptr is a nullptr.
std::shared_ptr<std::vector<T>> ptr;

bool is_not_null(idx_t _i) const {
return (validity == nullptr) || duckdb_validity_row_is_valid(validity, _i);
}
};

} // namespace sqlgen::duckdb

#endif
Loading
Loading