Minor code beautifications for DuckDB (#94)

This commit is contained in:
Dr. Patrick Urbanke (劉自成)
2025-11-25 20:08:05 +01:00
committed by GitHub
parent b2e4cba591
commit e85f262f6d
14 changed files with 200 additions and 118 deletions

View File

@@ -60,6 +60,22 @@ jobs:
- compiler: gcc
compiler-version: 14
db: mysql
- compiler: llvm
compiler-version: 16
db: duckdb
- compiler: llvm
compiler-version: 18
db: duckdb
- compiler: gcc
compiler-version: 11
additional-dep: "g++-11"
db: duckdb
- compiler: gcc
compiler-version: 12
db: duckdb
- compiler: gcc
compiler-version: 14
db: duckdb
name: "${{ github.job }} (${{ matrix.compiler }}-${{ matrix.compiler-version }}-${{ matrix.db }})"
concurrency:
group: ci-${{ github.ref }}-${{ github.job }}-${{ matrix.compiler }}-${{ matrix.compiler-version }}-${{ matrix.db }}
@@ -101,7 +117,22 @@ jobs:
sudo ln -s $(which ccache) /usr/local/bin/$CC
sudo ln -s $(which ccache) /usr/local/bin/$CXX
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_SQLITE3=OFF
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_SQLITE3=OFF
cmake --build build
- name: Compile
if: matrix.db == 'duckdb'
run: |
if [[ "${{ matrix.compiler }}" == "llvm" ]]; then
export CC=clang-${{ matrix.compiler-version }}
export CXX=clang++-${{ matrix.compiler-version }}
elif [[ "${{ matrix.compiler }}" == "gcc" ]]; then
export CC=gcc-${{ matrix.compiler-version }}
export CXX=g++-${{ matrix.compiler-version }}
fi
sudo ln -s $(which ccache) /usr/local/bin/$CC
sudo ln -s $(which ccache) /usr/local/bin/$CXX
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_DUCKDB=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF
cmake --build build
- name: Compile
if: matrix.db == 'sqlite'
@@ -116,7 +147,7 @@ jobs:
sudo ln -s $(which ccache) /usr/local/bin/$CC
sudo ln -s $(which ccache) /usr/local/bin/$CXX
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_CHECK_HEADERS=ON
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_CHECK_HEADERS=ON
cmake --build build
- name: Compile
if: matrix.db == 'mysql'
@@ -131,7 +162,7 @@ jobs:
sudo ln -s $(which ccache) /usr/local/bin/$CC
sudo ln -s $(which ccache) /usr/local/bin/$CXX
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-linux-dynamic
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-linux-dynamic
cmake --build build
- name: Set up postgres
if: matrix.db == 'postgres'

View File

@@ -18,12 +18,8 @@ jobs:
db: sqlite
- os: "macos-latest"
db: mysql
- os: "macos-13"
db: postgres
- os: "macos-13"
db: sqlite
- os: "macos-13"
db: mysql
- os: "macos-latest"
db: duckdb
name: "${{ github.job }} (${{ matrix.os }}-${{ matrix.db }})"
concurrency:
group: ci-${{ github.ref }}-${{ github.job }}-${{ matrix.os }}-${{ matrix.db }}
@@ -50,8 +46,21 @@ jobs:
- name: Run vcpkg
uses: lukka/run-vcpkg@v11
- name: Install ninja
run: brew install ninja autoconf bison flex
run: brew install ninja autoconf bison flex pkg-config icu4c
if: matrix.os == 'macos-latest'
- name: Compile
if: matrix.db == 'duckdb'
env:
CC: clang
CXX: clang++
run: |
if [[ "${{ matrix.os == 'macos-latest' }}" == "true" ]]; then
export VCPKG_FORCE_SYSTEM_BINARIES=arm
export CMAKE_GENERATOR=Ninja
fi
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_DUCKDB=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF
cmake --build build -j 4
- name: Compile
if: matrix.db == 'postgres'
env:
@@ -64,7 +73,7 @@ jobs:
export MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion)"
fi
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_SQLITE3=OFF -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_SQLITE3=OFF -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
cmake --build build -j 4
- name: Compile
if: matrix.db == 'sqlite'
@@ -77,7 +86,7 @@ jobs:
export CMAKE_GENERATOR=Ninja
fi
$CXX --version
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_POSTGRES=OFF
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_POSTGRES=OFF
cmake --build build -j 4
- name: Compile
if: matrix.db == 'mysql'
@@ -91,9 +100,9 @@ jobs:
fi
$CXX --version
if [[ "${{ matrix.os == 'macos-latest' }}" == "true" ]]; then
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=arm64-osx-dynamic -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=arm64-osx-dynamic -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
else
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-osx-dynamic -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
cmake -S . -B build -G Ninja -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_MYSQL=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF -DBUILD_SHARED_LIBS=ON -DVCPKG_TARGET_TRIPLET=x64-osx-dynamic -DSQLGEN_BUILD_DRY_TESTS_ONLY=ON
fi
cmake --build build -j 4
- name: Run tests

