AIGC:
Label: "1"
ContentProducer: 001191440300708461136T1XGW3
ProduceID: aed9bd2c2f3c6f83a54981e0ddb92ca2_8473260bcdfb11f18a50525400cd780f
ReservedCode1: cI0zLrooC0vi+C5ifCc68IcqjKSpNgmiECl40/R0n/HoaGE78G2vEUn3WHKk9qPOaYa5NIWyjYR7uFC7FWvWrNMitTXrYpA3S7aiLGYhjutk4Gjmo28xdwosX9UZ7IF9Xu6BbK+WEmeB6XuVKB2tnRLuedznInEisibatti24Ck1QHc/kZ/8MnJP2BE=
ContentPropagator: 001191440300708461136T1XGW3
PropagateID: aed9bd2c2f3c6f83a54981e0ddb92ca2_8473260bcdfb11f18a50525400cd780f
ReservedCode2: cI0zLrooC0vi+C5ifCc68IcqjKSpNgmiECl40/R0n/HoaGE78G2vEUn3WHKk9qPOaYa5NIWyjYR7uFC7FWvWrNMitTXrYpA3S7aiLGYhjutk4Gjmo28xdwosX9UZ7IF9Xu6BbK+WEmeB6XuVKB2tnRLuedznInEisibatti24Ck1QHc/kZ/8MnJP2BE=
DB::DuckDB
A DuckDB driver for Raku's DB interface,
implemented as a direct NativeCall binding over
the DuckDB C API — no C shim, no FFI glue layer, no external process.
use DB::DuckDB;
my $db = DB::DuckDB.new; # in-memory
# my $db = DB::DuckDB.new(path => 'data.duckdb'); # file backed
say $db.query('select 42').value; # 42
$db.execute('create table foo (id integer, name varchar)');
$db.execute('insert into foo values (?, ?)', 1, 'a');
for $db.query('select * from foo').hashes -> %row {
say %row<id>, %row<name>;
}
Status
Early but working. Implemented and covered by tests:
- driver:
new, connect, query, execute, close, library-version - connection:
prepare, prepare-nocache, query, execute, begin, commit, rollback, ping, finish - statement: positional
? and named $name placeholders (DuckDB syntax; :name is not supported), binding and rebinding, execute - result:
value, row, array, hash, arrays, hashes, names, rows, rows-changed, free
Results are read chunk by chunk out of the vectors and decoded in Raku (DB::DuckDB::Decoder),
because 2.0 stopped rendering several types through duckdb_value_varchar.
Not implemented (yet): callbacks/UDFs, extension loading, Appender, and anything that needs a
by-value struct return (see Design notes).
Requirements
- Raku 6.d / Rakudo (tested on Rakudo 2025.06, Windows 11)
libduckdb (tested against v2.0.0, works with v1.5.x too), plus the DB distribution (zef install DB)
How the library is located
%DB_DUCKDB_LIB environment variable, if it points at an existing fileresources/libraries/duckdb inside the distribution, if installed with one- next to the running
raku executable — this covers C:\rakudo\bin\duckdb.dll /usr/local/lib, /usr/lib, /opt/homebrew/lib- bare
duckdb, letting the OS loader search
$env:DB_DUCKDB_LIB = 'C:\rakudo\bin\duckdb.dll'
Install
zef install .
Or run straight from a checkout:
raku -Ilib -e 'use DB::DuckDB; say DB::DuckDB.new.query("select version()").value'
Type mapping (DuckDB → Raku)
| DuckDB type | Raku value |
|---|
BOOLEAN | Bool |
TINYINT / SMALLINT / INTEGER / BIGINT | Int |
UTINYINT / USMALLINT / UINTEGER / UBIGINT | Int |
FLOAT / DOUBLE | Num |
VARCHAR | Str |
NULL (any type) | Any (undefined) |
DATE, TIME, TIMESTAMP, TIMESTAMP_S/_MS/_NS, TIME_TZ, TIMESTAMP_TZ, TIMESTAMP_TZ_NS | Str in DuckDB's own ISO rendering (session time zone honoured) |
DECIMAL, HUGEINT / UHUGEINT | Str, exact and lossless |
BLOB, UUID, ENUM, INTERVAL, LIST, STRUCT, MAP, UNION, BIT, BIGNUM, ARRAY | Str in DuckDB's textual rendering |
SQLNULL type | Any |
Raku → DuckDB (bind): Bool → BOOLEAN, Int → BIGINT, Num / Rat / FatRat → DOUBLE,
Str → VARCHAR, Blob → BLOB, undefined → NULL, anything else → its .Str.
Dates and decimals are deliberately not auto-coerced into Raku Date/DateTime/Rat:
DuckDB's own textual rendering is exact for every type, while the duckdb_value_* family that
would return real date/decimal structures is deprecated and partly by-value. Exactness beats
convenience here — convert at the call site when you need it.
Design notes
- One database instance per driver.
connect creates connections that all share a single
duckdb_database. Opening :memory: twice would silently create two independent databases,
and opening a file path twice trips DuckDB's instance/file locking, so the driver opens once
and guards it with $!opened. duckdb_result is mapped as a fixed-layout struct (48 bytes on LP64: three idx_t plus
three pointers) and its size is asserted at load time, so an ABI change fails loudly instead
of corrupting memory.- Results are read through the chunk/vector API.
duckdb_result_chunk_count /
duckdb_result_get_chunk hand back a duckdb_data_chunk pointer, and each vector's data
buffer is read in place; the structs living inside those buffers are dissected field by field
through uint32/uint64 views, since NativeCall faults on CStruct array element access on
Windows. No struct is ever returned by value. - Values are rendered exactly like DuckDB's own
CAST(x AS VARCHAR), not coerced into Raku
Date/DateTime/Rat. TIMESTAMP_TZ is the one piece of session state involved: the vector
holds UTC microseconds, so the instant is shifted by the session zone's offset (synced from
strftime(..., '%z') on construction and refreshed after SET TimeZone) and only then
labelled with that offset. - Prepared statements are cached per connection by the
DB role; statements are destroyed
when their connection is dropped. close also tears down connections the caller forgot to
finish, because DuckDB will not release the file lock until every connection and prepared
statement is gone. - Works on Windows without an import library: the DLL is loaded at runtime, so the MSVC
.lib that ships with the C API is not needed.
Tests
raku -Ilib t/01-basic.rakutest # types, placeholders, transactions, errors
raku -Ilib t/02-file.rakutest # file database, multiple connections, reopen
raku -Ilib t/03-types.rakutest # every 2.0 type vs. DuckDB's own text rendering,
# NULLs, chunked results, session time zone, uuid
License
Artistic-2.0
(内容由AI生成,仅供参考)
(内容由AI生成,仅供参考)