feat: 完善扫描器、exec 及 schema 相关功能

- exec/scanner: 用 *interface{} 替换 **json.RawMessage 扫描目标,兼容 DuckDB 返回 map[string]interface{} 的场景;新增 toJSONRawMessage 转换函数
- exec/scanner: ScanVal 支持结构体指针,通过 JSON 中间层转换(DuckDB STRUCT 列)
- exec/scanner: 将 *sql.RawBytes 和 *[]byte 的处理从 ScanValContext 移入 scanner.ScanVal
- exec/query_executor: 简化 ScanValContext,移除私有 scan 方法
- exec: 补充 scanner 级别 ScanVal 测试用例
- internal/util/reflect: 重写 SafeSetVarValue,修复非指针 src 及 nil 指针字段的 panic
- internal/util/column_map: 恢复非匿名带标签结构体字段的展开逻辑
- schema: 新增 vector 列类型支持
- engine: 补充 DuckDB 相关配置
- dialect/sqlite3/vtab: 完善虚拟表适配器
- 各方言测试改用 sqlmock 虚拟连接
This commit is contained in:
2026-05-20 17:52:28 +08:00
parent ac81f1ff0b
commit 21b80bdea4
36 changed files with 1131 additions and 318 deletions
+36 -52
View File
@@ -1,14 +1,12 @@
package sqlserver_test
import (
"os"
"testing"
"git.fsdpf.net/go/db/engine"
"git.fsdpf.net/go/db/schema"
sqlmock "github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/suite"
_ "github.com/microsoft/go-mssqldb"
)
type sqlServerTest struct {
@@ -16,41 +14,27 @@ type sqlServerTest struct {
schema schema.Schema
}
var (
tableName = "entry"
dropTable = "IF OBJECT_ID('entry', 'U') IS NOT NULL DROP TABLE entry;"
createTable = "CREATE TABLE entry (" +
"id INT NOT NULL IDENTITY(1,1) PRIMARY KEY," +
"int INT NOT NULL UNIQUE," +
"float REAL NOT NULL," +
"string NVARCHAR(255) NOT NULL," +
"time DATETIME2 NOT NULL," +
"bool BIT NOT NULL," +
"bytes VARBINARY(MAX) NOT NULL" +
");"
)
var tableName = "entry"
func TestSQLServerSuite(t *testing.T) {
suite.Run(t, new(sqlServerTest))
}
func (t *sqlServerTest) SetupSuite() {
db := engine.Open(map[string]engine.DBConfig{
"test-sqlserver": engine.NewDBConfig("sqlserver",
engine.WithHost(os.Getenv("SQLSERVER_HOST")),
engine.WithPort(os.Getenv("SQLSERVER_PORT")),
engine.WithDatabase(os.Getenv("SQLSERVER_DB")),
engine.WithUsername(os.Getenv("SQLSERVER_USER")),
engine.WithPassword(os.Getenv("SQLSERVER_PASSWD")),
),
mockDB, mock, _ := sqlmock.New()
mock.ExpectQuery("SELECT.*COLUMN_NAME.*COLUMNS").
WillReturnRows(sqlmock.NewRows([]string{"COLUMN_NAME"}).
AddRow("id").AddRow("int").AddRow("float").
AddRow("string").AddRow("time").AddRow("bool").AddRow("bytes"))
mock.ExpectQuery("SELECT.*COUNT.*TABLES").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
conn := engine.Mock(map[string]engine.MockDBConfig{
"test-sqlserver": {Driver: "sqlserver", Mock: mockDB},
}).Connection("test-sqlserver")
t.schema = schema.GetSchemaDialect(db)
db.Exec(dropTable)
db.Exec(createTable)
t.schema = schema.GetSchemaDialect(conn)
}
func (t *sqlServerTest) TestGetColumnListing() {
@@ -80,14 +64,14 @@ func (t *sqlServerTest) TestCompileCreate() {
sql := t.schema.CompileCreate(bp)
t.Equal([]string{
"CREATE TABLE [users] (\n" +
"[id] BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY COMMENT 'ID',\n" +
"[enabled] BIT NOT NULL DEFAULT 1 COMMENT '是否有效',\n" +
"[created_user] CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '创建者',\n" +
"[owned_user] CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '拥有者',\n" +
"[created_at] DATETIME2 NOT NULL DEFAULT GETDATE() COMMENT '创建时间',\n" +
"[updated_at] DATETIME2 NOT NULL DEFAULT GETDATE() COMMENT '更新时间',\n" +
"[deleted_at] DATETIME2 NULL COMMENT '删除时间'\n" +
"CREATE TABLE \"users\" (\n" +
"\"id\" BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,\n" +
"\"enabled\" BIT NOT NULL DEFAULT '1',\n" +
"\"created_user\" CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',\n" +
"\"owned_user\" CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',\n" +
"\"created_at\" DATETIME2 NOT NULL DEFAULT GETDATE(),\n" +
"\"updated_at\" DATETIME2 NOT NULL DEFAULT GETDATE(),\n" +
"\"deleted_at\" DATETIME2 NULL\n" +
")",
}, sql)
@@ -104,10 +88,10 @@ func (t *sqlServerTest) TestCompileAdd() {
sql := t.schema.CompileAdd(bp)
t.Equal([]string{
"ALTER TABLE [users]\n" +
"ADD [name] NVARCHAR(50) NOT NULL COMMENT '用户名',\n" +
"ADD [age] SMALLINT NOT NULL DEFAULT 18 COMMENT '年龄',\n" +
"ADD [sex] NVARCHAR(1) NOT NULL DEFAULT '0' COMMENT '性别'",
"ALTER TABLE \"users\"\n" +
"ADD \"name\" NVARCHAR(50) NOT NULL,\n" +
"ADD \"age\" SMALLINT NOT NULL DEFAULT '18',\n" +
"ADD \"sex\" NVARCHAR(1) NOT NULL DEFAULT '0'",
}, sql)
t.T().Log(sql)
@@ -122,9 +106,9 @@ func (t *sqlServerTest) TestCompileChange() {
sql := t.schema.CompileChange(bp)
t.Equal([]string{
"ALTER TABLE [users]\n" +
"ALTER COLUMN [name] NVARCHAR(50) NOT NULL,\n" +
"ALTER COLUMN [age] SMALLINT NOT NULL DEFAULT 19 COMMENT '年龄'",
"ALTER TABLE \"users\"\n" +
"ALTER COLUMN \"name\" NVARCHAR(50) NOT NULL,\n" +
"ALTER COLUMN \"age\" SMALLINT NOT NULL DEFAULT '19'",
}, sql)
t.T().Log(sql)
@@ -138,9 +122,9 @@ func (t *sqlServerTest) TestCompileDropColumn() {
sql := t.schema.CompileDropColumn(bp)
t.Equal([]string{
"ALTER TABLE [users]\n" +
"DROP COLUMN [age],\n" +
"DROP COLUMN [sex]",
"ALTER TABLE \"users\"\n" +
"DROP COLUMN \"age\",\n" +
"DROP COLUMN \"sex\"",
}, sql)
t.T().Log(sql)
@@ -153,7 +137,7 @@ func (t *sqlServerTest) TestCompileDrop() {
sql := t.schema.CompileDrop(bp)
t.Equal([]string{"DROP TABLE [users]"}, sql)
t.Equal([]string{"DROP TABLE \"users\""}, sql)
t.T().Log(sql)
}
@@ -164,7 +148,7 @@ func (t *sqlServerTest) TestCompileDropIfExists() {
bp.Drop()
sql := t.schema.CompileDropIfExists(bp)
t.Equal([]string{"DROP TABLE IF EXISTS [users]"}, sql)
t.Equal([]string{"DROP TABLE IF EXISTS \"users\""}, sql)
t.T().Log(sql)
}
@@ -176,7 +160,7 @@ func (t *sqlServerTest) TestCompileRename() {
sql := t.schema.CompileRename(bp)
t.Equal([]string{"EXEC sp_rename [users], [user]"}, sql)
t.Equal([]string{"EXEC sp_rename \"users\", \"user\""}, sql)
t.T().Log(sql)
}