feat: 新增 DuckDB 方言支持及 SQLite3 自动注册 IF 函数

- 新增 dialect/duckdb 方言,支持 DuckDB SQL 语法
- 新增 schema/dialect/duckdb DDL 操作支持
- dialect/sqlite3: 注册 sqlite3_with_if 驱动,连接时自动注册 IF() 函数
- engine: MakeConnection 对 sqlite3 自动使用带 IF 支持的驱动
- engine: 新增 DBConfig DuckDB 配置项及相关 Option 函数
- 统一各方言测试引用路径
This commit is contained in:
2026-04-16 10:14:46 +08:00
parent c4590cf592
commit 2221ca0ddb
19 changed files with 1270 additions and 62 deletions
+104 -8
View File
@@ -5,9 +5,6 @@ import (
"time"
"git.fsdpf.net/go/db/engine"
_ "github.com/denisenkom/go-mssqldb"
_ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
)
// TestMySQLConfig 测试 MySQL 驱动的配置
@@ -59,7 +56,7 @@ func TestMySQLConfig(t *testing.T) {
engine.WithSQLServerInstance("SQLEXPRESS"),
},
wantErr: true,
errMsg: "engine.WithSQLServerInstance is only valid for sqlserver driver",
errMsg: "WithSQLServerInstance is only valid for sqlserver driver",
},
}
@@ -124,7 +121,7 @@ func TestPostgreSQLConfig(t *testing.T) {
engine.WithMySQLCollation("utf8mb4_unicode_ci"),
},
wantErr: true,
errMsg: "engine.WithMySQLCollation is only valid for mysql driver",
errMsg: "WithMySQLCollation is only valid for mysql driver",
},
}
@@ -179,7 +176,7 @@ func TestSQLiteConfig(t *testing.T) {
engine.WithPgSslmode("disable"),
},
wantErr: true,
errMsg: "engine.WithPgSslmode is only valid for pgsql driver",
errMsg: "WithPgSslmode is only valid for pgsql driver",
},
}
@@ -244,7 +241,7 @@ func TestSQLServerConfig(t *testing.T) {
engine.WithSQLiteFile("test.db"),
},
wantErr: true,
errMsg: "engine.WithSQLiteFile is only valid for sqlite3 driver",
errMsg: "WithSQLiteFile is only valid for sqlite3 driver",
},
}
@@ -285,6 +282,105 @@ func TestInvalidDriver(t *testing.T) {
engine.NewDBConfig("oracle")
}
// TestDuckDBConfig 测试 DuckDB 驱动的配置
func TestDuckDBConfig(t *testing.T) {
tests := []struct {
name string
options []engine.Option
wantErr bool
errMsg string
wantDSN string
}{
{
name: "In-memory database (no file)",
options: []engine.Option{},
wantErr: false,
wantDSN: "",
},
{
name: "File-based database",
options: []engine.Option{
engine.WithDuckDBFile("test.db"),
},
wantErr: false,
wantDSN: "test.db",
},
{
name: "With access mode",
options: []engine.Option{
engine.WithDuckDBFile("test.db"),
engine.WithDuckDBAccessMode("READ_ONLY"),
},
wantErr: false,
wantDSN: "test.db?access_mode=READ_ONLY",
},
{
name: "With threads",
options: []engine.Option{
engine.WithDuckDBFile("test.db"),
engine.WithDuckDBThreads(4),
},
wantErr: false,
wantDSN: "test.db?threads=4",
},
{
name: "With max memory",
options: []engine.Option{
engine.WithDuckDBFile("test.db"),
engine.WithDuckDBMaxMemory("1GB"),
},
wantErr: false,
wantDSN: "test.db?max_memory=1GB",
},
{
name: "With all options",
options: []engine.Option{
engine.WithDuckDBFile("test.db"),
engine.WithDuckDBAccessMode("READ_WRITE"),
engine.WithDuckDBThreads(8),
engine.WithDuckDBMaxMemory("2GB"),
},
wantErr: false,
},
{
name: "With MySQL option (should panic)",
options: []engine.Option{
engine.WithDuckDBFile("test.db"),
engine.WithMySQLCollation("utf8mb4_unicode_ci"),
},
wantErr: true,
errMsg: "WithMySQLCollation is only valid for mysql driver",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if !tt.wantErr {
t.Errorf("engine.NewDBConfig() panicked unexpectedly: %v", r)
} else if tt.errMsg != "" && r != tt.errMsg {
t.Errorf("engine.NewDBConfig() panic = %v, want %v", r, tt.errMsg)
}
} else if tt.wantErr {
t.Errorf("engine.NewDBConfig() did not panic, expected panic with: %v", tt.errMsg)
}
}()
config := engine.NewDBConfig("duckdb", tt.options...)
if !tt.wantErr {
if config.Driver != "duckdb" {
t.Errorf("Driver = %v, want duckdb", config.Driver)
}
dsn := config.ToDSN()
if tt.wantDSN != "" && dsn != tt.wantDSN {
t.Errorf("DSN = %v, want %v", dsn, tt.wantDSN)
}
}
})
}
}
// TestDefaultValues 测试默认值
func TestDefaultValues(t *testing.T) {
config := engine.NewDBConfig("mysql",
@@ -358,7 +454,7 @@ func TestToDSN(t *testing.T) {
engine.WithPassword("password"),
engine.WithSQLServerInstance("SQLEXPRESS"),
),
wantDSN: "sqlserver://sa:password@sqlserver.example.com\\SQLEXPRESS:1433?server=sqlserver.example.com\\SQLEXPRESS&database=master",
wantDSN: "sqlserver://sa:password@sqlserver.example.com\\SQLEXPRESS:1433?database=master&server=sqlserver.example.com%5CSQLEXPRESS",
wantErr: false,
},
}