View File

@@ -11,6 +11,7 @@ jobs:
fail-fast: false
matrix:
include:
- db: duckdb
- db: postgres
- db: sqlite
- db: mysql
@@ -33,6 +34,11 @@ 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 == 'duckdb'
run: |
cmake -S . -B build -DCMAKE_CXX_STANDARD=20 -DCMAKE_BUILD_TYPE=Release -DSQLGEN_BUILD_TESTS=ON -DSQLGEN_DUCKDB=ON -DSQLGEN_POSTGRES=OFF -DSQLGEN_SQLITE3=OFF
cmake --build build --config Release -j4
- name: Compile
if: matrix.db == 'postgres'
run: |

View File

@@ -6,6 +6,7 @@
[![Generic badge](https://img.shields.io/badge/gcc-11+-blue.svg)](https://shields.io/)
[![Generic badge](https://img.shields.io/badge/clang-14+-blue.svg)](https://shields.io/)
[![Generic badge](https://img.shields.io/badge/MSVC-17+-blue.svg)](https://shields.io/)
[![Conan Center](https://img.shields.io/conan/v/sqlgen)](https://conan.io/center/recipes/sqlgen)
**📖 Documentation**: [Click here](docs/README.md)
@@ -31,14 +32,21 @@ 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 | |
| DuckDB | [duckdb](https://github.com/duckdb/duckdb) | >= 1.4.2 | 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 | |
## Quick Start
### Installation using vcpkg
### Install using vcpkg or Conan
You can install the latest release of sqlgen
using either [vcpkg](https://vcpkg.io/en/package/sqlgen) or [Conan](https://conan.io/center/recipes/sqlgen).
### Build using vcpkg
Alternatively, you can build sqlgen from source using vcpkg:
1. Make sure you have the required dependencies installed (skip this step on Windows):
```bash
@@ -66,7 +74,7 @@ Run `./vcpkg/vcpkg help triplets` to view all supported triplets.
Common triplets for shared libraries are `x64-linux-dynamic`,
`arm64-osx-dynamic` or `x64-osx-dynamic`.
Add `-DSQLGEN_MYSQL=ON` to support MySQL/MariaDB.
Add `-DSQLGEN_MYSQL=ON` to support MySQL/MariaDB. Add `-DSQLGEN_DUCKDB=ON` to support DuckDB.
4. Include in your CMake project:
```cmake
@@ -74,7 +82,9 @@ find_package(sqlgen REQUIRED)
target_link_libraries(your_target PRIVATE sqlgen::sqlgen)
```
### Installation using Conan
### Build using Conan
You can also build sqlgen from source using Conan:
1. Install Conan (assuming you have Python and pipx installed):

View File

@@ -19,7 +19,7 @@ class SQLGenConan(ConanFile):
license = "MIT"
url = "https://github.com/conan-io/conan-center-index"
homepage = "https://github.com/getml/sqlgen"
topics = ("postgres", "sqlite", "orm")
topics = ("duckdb", "mypy", "postgres", "sqlite", "orm")
package_type = "library"
settings = "os", "arch", "compiler", "build_type"
@@ -29,6 +29,7 @@ class SQLGenConan(ConanFile):
"with_mysql": [True, False],
"with_postgres": [True, False],
"with_sqlite3": [True, False],
"with_duckdb": [True, False],
}
default_options = {
"shared": False,
@@ -36,6 +37,7 @@ class SQLGenConan(ConanFile):
"with_mysql": False,
"with_postgres": True,
"with_sqlite3": True,
"with_duckdb": False,
}
def config_options(self):
@@ -54,6 +56,8 @@ class SQLGenConan(ConanFile):
self.requires("libpq/17.5", transitive_headers=True)
if self.options.with_sqlite3:
self.requires("sqlite3/3.49.1", transitive_headers=True)
if self.options.with_duckdb:
self.requires("duckdb/1.1.3", transitive_headers=True)
def build_requirements(self):
self.tool_requires("cmake/[>=3.23 <4]")
@@ -78,6 +82,7 @@ class SQLGenConan(ConanFile):
tc.cache_variables["SQLGEN_MYSQL"] = self.options.with_mysql
tc.cache_variables["SQLGEN_POSTGRES"] = self.options.with_postgres
tc.cache_variables["SQLGEN_SQLITE3"] = self.options.with_sqlite3
tc.cache_variables["SQLGEN_DUCKDB"] = self.options.with_duckdb
tc.cache_variables["SQLGEN_USE_VCPKG"] = False
tc.generate()

View File

@@ -17,32 +17,13 @@ class SQLGEN_API DuckDBAppender {
static Result<Ref<DuckDBAppender>> make(
const std::string& _sql, const ConnPtr& _conn,
const std::vector<const char*>& _columns,
const std::vector<duckdb_logical_type>& _types) {
try {
return Ref<DuckDBAppender>::make(_sql, _conn, _columns, _types);
} catch (const std::exception& e) {
return error(e.what());
}
}
const std::vector<duckdb_logical_type>& _types);
DuckDBAppender(const std::string& _sql, const ConnPtr& _conn,
std::vector<const char*> _columns,
std::vector<duckdb_logical_type> _types)
: destroy_(false) {
if (duckdb_appender_create_query(
_conn->conn(), _sql.c_str(), static_cast<idx_t>(_columns.size()),
_types.data(), "sqlgen_appended_data", _columns.data(),
&appender_) == DuckDBError) {
throw std::runtime_error("Could not create appender.");
}
destroy_ = true;
}
std::vector<duckdb_logical_type> _types);
~DuckDBAppender() {
if (destroy_) {
duckdb_appender_destroy(&appender_);
}
}
~DuckDBAppender();
DuckDBAppender(const DuckDBAppender& _other) = delete;
@@ -53,25 +34,11 @@ class SQLGEN_API DuckDBAppender {
DuckDBAppender& operator=(const DuckDBAppender& _other) = delete;
DuckDBAppender& operator=(DuckDBAppender&& _other) {
if (this == &_other) {
return *this;
}
destroy_ = _other.destroy_;
appender_ = _other.appender_;
_other.destroy_ = false;
return *this;
}
DuckDBAppender& operator=(DuckDBAppender&& _other);
duckdb_appender& appender() { return appender_; }
Result<Nothing> close() {
const auto state = duckdb_appender_close(appender_);
if (state == DuckDBError) {
return error(duckdb_appender_error(appender_));
}
return Nothing{};
}
Result<Nothing> close();
private:
bool destroy_;

View File

@@ -15,27 +15,11 @@ class SQLGEN_API DuckDBResult {
public:
static Result<Ref<DuckDBResult>> make(const std::string& _query,
const ConnPtr& _conn) {
try {
return Ref<DuckDBResult>::make(_query, _conn);
} catch (const std::exception& e) {
return error(e.what());
}
}
const ConnPtr& _conn);
DuckDBResult(const std::string& _query, const ConnPtr& _conn)
: destroy_(false) {
if (duckdb_query(_conn->conn(), _query.c_str(), &res_) == DuckDBError) {
throw std::runtime_error(duckdb_result_error(&res_));
}
destroy_ = true;
}
DuckDBResult(const std::string& _query, const ConnPtr& _conn);
~DuckDBResult() {
if (destroy_) {
duckdb_destroy_result(&res_);
}
}
~DuckDBResult();
DuckDBResult(const DuckDBResult& _other) = delete;
@@ -46,15 +30,7 @@ class SQLGEN_API DuckDBResult {
DuckDBResult& operator=(const DuckDBResult& _other) = delete;
DuckDBResult& operator=(DuckDBResult&& _other) {
if (this == &_other) {
return *this;
}
destroy_ = _other.destroy_;
res_ = _other.res_;
_other.destroy_ = false;
return *this;
}
DuckDBResult& operator=(DuckDBResult&& _other);
duckdb_result& res() { return res_; }

View File

@@ -1,5 +1,7 @@
@PACKAGE_INIT@
set(SQLGEN_DUCKDB @SQLGEN_DUCKDB@)
set(SQLGEN_MYSQL @SQLGEN_MYSQL@)
set(SQLGEN_POSTGRES @SQLGEN_POSTGRES@)
set(SQLGEN_SQLITE3 @SQLGEN_SQLITE3@)
@@ -9,6 +11,14 @@ include(${CMAKE_CURRENT_LIST_DIR}/sqlgen-exports.cmake)
find_dependency(reflectcpp)
if(SQLGEN_DUCKDB)
find_dependency(DuckDB)
endif()
if(SQLGEN_MYSQL)
find_dependency(unofficial-libmariadb)
endif()
if(SQLGEN_POSTGRES)
find_dependency(PostgreSQL)
endif()

View File

@@ -0,0 +1,53 @@
#include "sqlgen/duckdb/DuckDBAppender.hpp"
namespace sqlgen::duckdb {
Result<Ref<DuckDBAppender>> DuckDBAppender::make(
const std::string& _sql, const ConnPtr& _conn,
const std::vector<const char*>& _columns,
const std::vector<duckdb_logical_type>& _types) {
try {
return Ref<DuckDBAppender>::make(_sql, _conn, _columns, _types);
} catch (const std::exception& e) {
return error(e.what());
}
}
DuckDBAppender::DuckDBAppender(const std::string& _sql, const ConnPtr& _conn,
std::vector<const char*> _columns,
std::vector<duckdb_logical_type> _types)
: destroy_(false) {
if (duckdb_appender_create_query(
_conn->conn(), _sql.c_str(), static_cast<idx_t>(_columns.size()),
_types.data(), "sqlgen_appended_data", _columns.data(),
&appender_) == DuckDBError) {
throw std::runtime_error("Could not create appender.");
}
destroy_ = true;
}
DuckDBAppender::~DuckDBAppender() {
if (destroy_) {
duckdb_appender_destroy(&appender_);
}
}
DuckDBAppender& DuckDBAppender::operator=(DuckDBAppender&& _other) {
if (this == &_other) {
return *this;
}
destroy_ = _other.destroy_;
appender_ = _other.appender_;
_other.destroy_ = false;
return *this;
}
Result<Nothing> DuckDBAppender::close() {
const auto state = duckdb_appender_close(appender_);
if (state == DuckDBError) {
return error(duckdb_appender_error(appender_));
}
return Nothing{};
}
} // namespace sqlgen::duckdb

View File

@@ -0,0 +1,38 @@
#include "sqlgen/duckdb/DuckDBResult.hpp"
namespace sqlgen::duckdb {
Result<Ref<DuckDBResult>> DuckDBResult::make(const std::string& _query,
const ConnPtr& _conn) {
try {
return Ref<DuckDBResult>::make(_query, _conn);
} catch (const std::exception& e) {
return error(e.what());
}
}
DuckDBResult::DuckDBResult(const std::string& _query, const ConnPtr& _conn)
: destroy_(false) {
if (duckdb_query(_conn->conn(), _query.c_str(), &res_) == DuckDBError) {
throw std::runtime_error(duckdb_result_error(&res_));
}
destroy_ = true;
}
DuckDBResult::~DuckDBResult() {
if (destroy_) {
duckdb_destroy_result(&res_);
}
}
DuckDBResult& DuckDBResult::operator=(DuckDBResult&& _other) {
if (this == &_other) {
return *this;
}
destroy_ = _other.destroy_;
res_ = _other.res_;
_other.destroy_ = false;
return *this;
}
} // namespace sqlgen::duckdb

View File

@@ -1,27 +0,0 @@
#include "sqlgen/postgres/exec.hpp"
#include <ranges>
#include <rfl.hpp>
#include <sstream>
#include <stdexcept>
namespace sqlgen::postgres {
Result<Ref<PGresult>> exec(const Ref<PGconn>& _conn,
const std::string& _sql) noexcept {
const auto res = PQexec(_conn.get(), _sql.c_str());
const auto status = PQresultStatus(res);
if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK &&
status != PGRES_COPY_IN) {
const auto err =
error("Executing '" + _sql + "' failed: " + PQresultErrorMessage(res));
PQclear(res);
return err;
}
return Ref<PGresult>::make(std::shared_ptr<PGresult>(res, PQclear));
}
} // namespace sqlgen::postgres

View File

@@ -1,5 +1,5 @@
#include "sqlgen/duckdb/Connection.cpp"
#include "sqlgen/duckdb/DuckDBAppender.cpp"
#include "sqlgen/duckdb/DuckDBConnection.cpp"
// #include "sqlgen/duckdb/Iterator.cpp"
// #include "sqlgen/duckdb/exec.cpp"
#include "sqlgen/duckdb/DuckDBResult.cpp"
#include "sqlgen/duckdb/to_sql.cpp"

View File

@@ -46,7 +46,7 @@ TEST(duckdb, test_union_in_select) {
const auto united = sqlgen::unite<std::vector<User1>>(s1, s2, s3);
const auto sel = sqlgen::select_from(united, "name"_c, "age"_c) |
sqlgen::to<std::vector<User1>>;
sqlgen::order_by("name"_c) | sqlgen::to<std::vector<User1>>;
const auto result = sel(conn);
@@ -56,7 +56,7 @@ TEST(duckdb, test_union_in_select) {
EXPECT_EQ(
query,
R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")))");
R"(SELECT "name", "age" FROM (SELECT "name", "age" FROM (SELECT "name", "age" FROM "User1") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User2") UNION SELECT "name", "age" FROM (SELECT "name", "age" FROM "User3")) ORDER BY "name")");
EXPECT_EQ(users.size(), 3);
EXPECT_EQ(users.at(0).name, "Jane");

View File

@@ -11,8 +11,12 @@
"dependencies": [
{
"name": "duckdb",
"version>=": "1.4.1"
}
"version>=": "1.4.2"
},
{
"name":"icu",
"version>=":"78.1"
}
]
},
"mysql": {