DB::DuckDB — Raku-DB模块接口契约逐字报告
Raku DB 模块接口契约逐字报告
目标:为新建
DB::DuckDB驱动提供照抄级接口基线。 采集日期:2026-10-22 采集对象:本机 Rakudo(C:\rakudo\bin\raku.exe)已安装的DB发行版与其参考驱动DB::SQLite。
0. 定位结果与可信度说明
0.1 定位过程
| 检查位置 | 结果 |
|---|---|
C:\rakudo\share\perl6\site\lib\ | 无 *.rakumod(Get-ChildItem -Recurse -Filter *.rakumod 计数 = 0),仅有 .precomp\ 缓存 |
C:\rakudo\share\perl6\vendor\lib\ | 不存在 |
C:\rakudo\share\perl6\lib\ | 仅 Perl6\、Raku\(core) |
C:\Users\Admin\.raku\lib\ | 不存在 |
zef / ~/.raku/dist、site/dist | 存在 DB 与 DB::SQLite 的 META6 记录(见 §0.2) |
site/sources\<SHA1> | DB 与 DB::SQLite 全部 .rakumod 源文件原文存在(内容寻址快照) |
结论:DB 模块的 .rakumod 文件已不在 lib/ 下,但 Rakudo 的 precomp 源码快照(C:\rakudo\share\perl6\site\sources\,文件名为内容 SHA1)完整保留了逐字源码,且 use DB; 仍可从 precomp 缓存正常加载(已实测 raku -e 'use DB; say 42' → 42)。
0.2 发行版元数据(META6 原文,C:\rakudo\share\perl6\site\dist\A8844EDD7E4DD10FC8035648876F7B558A70BDBA)
{"api":"1","auth":"github:CurtTilmes","authors":["Curt Tilmes <[email protected]>"],"depends":{"runtime":{"requires":["Concurrent::Stack"]},"test":{"requires":["Test","Test::When"]}},"description":"Base roles for DB::* family of SQL Database modules","files":{},"license":"BSD-2-Clause","meta-version":"1","name":"DB","perl":"6.c","provides":{"DB":{"lib/DB.rakumod":{"file":"03DA109C5B8532E87FCA813A145C35AABBCB9BC8","time":null}},"DB::Connection":{"lib/DB/Connection.rakumod":{"file":"7D31F71E20FBC2186DACFFB1F4D67A911166094C","time":null}},"DB::Mock":{"lib/DB/Mock.rakumod":{"file":"48F58C267EEE065A1956AC0693126BB4D6CFF5A4","time":null}},"DB::Result":{"lib/DB/Result.rakumod":{"file":"95A7DEA39A879FBDF062CFC4BD669BE298F3078A","time":null}},"DB::Statement":{"lib/DB/Statement.rakumod":{"file":"E4E2D72E6404EF6F25B5680D2EC27229A5F0C795","time":null}}},"support":{"bugtracker":"https://github.com/CurtTilmes/raku-db/issues","email":"[email protected]","source":"https://github.com/CurtTilmes/raku-db.git"},"tags":["DATABASE"],"test-depends":["Test","Test::META","Test::When"],"ver":"0.5","version":"0.5"}参考驱动 META6(site\dist\31271DE6533212DE6B4B2262DB0EB5066A695DCA):
{"api":"1","auth":"github:CurtTilmes","authors":["Curt Tilmes <[email protected]>"],"depends":{"runtime":{"requires":["BitEnum","DB","NativeLibs:ver<0.0.7+>:auth<github:salortiz>","sqlite3:from<native>"]},"test":{"requires":["Test","Test::When"]}},"description":"SQLite access for Raku","files":{},"license":"BSD-2-Clause","meta-version":"1","name":"DB::SQLite","perl":"6.c","provides":{"DB::SQLite":{"lib/DB/SQLite.rakumod":{"file":"79F654BF6F54F73FCB982ADA494C8AB7BB477D9E","time":null}},"DB::SQLite::Connection":{"lib/DB/SQLite/Connection.rakumod":{"file":"032DBF250FD89CDEFA0FCB5A64857F4F96336737","time":null}},"DB::SQLite::Native":{"lib/DB/SQLite/Native.rakumod":{"file":"87AE851FDD0C59A12264F4E093BEB86F2639B27C","time":null}},"DB::SQLite::Result":{"lib/DB/SQLite/Result.rakumod":{"file":"9B24C886DDC4C9876CE29796855AA99DF64B28AF","time":null}},"DB::SQLite::Statement":{"lib/DB/SQLite/Statement.rakumod":{"file":"FF81D543D88B162772D20C1D6C6440917214D568","time":null}}},"support":{"bugtracker":"https://github.com/CurtTilmes/raku-dbsqlite/issues","email":"[email protected]","source":"https://github.com/CurtTilmes/raku-dbsqlite.git"},"tags":["DATABASE","SQLITE"],"ver":"0.7","version":"0.7"}0.3 文件清单
| 模块 | 原始路径 | 快照文件(C:\rakudo\share\perl6\site\sources\) | 字节 |
|---|---|---|---|
DB | lib/DB.rakumod | 03DA109C5B8532E87FCA813A145C35AABBCB9BC8 | 873 |
DB::Connection | lib/DB/Connection.rakumod | 7D31F71E20FBC2186DACFFB1F4D67A911166094C | 1664 |
DB::Statement | lib/DB/Statement.rakumod | E4E2D72E6404EF6F25B5680D2EC27229A5F0C795 | 205 |
DB::Result | lib/DB/Result.rakumod | 95A7DEA39A879FBDF062CFC4BD669BE298F3078A | 1306 |
DB::Mock | lib/DB/Mock.rakumod | 48F58C267EEE065A1956AC0693126BB4D6CFF5A4 | 1375 |
DB::SQLite(参考驱动) | lib/DB/SQLite.rakumod | 79F654BF6F54F73FCB982ADA494C8AB7BB477D9E | 2896 |
DB::SQLite::Connection | lib/DB/SQLite/Connection.rakumod | 032DBF250FD89CDEFA0FCB5A64857F4F96336737 | 2798 |
DB::SQLite::Statement | lib/DB/SQLite/Statement.rakumod | FF81D543D88B162772D20C1D6C6440917214D568 | 2093 |
DB::SQLite::Result | lib/DB/SQLite/Result.rakumod | 9B24C886DDC4C9876CE29796855AA99DF64B28AF | 2446 |
DB::SQLite::Native | lib/DB/SQLite/Native.rakumod | 87AE851FDD0C59A12264F4E093BEB86F2639B27C | 7556 |
1. DB(role,驱动入口)
文件:lib/DB.rakumod,全文(原文):
use Concurrent::Stack;
use DB::Connection;
role DB
{
has $.max-connections = 5;
has $.connections = Concurrent::Stack.new;
method connect(--> DB::Connection) {...}
method db(--> DB::Connection)
{
while my $db = $!connections.pop
{
return $db if $db.ping;
$db.DESTROY
}
$.connect
}
method query(|args)
{
$.db.query(:finish, |args)
}
method execute(|args)
{
$.db.execute(:finish, |args)
}
method cache(DB::Connection:D $db)
{
if $!connections.elems < $!max-connections
{
$!connections.push($db)
}
else
{
$db.DESTROY
}
}
method finish(--> Nil)
{
.DESTROY while $_ = $!connections.pop
}
submethod DESTROY()
{
self.finish
}
}要点:
role DB是参数化 role group(运行时DB.HOW=Perl6::Metamodel::ParametricRoleGroupHOW)。驱动侧唯一必须实现的方法:
method connect(--> DB::Connection) {...}(stubbed,{...}为必须实现)。属性
$.max-connections = 5、$.connections = Concurrent::Stack.new为公有 accessor;db()从池中取连接,ping失败则DESTROY后新建。query/execute在DB层强制注入:finish(用完即还池)。
2. DB::Connection(role)
文件:lib/DB/Connection.rakumod,全文(原文):
use DB::Statement;
role DB::Connection
{
has $.owner is required;
has Bool $!transaction = False;
has %!prepare-cache;
method ping(--> Bool) { True }
method free(--> Nil) {}
method prepare-nocache(Str:D $query --> DB::Statement) {...}
# execute defaults to the same as query(), but some modules
# do different things with it.
method execute(Str:D $command, Bool :$finish, |args)
{
$.prepare($command).execute(|args, :$finish);
}
method clear-cache(--> Nil)
{
.free for %!prepare-cache.values;
%!prepare-cache = ()
}
method finish(--> Nil)
{
if $.ping
{
$.rollback if $!transaction;
$!owner.cache(self);
}
else
{
self.DESTROY
}
}
method prepare(Str:D $query, Bool :$nocache --> DB::Statement)
{
return $.prepare-nocache($query) if $nocache;
return $_ with %!prepare-cache{$query};
%!prepare-cache{$query} = $.prepare-nocache($query)
}
method query(Str:D $query, Bool :$finish, Bool :$nocache, |args)
{
$.prepare($query, :$nocache).execute(|args, :$finish);
}
method begin(--> DB::Connection)
{
self.execute('begin');
$!transaction = True;
self
}
method commit(--> DB::Connection)
{
self.execute('commit');
$!transaction = False;
self
}
method rollback(--> DB::Connection)
{
self.execute('rollback');
$!transaction = False;
self
}
submethod DESTROY()
{
self.clear-cache;
self.free
}
}要点:
必须实现的属性:
has $.owner is required;(驱动类自身实例)。必须实现的方法:
prepare-nocache(Str:D $query --> DB::Statement) {...};ping、free有默认实现,驱动应按需 override。事务由父 role 用 SQL 文本
begin/commit/rollback驱动(不是原生事务 API);私有布尔$!transaction控制finish时自动回滚。语句缓存
%!prepare-cache由 role 负责,key 为 SQL 字符串;clear-cache/DESTROY时逐个free。
3. DB::Statement(role)
文件:lib/DB/Statement.rakumod,全文(原文,共 205 字节):
role DB::Statement
{
has $.db is required;
method free(--> Nil) {}
method execute() {...}
method finish(--> Nil)
{
$!db.finish
}
submethod DESTROY() { self.free }
}要点:
属性:
has $.db is required;(所属DB::Connection)。必须实现:
method execute() {...}。注意签名陷阱:role 里
execute()零参数,但所有实现(DB::Mock、DB::SQLite)都写成method execute(Bool :$finish, *@args, *%args)—— Raku 允许实现加可选参数,实际调用点固定为execute(|args, :$finish)。
4. DB::Result(role)+ 两个 Iterator 类
文件:lib/DB/Result.rakumod,全文(原文):
class DB::Result::ArrayIterator does Iterator
{
has $.result is required;
method pull-one()
{
$!result.row or do
{
$!result.finish;
IterationEnd
}
}
}
class DB::Result::HashIterator does Iterator
{
has $.result is required;
method pull-one()
{
my $row = $!result.row or do
{
$!result.finish;
return IterationEnd
}
%( $!result.keys.list Z=> @$row )
}
}
role DB::Result
{
has $.sth;
has Bool $.finish;
has $.keys-cache;
method free() {}
method finish(--> Nil)
{
self.free;
$!sth.finish if $!finish && $!sth;
}
method row() { ... }
method names() { ... }
method keys()
{
$!keys-cache // ($!keys-cache = $.names)
}
method value()
{
LEAVE $.finish;
$.row[0]
}
method array()
{
LEAVE $.finish;
$.row
}
method hash()
{
LEAVE $.finish;
%( @$.keys Z=> @$.row )
}
method arrays()
{
Seq.new: DB::Result::ArrayIterator.new(result => self)
}
method hashes()
{
Seq.new: DB::Result::HashIterator.new(result => self)
}
submethod DESTROY()
{
self.free
}
}要点:
必须实现:
row()与names()(均 stubbed)。row()约定:有行返回位置列表,无行返回空列表((),假值),迭代器据此终止。finish/free语义:free驱动自实现;finish在:finish为真时顺带$!sth.finish(把连接还池)。count()不在 role 中,由驱动自定义(DB::SQLite::Result提供has $.count)。
5. DB::Mock(官方测试替身,完整可运行实现范例)
文件:lib/DB/Mock.rakumod,全文(原文):
use DB;
use DB::Connection;
use DB::Statement;
use DB::Result;
our $mock-connections = 0;
our $mock-statements = 0;
our $mock-results = 0;
class DB::Mock::Result does DB::Result
{
has $.alive = True;
has $.r = 0;
has $.max;
method free() { $mock-results-- if $!alive; $!alive = False }
method names() { <a b c> }
method row()
{
(++$!r > $!max) ?? () !! ($!r<>, "string {$!r}", Any)
}
}
class DB::Mock::Statement does DB::Statement
{
has $.alive = True;
method free() { $mock-statements-- if $!alive; $!alive = False }
method execute(Bool :$finish, *@args)
{
$mock-results++;
DB::Mock::Result.new(:sth(self), :$finish, max => @args[0] // 5)
}
}
class DB::Mock::Connection does DB::Connection
{
has $.alive = True;
has $.state is rw = True;
method ping(--> Bool) { $!state }
method free(--> Nil) { $mock-connections-- if $!alive; $!alive = False }
method prepare-nocache(Str:D $query --> DB::Mock::Statement)
{
$mock-statements++;
DB::Mock::Statement.new(:db(self))
}
method execute(Str:D $query, Bool :$finish)
{
LEAVE $.finish if $finish;
return 1;
}
}
class DB::Mock does DB
{
method connect(--> DB::Mock::Connection)
{
$mock-connections++;
DB::Mock::Connection.new(:owner(self))
}
}这是照抄级最小实现:一个驱动只需 4 个类(Driver does DB、Connection does DB::Connection、Statement does DB::Statement、Result does DB::Result)。
6. 参考驱动 DB::SQLite 全文
6.1 lib/DB/SQLite.rakumod(驱动类 + POD 用法)
use DB;
use DB::SQLite::Native;
use DB::SQLite::Connection;
class DB::SQLite does DB
{
has $.filename;
has $.busy-timeout;
has %.flags;
method BUILD(:$!filename = '', :$!busy-timeout = 10000, *%!flags) {}
method connect(--> DB::SQLite::Connection)
{
my $conn = DB::SQLite::Native.open($!filename, |%!flags);
$conn.busy-timeout($_) with $!busy-timeout;
DB::SQLite::Connection.new(owner => self, :$conn)
}
}其 POD SYNOPSIS(官方用法示例,原文):
my $s = DB::SQLite.new; # You can pass in connection information if you want.
say $s.query('select 42').value;
# 42
$s.execute('insert into foo (x,y) values (1,2)');
for $s.query('select * from foo').arrays -> @row {
say @row;
}
for $s.query('select * from foo').hashes -> %row {
say %row;
}6.2 lib/DB/SQLite/Connection.rakumod
use DB::Connection;
use DB::SQLite::Native;
use DB::SQLite::Statement;
class DB::SQLite::Connection does DB::Connection
{
has DB::SQLite::Native $.conn is required;
method free(--> Nil)
{
.close with $!conn;
$!conn = Nil;
}
method prepare-nocache(Str:D $query --> DB::SQLite::Statement)
{
DB::SQLite::Statement.new(:db(self), stmt => $!conn.prepare($query))
}
method execute(Str:D $query, Bool :$finish --> Int)
{
LEAVE $.finish if $finish;
$!conn.exec($query);
$!conn.changes
}
}6.3 lib/DB/SQLite/Statement.rakumod
use DB::Statement;
use DB::SQLite::Native;
use DB::SQLite::Result;
class DB::SQLite::Statement does DB::Statement
{
has DB::SQLite::Native::Statement $.stmt handles <bind sql clear>;
has $.count = $!stmt.count;
method free(--> Nil)
{
.finalize with $!stmt;
$!stmt = Nil;
}
method execute(Bool :$finish, *@args, *%args)
{
.reset with $!stmt;
if @args
{
my \num-params = @args.elems;
loop (my $i = 0; $i < num-params; $i++)
{
$!stmt.bind($i+1, @args[$i]);
}
}
if %args
{
for %args.kv -> $k, $v
{
$!stmt.bind($k, $v)
}
}
if $!count # Has results?
{
return DB::SQLite::Result.new(:sth(self), :$finish)
}
else
{
LEAVE $.finish if $finish;
$!stmt.step == SQLITE_DONE
?? $!stmt.db.changes
!! $!stmt.db.check;
}
}
}6.4 lib/DB/SQLite/Result.rakumod
use DB::SQLite::Native;
use DB::Result;
sub sqlite-int($stmt, $i) { $stmt.int64($i) }
sub sqlite-double($stmt, $i) { $stmt.double($i) }
sub sqlite-text($stmt, $i) { $stmt.text($i) }
sub sqlite-blob($stmt, $i) { buf8.new($stmt.blob($i)[^$stmt.bytes($i)]) }
sub sqlite-null($stmt, $i) { Any }
my %convert = Map.new: map( { +SQLITE_TYPE::{.key} => .value}, (
SQLITE_INTEGER => &sqlite-int,
SQLITE_FLOAT => &sqlite-double,
SQLITE_TEXT => &sqlite-text,
SQLITE_BLOB => &sqlite-blob,
SQLITE_NULL => &sqlite-null
));
class DB::SQLite::Result does DB::Result
{
has $.stmt = $!sth.stmt;
has $.count = $!sth.count;
method names() { do for ^$!count { $!stmt.name($_) } }
method row()
{
given $!stmt.step()
{
when SQLITE_ROW
{
do for ^$!count -> $i
{
%convert{$!stmt.type($i)}($!stmt, $i);
}
}
when SQLITE_DONE
{
()
}
default
{
$!stmt.db.check($_)
}
}
}
}6.5 lib/DB/SQLite/Native.rakumod(NativeCall 层范式,节选关键部分)
use NativeLibs:ver<0.0.7+>:auth<github:salortiz>;
use BitEnum;
sub LIBSQLITE {
NativeLibs::Searcher.at-runtime(
'sqlite3', 'sqlite3_version', 0).()
}
enum SQLITE (
SQLITE_OK => 0,
SQLITE_ROW => 100,
SQLITE_DONE => 101
);
my constant NULL = Pointer;
class DB::SQLite::Native is repr('CPointer') {...}
sub sqlite3_errstr(int32 --> Str) is native(LIBSQLITE) {}
class DB::SQLite::Error is Exception
{
has Int $.code;
has Str $.message = sqlite3_errstr($!code);
}
class DB::SQLite::Native::Statement is repr('CPointer')
{
method db(--> DB::SQLite::Native)
is native(LIBSQLITE) is symbol('sqlite3_db_handle') {}
method check($code = $.db.errcode) is hidden-from-backtrace
{
$.db.check($code) unless $code == SQLITE_OK
}
method step(--> int32)
is native(LIBSQLITE) is symbol('sqlite3_step') {}
method bind-text(int32, Str, int32, Pointer --> int32)
is native(LIBSQLITE) is symbol('sqlite3_bind_text') {}
multi method bind(Int $n, Str:D $v --> Nil)
{
$.bind-text($n, $v, -1, Pointer.new(-1)) == SQLITE_OK or $.check
}
multi method bind(Int $n, Any:U --> Nil)
{
$.bind-null($n) == SQLITE_OK or $.check
}
multi method bind(Str:D $name, |rest)
{
$.bind($.param-index($name ~~ /^<[:@$]>/ ?? $name !! ('$' ~ $name)),
|rest)
}
method count(--> int32)
is native(LIBSQLITE) is symbol('sqlite3_column_count') {}
method name(int32 $iCol --> Str)
is native(LIBSQLITE) is symbol('sqlite3_column_name') {}
method type(int32 $iCol --> int32)
is native(LIBSQLITE) is symbol('sqlite3_column_type') {}
method text(int32 $iCol --> Str)
is native(LIBSQLITE) is symbol('sqlite3_column_text') {}
method finalize(--> int32)
is native(LIBSQLITE) is symbol('sqlite3_finalize') {}
method reset(--> int32)
is native(LIBSQLITE) is symbol('sqlite3_reset') {}
}
class DB::SQLite::Native
{
method errcode(--> int32)
is native(LIBSQLITE) is symbol('sqlite3_errcode') {}
method errmsg(--> Str)
is native(LIBSQLITE) is symbol('sqlite3_errmsg') {}
method check(int32 $code = $.errcode) is hidden-from-backtrace
{
die DB::SQLite::Error.new(:$code) unless $code == SQLITE_OK
}
method busy-timeout(int32 --> int32)
is native(LIBSQLITE) is symbol('sqlite3_busy_timeout') {}
sub sqlite3_open_v2(Str $filename,
DB::SQLite::Native $handle is rw,
int32 $flags,
Str $zVfs --> int32)
is native(LIBSQLITE) {}
method close( --> int32)
is native(LIBSQLITE) is symbol('sqlite3_close_v2') {}
method open(Str:D $filename, *%flags --> DB::SQLite::Native)
{
my DB::SQLite::Native $handle .= new;
UNDO .close with $handle;
my $flags = BitEnum[SQLITE_FILE_OPEN_FLAGS, :lc,
prefix => 'SQLITE_OPEN_'].new(%flags.keys);
$flags.set(<readwrite create>) unless $flags.isset(<readonly>) ||
$flags.isset(<readwrite>);
$.check(sqlite3_open_v2($filename, $handle, +$flags, Str));
return $handle;
}
method prepare(Str:D $sql) is hidden-from-backtrace
{
my DB::SQLite::Native::Statement $stmt .= new;
UNDO .finalize with $stmt;
$.check(sqlite3_prepare_v2(self, $sql, -1, $stmt, Pointer));
return $stmt;
}
method changes(--> int32)
is native(LIBSQLITE) is symbol('sqlite3_changes') {}
method exec(Str:D $sql)
{
$.check($.sqlite3_exec($sql, NULL, NULL, NULL));
}
}NativeCall 层关键范式:
class X::Native is repr('CPointer') {...}—— 句柄类型前置声明。原生函数用
method ... is native(LIBSQLITE) is symbol('...') {}直接绑到类方法上(Raku 把self作为首参传出)。错误用
die X::Error.new(:$code)抛自定义is Exception子类,无out_error字符串。资源安全:
UNDO .close with $handle;/UNDO .finalize with $stmt;—— 构造中途失败自动回滚释放。
7. 契约总表(实现 DB::DuckDB 的照抄清单)
7.1 命名与结构(沿用 DB::SRC 的既有模式)
| 层 | 类/role | 必须 does |
|---|---|---|
| 驱动入口 | class DB::DuckDB does DB | DB |
| 连接 | class DB::DuckDB::Connection does DB::Connection | DB::Connection |
| 语句 | class DB::DuckDB::Statement does DB::Statement | DB::Statement |
| 结果 | class DB::DuckDB::Result does DB::Result | DB::Result |
| 原生句柄 | class DB::DuckDB::Native(+ is repr('CPointer') 的子类) | — |
7.2 必须实现 vs 可继承
| 类 | 必须实现(stubbed / required) | 可继承默认实现 |
|---|---|---|
DB | connect(--> DB::Connection) | db、query、execute、cache、finish、DESTROY |
DB::Connection | prepare-nocache(Str:D $query --> DB::Statement);属性 $.owner is required | ping(True)、free(空)、prepare、query、execute、clear-cache、finish、begin/commit/rollback、DESTROY |
DB::Statement | execute(...);属性 $.db is required | free(空)、finish($!db.finish)、DESTROY |
DB::Result | row()、names() | free、finish、keys、value、array、hash、arrays、hashes、DESTROY |
7.3 调用链(运行时实测确认)
$driver.query($sql, |args) → DB.query → $conn.query(:finish) → prepare($sql) → prepare-nocache
→ $sth.execute(|args, :finish)
$driver.execute($sql, |args) → DB.execute → $conn.execute(:finish) → prepare-nocache + execute
$conn.query($sql, :finish, :nocache, |args) → prepare(...).execute(|args, :$finish)
DB::Statement.finish → $!db.finish → 若 ping 则 rollback(if tx) + owner.cache(self)7.4 构造与 DSN
无统一
connect($dsn)静态工厂;驱动类的new/BUILD即连接配置入口:DB::SQLite:BUILD(:$!filename = '', :$!busy-timeout = 10000, *%!flags) {},即DB::SQLite.new(filename => 'x.db', :readonly)。对应 DuckDB:
DB::DuckDB.new(:$path = ':memory:', *%config),connect内duckdb_open+duckdb_connect。
connect无参,返回DB::Connection;连接不共享 dll 级全局状态,池由DBrole 管理(默认 5 条)。
7.5 错误处理约定
无统一
X::DB::Error基类。各驱动自定义is Exception子类并die:class DB::SQLite::Error is Exception { has Int $.code; has Str $.message = sqlite3_errstr($!code); } ... method check(int32 $code = $.errcode) is hidden-from-backtrace { die DB::SQLite::Error.new(:$code) unless $code == SQLITE_OK }惯例:
method check(...) is hidden-from-backtrace,失败即die;DuckDB 侧对应duckdb_state != DuckDBSuccess时读char** out_error并duckdb_free,构造class DB::DuckDB::Error is Exception { has Int $.code; has Str $.message; }。
7.6 事务
事务不调用原生 API,父 role 直接执行 SQL 文本:
方法 实现 返回值 begin(--> DB::Connection)self.execute('begin'); $!transaction = Trueselfcommit(--> DB::Connection)self.execute('commit'); $!transaction = Falseselfrollback(--> DB::Connection)self.execute('rollback'); $!transaction = Falseself若驱动要支持嵌套/保存点,需自行 override 这三个方法。
finish时若$!transaction为真,自动rollback。
7.7 结果消费 API(DB::Result)
| 方法 | 语义 |
|---|---|
value | 首行首列标量(LEAVE $.finish) |
array | 首行(LEAVE $.finish) |
hash | 首行 hash(LEAVE $.finish) |
arrays | Seq,全部行(迭代耗完自动 finish) |
hashes | Seq,全部行 hash |
keys / names | 列名;keys 带 $!keys-cache |
row | 驱动实现,无行返回 () |
finish / free | finish 在 :finish 时还连接;free 释放结果资源 |
8. 官方示例与测试用例
8.1 官方 POD SYNOPSIS(DB::SQLite,原文,见 §6.1)
my $s = DB::SQLite.new;
say $s.query('select 42').value; # 42
$s.execute('insert into foo (x,y) values (1,2)');
for $s.query('select * from foo').arrays -> @row { say @row }
for $s.query('select * from foo').hashes -> %row { say %row }8.2 t/ 目录可用性(重要)
本机未安装
t/测试文件:DB与DB::SQLite的 META6 中"files":{},zef 安装只落地lib/与 precomp 源码快照;全盘搜索*raku-db*目录为空,site/sources中亦无use DB::SQLite的测试快照(仅 5 个 lib 模块命中)。因此以官方
DB::Mock(发行版自带测试替身)+ 运行时实跑代替 t/ 用例验证契约。下面 §8.3 是用例与实测输出。
8.3 实测用例(等价于官方测试写法)
use DB;
use DB::Mock;
my $db = DB::Mock.new;
my $result = $db.query('select 1', :finish);
say $result.^name; # DB::Mock::Result
say $result.keys; # (a b c)
say $result.value; # 1
say $result.array.raku; # $(2, "string 2", Any)
say $result.hash.raku; # {:a(3), :b("string 3"), :c(Any)}
say $result.arrays.raku; # ((4, "string 4", Any), (5, "string 5", Any)).Seq
say $db.execute('insert into foo values (1)'); # 1
my $conn = $db.db;
$conn.begin; $conn.commit; $conn.rollback;
say $conn.ping; # True
$conn.finish;实测输出(raku mock-usage.raku):
connect class: DB::Mock::Connection
db() class: DB::Mock::Connection
result class: DB::Mock::Result
keys: (a b c)
value: 1
array: $(2, "string 2", Any)
hash: {:a(3), :b("string 3"), :c(Any)}
arrays: ((4, "string 4", Any), (5, "string 5", Any)).Seq
hashes: ().Seq
execute: 1
begin/commit/rollback: DB::Mock::Connection / DB::Mock::Connection / DB::Mock::Connection
ping: True
db.elems cached: 58.4 运行时内省结果(确认无隐藏契约)
raku introspect-db.raku 关键输出:
DB.^name = DB
DB HOW = Perl6::Metamodel::ParametricRoleGroupHOW
DB attributes: $!max-connections : Mu [accessor $.max-connections] | $!connections : Mu [accessor $.connections]
DB methods: connect, db, query, execute, cache, finish, DESTROY
DB::Connection attributes: $!owner : Mu [accessor $.owner] | $!transaction : Bool | %!prepare-cache : Associative
DB::Connection methods: ping, free, prepare-nocache, execute, clear-cache, finish, prepare, query, begin, commit, rollback, DESTROY
DB::Statement attributes: $!db : Mu [accessor $.db]
DB::Statement methods: free, execute, finish, DESTROY
DB::Result attributes: $!sth : Mu [accessor $.sth] | $!finish : Bool [accessor $.finish] | $!keys-cache : Mu [accessor $.keys-cache]
DB::Result methods: free, finish, row, names, keys, value, array, hash, arrays, hashes, DESTROY
DB::Mock does: DB注意:DB.^ver / ^api / ^auth 在无 META 关联加载时返回 n/a;发行版版本以 §0.2 的 META6 为准 —— DB ver 0.5 / api 1 / auth github:CurtTilmes。
9. 给 DB::DuckDB 的落地建议(严格对齐契约)
DB::DuckDB does DB:has $.path = ':memory:'、has %.config;BUILD只存配置,不在BUILD里建库(connect才开)。connect内:duckdb_open(path, $db)→duckdb_connect($db, $conn);把$db(database handle)与$conn(connection handle)都挂在DB::DuckDB::Connection上,free中先duckdb_disconnect再duckdb_close。prepare-nocache→duckdb_prepare,返回DB::DuckDB::Statement,其has $.count = duckdb_column_count(用于判断"是否有结果集",对齐DB::SQLite的if $!count分支)。execute分两支:有结果集 → 返回DB::DuckDB::Result;无结果集 →duckdb_execute_prepared后返回duckdb_rows_changed(Int)。DB::DuckDB::Result.row:duckdb_step→DuckDBRowCount语义;duckdb_row_count/duckdb_column_count取维度,duckdb_value_*取列值,DuckDBType做类型映射;names用duckdb_column_name。事务:无需实现
begin/commit/rollback(父 role 已用 SQL 文本实现),但需保证execute('begin')走无结果集分支且返回真值。错误:所有
duckdb_*返回duckdb_state的调用统一走self.check($state);out_error用char**+duckdb_free释放,严禁内存泄漏(DuckDB 无duckdb_free_string)。
附:原始快照路径(供人工复核)
C:\rakudo\share\perl6\site\sources\03DA109C5B8532E87FCA813A145C35AABBCB9BC8 DB.rakumod
C:\rakudo\share\perl6\site\sources\7D31F71E20FBC2186DACFFB1F4D67A911166094C DB/Connection.rakumod
C:\rakudo\share\perl6\site\sources\E4E2D72E6404EF6F25B5680D2EC27229A5F0C795 DB/Statement.rakumod
C:\rakudo\share\perl6\site\sources\95A7DEA39A879FBDF062CFC4BD669BE298F3078A DB/Result.rakumod
C:\rakudo\share\perl6\site\sources\48F58C267EEE065A1956AC0693126BB4D6CFF5A4 DB/Mock.rakumod
C:\rakudo\share\perl6\site\sources\79F654BF6F54F73FCB982ADA494C8AB7BB477D9E DB/SQLite.rakumod
C:\rakudo\share\perl6\site\sources\032DBF250FD89CDEFA0FCB5A64857F4F96336737 DB/SQLite/Connection.rakumod
C:\rakudo\share\perl6\site\sources\FF81D543D88B162772D20C1D6C6440917214D568 DB/SQLite/Statement.rakumod
C:\rakudo\share\perl6\site\sources\9B24C886DDC4C9876CE29796855AA99DF64B28AF DB/SQLite/Result.rakumod
C:\rakudo\share\perl6\site\sources\87AE851FDD0C59A12264F4E093BEB86F2639B27C DB/SQLite/Native.rakumod(内容由AI生成,仅供参考) (内容由AI生成,仅供参考)