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:
@@ -53,7 +53,7 @@ func (this Mysql) CompileCreate(bp *schema.Blueprint) []string {
|
||||
}
|
||||
columns := strings.Join(this.getAddedColumns(bp), ",\n")
|
||||
|
||||
sql := this.GenerateSQL("? TABLE ? (\n?\n)", temporary, db.T(bp.GetTable()), db.L(columns))
|
||||
sql := this.GenerateSQL("? TABLE IF NOT EXISTS ? (\n?\n)", temporary, db.T(bp.GetTable()), db.L(columns))
|
||||
|
||||
charset := bp.Charset
|
||||
if charset == "" {
|
||||
@@ -183,6 +183,8 @@ func (this Mysql) GetColumnType(column *schema.ColumnDefinition) string {
|
||||
return "year"
|
||||
case "uuid":
|
||||
return "char(36)"
|
||||
case "vector":
|
||||
return this.GenerateSQL("VECTOR(?)", column.Length)
|
||||
}
|
||||
panic("Unsupported data type: " + column.Type)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package mysql_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/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
type mysqlTest struct {
|
||||
@@ -17,42 +15,28 @@ type mysqlTest struct {
|
||||
schema schema.Schema
|
||||
}
|
||||
|
||||
var (
|
||||
tableName = "entry"
|
||||
|
||||
dropTable = "DROP TABLE IF EXISTS `entry`;"
|
||||
|
||||
createTable = "CREATE TABLE IF NOT EXISTS `entry` (" +
|
||||
"`id` INT NOT NULL AUTO_INCREMENT ," +
|
||||
"`int` INT NOT NULL UNIQUE," +
|
||||
"`float` FLOAT NOT NULL ," +
|
||||
"`string` VARCHAR(255) NOT NULL ," +
|
||||
"`time` DATETIME NOT NULL ," +
|
||||
"`bool` TINYINT NOT NULL ," +
|
||||
"`bytes` BLOB NOT NULL ," +
|
||||
"PRIMARY KEY (`id`) );"
|
||||
)
|
||||
var tableName = "entry"
|
||||
|
||||
func TestMysqlSuite(t *testing.T) {
|
||||
suite.Run(t, new(mysqlTest))
|
||||
}
|
||||
|
||||
func (t *mysqlTest) SetupSuite() {
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"test-mysql": engine.NewDBConfig("mysql",
|
||||
engine.WithHost(os.Getenv("MYSQL_HOST")),
|
||||
engine.WithPort(os.Getenv("MYSQL_PORT")),
|
||||
engine.WithDatabase(os.Getenv("MYSQL_DB")),
|
||||
engine.WithUsername(os.Getenv("MYSQL_USER")),
|
||||
engine.WithPassword(os.Getenv("MYSQL_PASSWD")),
|
||||
engine.WithParseTime(true),
|
||||
),
|
||||
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-mysql": {Driver: "mysql", Mock: mockDB},
|
||||
}).Connection("test-mysql")
|
||||
|
||||
t.schema = schema.GetSchemaDialect(db)
|
||||
|
||||
// db.Exec(dropTable)
|
||||
db.Exec(createTable)
|
||||
t.schema = schema.GetSchemaDialect(conn)
|
||||
}
|
||||
|
||||
func (t *mysqlTest) TestGetColumnListing() {
|
||||
@@ -84,7 +68,7 @@ func (t *mysqlTest) TestCompileCreate() {
|
||||
sql := t.schema.CompileCreate(bp)
|
||||
|
||||
t.Equal([]string{
|
||||
"CREATE TABLE `users` (\n" +
|
||||
"CREATE TABLE IF NOT EXISTS `users` (\n" +
|
||||
"`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'ID',\n" +
|
||||
"`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效',\n" +
|
||||
"`created_user` char(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '创建者',\n" +
|
||||
@@ -125,11 +109,13 @@ func (t *mysqlTest) TestCompileChange() {
|
||||
bp.String("name", 50).Change("username")
|
||||
bp.SmallInteger("age").Default("19").Comment("年龄").Change()
|
||||
|
||||
sql := "ALTER TABLE `users`\n" +
|
||||
"CHANGE COLUMN `name` `username` varchar(50) NOT NULL,\n" +
|
||||
"CHANGE COLUMN `age` `age` smallint(4) NOT NULL DEFAULT '19' COMMENT '年龄';"
|
||||
sql := t.schema.CompileChange(bp)
|
||||
|
||||
t.Equal(t.schema.CompileChange(bp), sql)
|
||||
t.Equal([]string{
|
||||
"ALTER TABLE `users`\n" +
|
||||
"CHANGE COLUMN `name` `username` varchar(50) NOT NULL,\n" +
|
||||
"CHANGE COLUMN `age` `age` smallint(4) NOT NULL DEFAULT '19' COMMENT '年龄'",
|
||||
}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
@@ -189,4 +175,19 @@ func (t *mysqlTest) TestCompileRename() {
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
// 添加虚拟生成列
|
||||
func (t *mysqlTest) TestCompileAddVirtualColumn() {
|
||||
bp := schema.NewBlueprint("user_assets")
|
||||
bp.String("file_url", 512).VirtualAs("CONCAT('/api/user-asset-raw/', file)").Comment("文件访问URL")
|
||||
|
||||
sql := t.schema.CompileAdd(bp)
|
||||
|
||||
t.Equal([]string{
|
||||
"ALTER TABLE `user_assets`\n" +
|
||||
"ADD COLUMN `file_url` varchar(512) GENERATED ALWAYS AS (CONCAT('/api/user-asset-raw/', file)) VIRTUAL COMMENT '文件访问URL'",
|
||||
}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
// 修改表备注
|
||||
|
||||
Reference in New Issue
Block a user