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:
@@ -0,0 +1,169 @@
|
||||
# DuckDB Schema Dialect
|
||||
|
||||
DuckDB schema dialect 为 DuckDB 数据库提供了 DDL (Data Definition Language) 操作支持。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 创建表 (CREATE TABLE)
|
||||
- ✅ 添加列 (ALTER TABLE ADD COLUMN)
|
||||
- ✅ 修改列 (ALTER TABLE ALTER COLUMN)
|
||||
- ✅ 删除列 (ALTER TABLE DROP COLUMN)
|
||||
- ✅ 删除表 (DROP TABLE)
|
||||
- ✅ 重命名表 (ALTER TABLE RENAME TO)
|
||||
- ✅ 表注释 (COMMENT ON TABLE)
|
||||
- ✅ 列注释 (通过 Comment 属性)
|
||||
- ✅ 自增主键 (SERIAL/BIGSERIAL)
|
||||
- ✅ 查询表是否存在
|
||||
- ✅ 获取表的列列表
|
||||
|
||||
## 支持的数据类型
|
||||
|
||||
| Schema 类型 | DuckDB 类型 | 说明 |
|
||||
|------------|-------------|------|
|
||||
| char | CHAR(n) | 固定长度字符 |
|
||||
| string | VARCHAR(n) | 可变长度字符串 |
|
||||
| text | TEXT | 长文本 |
|
||||
| integer | INTEGER | 整数 |
|
||||
| bigInteger | BIGINT | 大整数 |
|
||||
| smallInteger | SMALLINT | 小整数 |
|
||||
| tinyInteger | TINYINT | 微整数 |
|
||||
| decimal | DECIMAL(m,d) | 定点数 |
|
||||
| boolean | BOOLEAN | 布尔值 |
|
||||
| json | JSON | JSON 数据 |
|
||||
| binary | BLOB | 二进制数据 |
|
||||
| datetime | TIMESTAMP | 日期时间 |
|
||||
| timestamp | TIMESTAMP | 时间戳 |
|
||||
| date | DATE | 日期 |
|
||||
| time | TIME | 时间 |
|
||||
| uuid | UUID | UUID |
|
||||
|
||||
## 使用示例
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.fsdpf.net/go/db/engine"
|
||||
"git.fsdpf.net/go/db/schema"
|
||||
|
||||
_ "git.fsdpf.net/go/db/dialect/duckdb"
|
||||
_ "git.fsdpf.net/go/db/schema/dialect/duckdb"
|
||||
_ "github.com/marcboeker/go-duckdb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建数据库连接
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"duckdb": engine.NewDBConfig("duckdb",
|
||||
engine.WithDuckDBFile("mydb.duckdb"), // 或者 "" 表示内存数据库
|
||||
),
|
||||
}).Connection("duckdb")
|
||||
|
||||
// 获取 schema 实现
|
||||
s := schema.GetSchemaDialect(db)
|
||||
|
||||
// 创建表
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.Create()
|
||||
|
||||
bp.BigIncrements("id").AutoIncrement().Comment("用户ID")
|
||||
bp.String("name", 50).Comment("用户名")
|
||||
bp.String("email", 100).Comment("邮箱")
|
||||
bp.Integer("age").Nullable().Comment("年龄")
|
||||
bp.Boolean("active").Default("1").Comment("是否激活")
|
||||
bp.Timestamp("created_at").UseCurrent().Comment("创建时间")
|
||||
bp.Timestamp("updated_at").UseCurrent().Comment("更新时间")
|
||||
|
||||
// 编译并执行
|
||||
sqls := s.CompileCreate(bp)
|
||||
for _, sql := range sqls {
|
||||
db.Exec(sql)
|
||||
}
|
||||
|
||||
// 添加新列
|
||||
bp2 := schema.NewBlueprint("users")
|
||||
bp2.String("phone", 20).Nullable().Comment("电话")
|
||||
|
||||
sqls = s.CompileAdd(bp2)
|
||||
for _, sql := range sqls {
|
||||
db.Exec(sql)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## DuckDB 特性说明
|
||||
|
||||
### 自增主键
|
||||
|
||||
DuckDB 使用 `SERIAL` 类型系列实现自增:
|
||||
|
||||
- `SERIAL` - 对应 INTEGER 自增
|
||||
- `BIGSERIAL` - 对应 BIGINT 自增
|
||||
- `SMALLSERIAL` - 对应 SMALLINT 自增
|
||||
|
||||
```go
|
||||
bp.BigIncrements("id").AutoIncrement()
|
||||
// 生成: "id" BIGSERIAL NOT NULL PRIMARY KEY
|
||||
```
|
||||
|
||||
### 列修改
|
||||
|
||||
DuckDB 的 ALTER COLUMN 操作需要分别执行:
|
||||
|
||||
```go
|
||||
bp.String("name", 100).Change("username")
|
||||
// 生成多条 SQL:
|
||||
// 1. ALTER TABLE "users" RENAME COLUMN "name" TO "username"
|
||||
// 2. ALTER TABLE "users" ALTER COLUMN "username" TYPE VARCHAR(100)
|
||||
// 3. ALTER TABLE "users" ALTER COLUMN "username" SET NOT NULL
|
||||
```
|
||||
|
||||
### 表注释和列注释
|
||||
|
||||
DuckDB 使用标准 SQL 的 COMMENT ON 语法:
|
||||
|
||||
```go
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.Comment = "用户表"
|
||||
// 生成: COMMENT ON TABLE "users" IS '用户表'
|
||||
```
|
||||
|
||||
列注释暂时不支持单独的 COMMENT ON COLUMN 语句。
|
||||
|
||||
### 标识符引用
|
||||
|
||||
DuckDB 使用双引号引用标识符 (PostgreSQL 风格):
|
||||
|
||||
```sql
|
||||
CREATE TABLE "users" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"name" VARCHAR(50) NOT NULL
|
||||
)
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
运行测试:
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
go test ./schema/dialect/duckdb/...
|
||||
|
||||
# 运行示例测试
|
||||
go test -v ./schema/dialect/duckdb/... -run Example
|
||||
|
||||
# 运行特定测试
|
||||
go test -v ./schema/dialect/duckdb/... -run TestCompileCreate
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. DuckDB 是分析型数据库,主要用于 OLAP 场景
|
||||
2. 内存数据库性能优异,适合测试和临时数据处理
|
||||
3. 支持 PostgreSQL 兼容的大部分语法
|
||||
4. 不支持所有的 MySQL 特性 (如 UNSIGNED、AUTO_INCREMENT 等)
|
||||
|
||||
## 相关链接
|
||||
|
||||
- [DuckDB 官方文档](https://duckdb.org/docs/)
|
||||
- [go-duckdb 驱动](https://github.com/marcboeker/go-duckdb)
|
||||
@@ -0,0 +1,271 @@
|
||||
package duckdb
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.fsdpf.net/go/db"
|
||||
"git.fsdpf.net/go/db/dialect/duckdb"
|
||||
"git.fsdpf.net/go/db/internal/sb"
|
||||
"git.fsdpf.net/go/db/schema"
|
||||
"git.fsdpf.net/go/db/sqlgen"
|
||||
)
|
||||
|
||||
type DuckDB struct {
|
||||
db *db.Database
|
||||
esg sqlgen.ExpressionSQLGenerator
|
||||
}
|
||||
|
||||
var duckdbDefaultModifiers = []string{
|
||||
"Nullable", "Default", "Increment", "Comment",
|
||||
}
|
||||
|
||||
func (this DuckDB) GetColumnListing(table string) ([]string, error) {
|
||||
cols := []string{}
|
||||
err := this.db.From(db.S("information_schema").Table("columns")).Where(
|
||||
db.C("table_name").Eq(table),
|
||||
).Pluck(&cols, "column_name")
|
||||
return cols, err
|
||||
}
|
||||
|
||||
func (this DuckDB) TableExists(table string) (bool, error) {
|
||||
result, err := this.db.From(db.S("information_schema").Table("tables")).Where(db.Ex{
|
||||
"table_name": table,
|
||||
"table_type": "BASE TABLE",
|
||||
}).Count()
|
||||
return result > 0, err
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileCreate(bp *schema.Blueprint) []string {
|
||||
temporary := db.L("CREATE")
|
||||
if bp.Temporary {
|
||||
temporary = db.L("CREATE TEMPORARY")
|
||||
}
|
||||
columns := strings.Join(this.getAddedColumns(bp), ",\n")
|
||||
sql := this.GenerateSQL("? TABLE ? (\n?\n)", temporary, db.T(bp.GetTable()), db.L(columns))
|
||||
|
||||
if comment := bp.Comment; comment != "" {
|
||||
sql = sql[:len(sql)-1] + ";" + this.GenerateSQL("\nCOMMENT ON TABLE ? IS ?", db.T(bp.GetTable()), db.V(comment))
|
||||
}
|
||||
|
||||
return []string{sql}
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileAdd(bp *schema.Blueprint) []string {
|
||||
columns := strings.Join(schema.PrefixArray("ADD COLUMN", this.getAddedColumns(bp)), ",\n")
|
||||
sql := this.GenerateSQL("ALTER TABLE ?\n?", db.T(bp.GetTable()), db.L(columns))
|
||||
return []string{sql}
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileChange(bp *schema.Blueprint) []string {
|
||||
sqls := []string{}
|
||||
for _, column := range bp.GetChangedColumns() {
|
||||
name := column.Name
|
||||
rename := column.Name
|
||||
if column.GetRename() != "" {
|
||||
rename = column.GetRename()
|
||||
}
|
||||
|
||||
// DuckDB 需要分别修改类型和约束
|
||||
if rename != name {
|
||||
sqls = append(sqls, this.GenerateSQL("ALTER TABLE ? RENAME COLUMN ? TO ?",
|
||||
db.T(bp.GetTable()), db.C(name), db.C(rename)))
|
||||
}
|
||||
|
||||
// 修改类型
|
||||
sql := this.GenerateSQL("ALTER TABLE ? ALTER COLUMN ? TYPE ?",
|
||||
db.T(bp.GetTable()), db.C(rename), db.L(this.GetColumnType(column)))
|
||||
sqls = append(sqls, sql)
|
||||
|
||||
// 修改 nullable
|
||||
if column.IsNullable() {
|
||||
sqls = append(sqls, this.GenerateSQL("ALTER TABLE ? ALTER COLUMN ? DROP NOT NULL",
|
||||
db.T(bp.GetTable()), db.C(rename)))
|
||||
} else {
|
||||
sqls = append(sqls, this.GenerateSQL("ALTER TABLE ? ALTER COLUMN ? SET NOT NULL",
|
||||
db.T(bp.GetTable()), db.C(rename)))
|
||||
}
|
||||
|
||||
// 修改默认值
|
||||
v := column.GetDefault()
|
||||
if v != nil {
|
||||
sqls = append(sqls, this.GenerateSQL("ALTER TABLE ? ALTER COLUMN ? SET DEFAULT ?",
|
||||
db.T(bp.GetTable()), db.C(rename), v))
|
||||
} else if column.IsUseCurrent() {
|
||||
sqls = append(sqls, this.GenerateSQL("ALTER TABLE ? ALTER COLUMN ? SET DEFAULT CURRENT_TIMESTAMP",
|
||||
db.T(bp.GetTable()), db.C(rename)))
|
||||
}
|
||||
}
|
||||
return sqls
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileDropColumn(bp *schema.Blueprint) []string {
|
||||
columns := []string{}
|
||||
for _, item := range bp.GetCommands() {
|
||||
if item.Type != "dropColumn" {
|
||||
continue
|
||||
}
|
||||
for _, col := range item.Columns {
|
||||
columns = append(columns, this.GenerateSQL("DROP COLUMN ?", db.C(col)))
|
||||
}
|
||||
}
|
||||
sql := this.GenerateSQL("ALTER TABLE ?\n?", db.T(bp.GetTable()), db.L(strings.Join(columns, ",\n")))
|
||||
return []string{sql}
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileDrop(bp *schema.Blueprint) []string {
|
||||
return []string{this.GenerateSQL("DROP TABLE ?", db.T(bp.GetTable()))}
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileDropIfExists(bp *schema.Blueprint) []string {
|
||||
return []string{this.GenerateSQL("DROP TABLE IF EXISTS ?", db.T(bp.GetTable()))}
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileRename(bp *schema.Blueprint) []string {
|
||||
toName := ""
|
||||
commands := bp.GetCommands()
|
||||
if len(commands) == 0 {
|
||||
panic("new table undefined")
|
||||
}
|
||||
toName = commands[0].To
|
||||
if toName == "" {
|
||||
panic("new table undefined")
|
||||
}
|
||||
return []string{this.GenerateSQL("ALTER TABLE ? RENAME TO ?", db.T(bp.GetTable()), db.T(toName))}
|
||||
}
|
||||
|
||||
func (this DuckDB) CompileModifyComment(bp *schema.Blueprint) []string {
|
||||
return []string{
|
||||
this.GenerateSQL("COMMENT ON TABLE ? IS ?", db.T(bp.GetTable()), db.V(bp.Comment)),
|
||||
}
|
||||
}
|
||||
|
||||
func (this DuckDB) addModifiers(sql string, bp *schema.Blueprint, column *schema.ColumnDefinition) string {
|
||||
for _, modifier := range duckdbDefaultModifiers {
|
||||
sql += this.GetColumnModifier(modifier, bp, column)
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
func (this DuckDB) GetColumnType(column *schema.ColumnDefinition) string {
|
||||
switch column.Type {
|
||||
case "char":
|
||||
return this.GenerateSQL("CHAR(?)", column.Length)
|
||||
case "string":
|
||||
return this.GenerateSQL("VARCHAR(?)", column.Length)
|
||||
case "text":
|
||||
return "TEXT"
|
||||
case "integer":
|
||||
return "INTEGER"
|
||||
case "bigInteger":
|
||||
return "BIGINT"
|
||||
case "smallInteger":
|
||||
return "SMALLINT"
|
||||
case "tinyInteger":
|
||||
return "TINYINT"
|
||||
case "decimal":
|
||||
return this.GenerateSQL("DECIMAL(?,?)", column.Total, column.Places)
|
||||
case "boolean":
|
||||
return "BOOLEAN"
|
||||
case "enum":
|
||||
// DuckDB 支持 ENUM 类型
|
||||
return this.GenerateSQL("VARCHAR(?)", column.Length)
|
||||
case "json":
|
||||
return "JSON"
|
||||
case "binary":
|
||||
return "BLOB"
|
||||
case "datetime":
|
||||
return "TIMESTAMP"
|
||||
case "timestamp":
|
||||
return "TIMESTAMP"
|
||||
case "date":
|
||||
return "DATE"
|
||||
case "time":
|
||||
return "TIME"
|
||||
case "year":
|
||||
return "SMALLINT"
|
||||
case "uuid":
|
||||
return "UUID"
|
||||
}
|
||||
panic("Unsupported data type: " + column.Type)
|
||||
}
|
||||
|
||||
func (this DuckDB) GetColumnModifier(modifier string, bp *schema.Blueprint, column *schema.ColumnDefinition) string {
|
||||
switch modifier {
|
||||
case "Nullable":
|
||||
if column.IsNullable() {
|
||||
return " NULL"
|
||||
}
|
||||
return " NOT NULL"
|
||||
case "Default":
|
||||
v := column.GetDefault()
|
||||
if v == nil {
|
||||
if column.IsUseCurrent() {
|
||||
return " DEFAULT CURRENT_TIMESTAMP"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return this.GenerateSQL(" DEFAULT ?", v)
|
||||
case "Increment":
|
||||
if column.IsAutoIncrement() {
|
||||
// DuckDB 使用 SERIAL 或者 SEQUENCE
|
||||
return " PRIMARY KEY"
|
||||
}
|
||||
case "Comment":
|
||||
// DuckDB 支持列注释,但需要在 CREATE TABLE 后使用 COMMENT ON
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this DuckDB) getAddedColumns(bp *schema.Blueprint) (columns []string) {
|
||||
for _, column := range bp.GetAddedColumns() {
|
||||
colType := this.GetColumnType(column)
|
||||
// 对于自增列,使用 SERIAL 类型
|
||||
if column.IsAutoIncrement() {
|
||||
switch column.Type {
|
||||
case "bigInteger":
|
||||
colType = "BIGSERIAL"
|
||||
case "smallInteger":
|
||||
colType = "SMALLSERIAL"
|
||||
default:
|
||||
colType = "SERIAL"
|
||||
}
|
||||
}
|
||||
sql := this.GenerateSQL("? ?", db.C(column.Name), db.L(colType))
|
||||
columns = append(columns, this.addModifiers(sql, bp, column))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this DuckDB) getChangedColumns(bp *schema.Blueprint) (columns []string) {
|
||||
for _, column := range bp.GetChangedColumns() {
|
||||
name := column.Name
|
||||
rename := column.Name
|
||||
if column.GetRename() != "" {
|
||||
rename = column.GetRename()
|
||||
}
|
||||
sql := this.GenerateSQL("? ?", db.C(rename), db.L(this.GetColumnType(column)))
|
||||
columns = append(columns, this.addModifiers(sql, bp, column))
|
||||
if rename != name {
|
||||
// 需要额外的重命名语句
|
||||
columns = append(columns, this.GenerateSQL("RENAME COLUMN ? TO ?", db.C(name), db.C(rename)))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this DuckDB) GenerateSQL(sql string, args ...any) string {
|
||||
sb := sb.NewSQLBuilder(false)
|
||||
this.esg.Generate(sb, db.L(sql, args...))
|
||||
result, _, _ := sb.ToSQL()
|
||||
return result
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema.RegisterDialect("duckdb", func(db *db.Database) schema.Schema {
|
||||
return &DuckDB{
|
||||
db: db,
|
||||
esg: sqlgen.NewExpressionSQLGenerator("duckdb", duckdb.DialectOptions()),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package duckdb_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.fsdpf.net/go/db/engine"
|
||||
"git.fsdpf.net/go/db/schema"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
_ "github.com/marcboeker/go-duckdb"
|
||||
)
|
||||
|
||||
type duckDBTest struct {
|
||||
suite.Suite
|
||||
schema schema.Schema
|
||||
}
|
||||
|
||||
var (
|
||||
tableName = "entry"
|
||||
|
||||
dropTable = "DROP TABLE IF EXISTS entry;"
|
||||
|
||||
createTable = "CREATE TABLE entry (" +
|
||||
"id INTEGER PRIMARY KEY," +
|
||||
"int INTEGER NOT NULL UNIQUE," +
|
||||
"float REAL NOT NULL," +
|
||||
"string VARCHAR(255) NOT NULL," +
|
||||
"time TIMESTAMP NOT NULL," +
|
||||
"bool BOOLEAN NOT NULL," +
|
||||
"bytes BLOB NOT NULL" +
|
||||
");"
|
||||
)
|
||||
|
||||
func TestDuckDBSuite(t *testing.T) {
|
||||
suite.Run(t, new(duckDBTest))
|
||||
}
|
||||
|
||||
func (t *duckDBTest) SetupSuite() {
|
||||
dbPath := os.Getenv("DUCKDB_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = ""
|
||||
}
|
||||
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"test-duckdb": engine.NewDBConfig("duckdb",
|
||||
engine.WithDuckDBFile(dbPath),
|
||||
),
|
||||
}).Connection("test-duckdb")
|
||||
|
||||
t.schema = schema.GetSchemaDialect(db)
|
||||
|
||||
db.Exec(dropTable)
|
||||
db.Exec(createTable)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestGetColumnListing() {
|
||||
result, err := t.schema.GetColumnListing(tableName)
|
||||
t.Require().NoError(err)
|
||||
t.Require().ElementsMatch([]string{"id", "int", "float", "string", "time", "bool", "bytes"}, result)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestTableExists() {
|
||||
result, err := t.schema.TableExists(tableName)
|
||||
t.Require().NoError(err)
|
||||
t.Require().Equal(result, true)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileCreate() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.Create()
|
||||
|
||||
bp.BigIncrements("id").AutoIncrement().Comment("ID")
|
||||
bp.Boolean("enabled").Default("1").Comment("是否有效")
|
||||
bp.Char("created_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("创建者")
|
||||
bp.Char("owned_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("拥有者")
|
||||
bp.Timestamp("created_at").UseCurrent().Comment("创建时间")
|
||||
bp.Timestamp("updated_at").UseCurrent().Comment("更新时间")
|
||||
bp.DateTime("deleted_at").Nullable().Comment("删除时间")
|
||||
|
||||
sql := t.schema.CompileCreate(bp)
|
||||
|
||||
t.Equal([]string{
|
||||
"CREATE TABLE \"users\" (\n" +
|
||||
"\"id\" BIGSERIAL NOT NULL PRIMARY KEY,\n" +
|
||||
"\"enabled\" BOOLEAN 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\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" +
|
||||
"\"updated_at\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n" +
|
||||
"\"deleted_at\" TIMESTAMP NULL\n" +
|
||||
")",
|
||||
}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileAdd() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
|
||||
bp.String("name", 50).Comment("用户名")
|
||||
bp.SmallInteger("age").Default("18").Comment("年龄")
|
||||
bp.String("sex", 1).Default("0").Comment("性别")
|
||||
|
||||
sql := t.schema.CompileAdd(bp)
|
||||
|
||||
t.Equal([]string{
|
||||
"ALTER TABLE \"users\"\n" +
|
||||
"ADD COLUMN \"name\" VARCHAR(50) NOT NULL,\n" +
|
||||
"ADD COLUMN \"age\" SMALLINT NOT NULL DEFAULT '18',\n" +
|
||||
"ADD COLUMN \"sex\" VARCHAR(1) NOT NULL DEFAULT '0'",
|
||||
}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileChange() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
|
||||
bp.String("name", 50).Change("username")
|
||||
bp.SmallInteger("age").Default("19").Comment("年龄").Change()
|
||||
|
||||
sql := t.schema.CompileChange(bp)
|
||||
|
||||
t.Require().NotEmpty(sql)
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileDropColumn() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
|
||||
bp.DropColumn("age", "sex")
|
||||
|
||||
sql := t.schema.CompileDropColumn(bp)
|
||||
|
||||
t.Equal([]string{
|
||||
"ALTER TABLE \"users\"\n" +
|
||||
"DROP COLUMN \"age\",\n" +
|
||||
"DROP COLUMN \"sex\"",
|
||||
}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileDrop() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
|
||||
bp.Drop()
|
||||
|
||||
sql := t.schema.CompileDrop(bp)
|
||||
|
||||
t.Equal([]string{"DROP TABLE \"users\""}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileDropIfExists() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
|
||||
bp.Drop()
|
||||
|
||||
sql := t.schema.CompileDropIfExists(bp)
|
||||
t.Equal([]string{"DROP TABLE IF EXISTS \"users\""}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileRename() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
|
||||
bp.Rename("user")
|
||||
|
||||
sql := t.schema.CompileRename(bp)
|
||||
|
||||
t.Equal([]string{"ALTER TABLE \"users\" RENAME TO \"user\""}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
|
||||
func (t *duckDBTest) TestCompileModifyComment() {
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.Comment = "用户表"
|
||||
|
||||
sql := t.schema.CompileModifyComment(bp)
|
||||
|
||||
t.Equal([]string{"COMMENT ON TABLE \"users\" IS '用户表'"}, sql)
|
||||
|
||||
t.T().Log(sql)
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package duckdb_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.fsdpf.net/go/db/engine"
|
||||
"git.fsdpf.net/go/db/schema"
|
||||
|
||||
_ "git.fsdpf.net/go/db/dialect/duckdb"
|
||||
_ "git.fsdpf.net/go/db/schema/dialect/duckdb"
|
||||
_ "github.com/marcboeker/go-duckdb"
|
||||
)
|
||||
|
||||
func Example_createTable() {
|
||||
// 创建内存数据库连接
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"duckdb": engine.NewDBConfig("duckdb",
|
||||
engine.WithDuckDBFile(""), // 空字符串表示内存数据库
|
||||
),
|
||||
}).Connection("duckdb")
|
||||
|
||||
// 获取 schema 方言实现
|
||||
s := schema.GetSchemaDialect(db)
|
||||
|
||||
// 创建表结构定义
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.Create()
|
||||
|
||||
bp.BigIncrements("id").AutoIncrement().Comment("用户ID")
|
||||
bp.String("name", 50).Comment("用户名")
|
||||
bp.String("email", 100).Comment("邮箱")
|
||||
bp.Integer("age").Nullable().Comment("年龄")
|
||||
bp.Boolean("active").Default("1").Comment("是否激活")
|
||||
bp.Timestamp("created_at").UseCurrent().Comment("创建时间")
|
||||
bp.Timestamp("updated_at").UseCurrent().Comment("更新时间")
|
||||
|
||||
// 编译生成 CREATE TABLE SQL
|
||||
sqls := s.CompileCreate(bp)
|
||||
for _, sql := range sqls {
|
||||
fmt.Println(sql)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// CREATE TABLE "users" (
|
||||
// "id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
// "name" VARCHAR(50) NOT NULL,
|
||||
// "email" VARCHAR(100) NOT NULL,
|
||||
// "age" INTEGER NULL,
|
||||
// "active" BOOLEAN NOT NULL DEFAULT '1',
|
||||
// "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
// "updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
// )
|
||||
}
|
||||
|
||||
func Example_addColumn() {
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"duckdb": engine.NewDBConfig("duckdb",
|
||||
engine.WithDuckDBFile(""),
|
||||
),
|
||||
}).Connection("duckdb")
|
||||
|
||||
s := schema.GetSchemaDialect(db)
|
||||
|
||||
// 添加新列
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.String("phone", 20).Nullable().Comment("电话号码")
|
||||
bp.Text("address").Nullable().Comment("地址")
|
||||
|
||||
sqls := s.CompileAdd(bp)
|
||||
for _, sql := range sqls {
|
||||
fmt.Println(sql)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ALTER TABLE "users"
|
||||
// ADD COLUMN "phone" VARCHAR(20) NULL,
|
||||
// ADD COLUMN "address" TEXT NULL
|
||||
}
|
||||
|
||||
func Example_dropColumn() {
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"duckdb": engine.NewDBConfig("duckdb",
|
||||
engine.WithDuckDBFile(""),
|
||||
),
|
||||
}).Connection("duckdb")
|
||||
|
||||
s := schema.GetSchemaDialect(db)
|
||||
|
||||
// 删除列
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.DropColumn("phone", "address")
|
||||
|
||||
sqls := s.CompileDropColumn(bp)
|
||||
for _, sql := range sqls {
|
||||
fmt.Println(sql)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ALTER TABLE "users"
|
||||
// DROP COLUMN "phone",
|
||||
// DROP COLUMN "address"
|
||||
}
|
||||
|
||||
func Example_renameTable() {
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"duckdb": engine.NewDBConfig("duckdb",
|
||||
engine.WithDuckDBFile(""),
|
||||
),
|
||||
}).Connection("duckdb")
|
||||
|
||||
s := schema.GetSchemaDialect(db)
|
||||
|
||||
// 重命名表
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.Rename("customers")
|
||||
|
||||
sqls := s.CompileRename(bp)
|
||||
for _, sql := range sqls {
|
||||
fmt.Println(sql)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// ALTER TABLE "users" RENAME TO "customers"
|
||||
}
|
||||
|
||||
func Example_modifyComment() {
|
||||
db := engine.Open(map[string]engine.DBConfig{
|
||||
"duckdb": engine.NewDBConfig("duckdb",
|
||||
engine.WithDuckDBFile(""),
|
||||
),
|
||||
}).Connection("duckdb")
|
||||
|
||||
s := schema.GetSchemaDialect(db)
|
||||
|
||||
// 修改表注释
|
||||
bp := schema.NewBlueprint("users")
|
||||
bp.Comment = "用户信息表"
|
||||
|
||||
sqls := s.CompileModifyComment(bp)
|
||||
for _, sql := range sqls {
|
||||
fmt.Println(sql)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// COMMENT ON TABLE "users" IS '用户信息表'
|
||||
}
|
||||
@@ -73,10 +73,10 @@ func (this Sqlite3) CompileCreate(bp *schema.Blueprint) (sqls []string) {
|
||||
columns = append(columns, sql)
|
||||
}
|
||||
|
||||
prefix := "? TABLE ? (\n?\n)"
|
||||
prefix := "? TABLE IF NOT EXISTS ? (\n?\n)"
|
||||
|
||||
if comment := bp.Comment; comment != "" {
|
||||
prefix = "? TABLE ? ( -- " + comment + "\n?\n)"
|
||||
prefix = "? TABLE IF NOT EXISTS ? ( -- " + comment + "\n?\n)"
|
||||
}
|
||||
|
||||
sqls = append(sqls,
|
||||
|
||||
@@ -81,7 +81,7 @@ func (t *sqlite3Test) TestCompileCreate() {
|
||||
sql := t.schema.CompileCreate(bp)
|
||||
|
||||
t.Equal([]string{
|
||||
"CREATE TABLE `users` (\n" +
|
||||
"CREATE TABLE IF NOT EXISTS `users` (\n" +
|
||||
"`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, -- ID\n" +
|
||||
"`enabled` tinyint(1) NOT NULL DEFAULT '1', -- 是否有效\n" +
|
||||
"`created_user` char(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', -- 创建者\n" +
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"git.fsdpf.net/go/db/schema"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
_ "github.com/microsoft/go-mssqldb"
|
||||
)
|
||||
|
||||
type sqlServerTest struct {
|
||||
|
||||
Reference in New Issue
Block a user