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
+10 -2
View File
@@ -13,7 +13,7 @@ import (
)
var (
sqlite3DefaultModifiers = []string{"VirtualAs", "StoredAs", "Hidden", "Nullable", "Default", "Increment"}
sqlite3DefaultModifiers = []string{"Hidden", "Nullable", "Default", "Increment"}
sqlite3Serials = []string{"bigInteger", "integer", "mediumInteger", "smallInteger", "tinyInteger"}
)
@@ -191,6 +191,9 @@ func (this Sqlite3) GetColumnModifier(modifier string, bp *schema.Blueprint, col
return "NOT NULL"
}
case "Default":
if column.IsHidden() {
return ""
}
if column.IsUseCurrent() {
return " DEFAULT CURRENT_TIMESTAMP"
}
@@ -223,7 +226,10 @@ func (this Sqlite3) GetColumnType(column *schema.ColumnDefinition) string {
case "text":
return "text"
case "integer":
return this.GenerateSQL("INTEGER(?)", column.Length)
if column.Length > 0 {
return this.GenerateSQL("INTEGER(?)", column.Length)
}
return "INTEGER"
case "bigInteger":
return "bigint(20)"
case "tinyInteger":
@@ -252,6 +258,8 @@ func (this Sqlite3) GetColumnType(column *schema.ColumnDefinition) string {
return "year"
case "uuid":
return "char(36)"
case "vector":
return this.GenerateSQL("float[?]", column.Length)
}
panic("Unsupported data type: " + column.Type)
}
+30 -36
View File
@@ -1,32 +1,16 @@
package sqlite3_test
import (
"os"
"testing"
"git.fsdpf.net/go/db"
"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/mattn/go-sqlite3"
)
var (
tableName = "entry"
dropTable = "DROP TABLE IF EXISTS `entry`;"
createTable = "CREATE TABLE IF NOT EXISTS `entry` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT," +
"`int` INT NOT NULL ," +
"`float` FLOAT NOT NULL ," +
"`string` VARCHAR(255) NOT NULL ," +
"`time` DATETIME NOT NULL ," +
"`bool` TINYINT NOT NULL ," +
"`bytes` BLOB NOT NULL" +
");"
)
var tableName = "entry"
type sqlite3Test struct {
suite.Suite
@@ -38,18 +22,28 @@ func TestSqlite3Suite(t *testing.T) {
}
func (t *sqlite3Test) SetupSuite() {
db := engine.Open(map[string]engine.DBConfig{
"test-sqlite3": engine.NewDBConfig("sqlite3",
engine.WithSQLiteFile(os.Getenv("DB_FILE")),
),
mockDB, mock, _ := sqlmock.New()
// TestGetColumnListing: PRAGMA table_info(entry)
mock.ExpectQuery("PRAGMA table_info").
WillReturnRows(sqlmock.NewRows([]string{"cid", "name", "type", "notnull", "dflt_value", "pk"}).
AddRow(0, "id", "INTEGER", 1, nil, 1).
AddRow(1, "int", "INT", 1, nil, 0).
AddRow(2, "float", "FLOAT", 1, nil, 0).
AddRow(3, "string", "VARCHAR(255)", 1, nil, 0).
AddRow(4, "time", "DATETIME", 1, nil, 0).
AddRow(5, "bool", "TINYINT", 1, nil, 0).
AddRow(6, "bytes", "BLOB", 1, nil, 0))
// TestTableExists: SELECT COUNT(*) FROM sqlite_master WHERE ...
mock.ExpectQuery("SELECT.*COUNT.*sqlite_master").
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
conn := engine.Mock(map[string]engine.MockDBConfig{
"test-sqlite3": {Driver: "sqlite3", Mock: mockDB},
}).Connection("test-sqlite3")
t.schema = schema.GetSchemaDialect(db)
// db.Exec(dropTable)
if _, err := db.Exec(createTable); err != nil {
t.Require().NoError(err)
}
t.schema = schema.GetSchemaDialect(conn)
}
func (t *sqlite3Test) TestTableExists() {
@@ -123,14 +117,14 @@ func (t *sqlite3Test) TestCompileChange() {
sql := t.schema.CompileChange(bp)
t.Equal(len([]string{
"ALTER TABLE `users` ADD COLUMN `name_1742738970482105700` varchar(50) NOT NULL",
"UPDATE `users` SET `name_1742738970482105700`=`name`",
"ALTER TABLE `users` ADD COLUMN `name_tmp` varchar(50) NOT NULL",
"UPDATE `users` SET `name_tmp`=`name`",
"ALTER TABLE `users` DROP COLUMN `name`",
"ALTER TABLE `users` RENAME COLUMN `name_1742738970482105700` TO `username`",
"ALTER TABLE `users` ADD COLUMN `age_1742738970482151800` smallint(4) NOT NULL DEFAULT '19'",
"UPDATE `users` SET `age_1742738970482151800`=`age`",
"ALTER TABLE `users` RENAME COLUMN `name_tmp` TO `username`",
"ALTER TABLE `users` ADD COLUMN `age_tmp` smallint(4) NOT NULL DEFAULT '19'",
"UPDATE `users` SET `age_tmp`=`age`",
"ALTER TABLE `users` DROP COLUMN `age`",
"ALTER TABLE `users` RENAME COLUMN `age_1742738970482151800` TO `age`",
"ALTER TABLE `users` RENAME COLUMN `age_tmp` TO `age`",
}), len(sql))
t.T().Log(sql)
@@ -195,9 +189,9 @@ func (t *sqlite3Test) TestCompileCreate_HiddenColumn() {
"CREATE TABLE IF NOT EXISTS `api_users` (\n" +
"`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT\n" +
"`name` varchar(100) NULL\n" +
"`age` INTEGER(0) NOT NULL\n" +
"`age` INTEGER NOT NULL\n" +
"`token` varchar(255) HIDDEN\n" +
"`page_size` INTEGER(0) HIDDEN\n" +
"`page_size` INTEGER HIDDEN\n" +
")",
}, sql)