package schema import ( "strings" "github.com/samber/lo" "git.fsdpf.net/go/db" ) type Blueprint struct { table string // the table the blueprint describes. columns []*ColumnDefinition // columns that should be added to the table commands []*Command // temporary bool // Whether to make the table temporary. charset string // The default character set that should be used for the table. collation string // The collation that should be used for the table. engine string // The engine that should be used for the table. } type Command struct { Type string CommandOptions } type CommandOptions struct { Index string Columns []string Algorithm string To string From string } func NewBlueprint(table string) *Blueprint { return &Blueprint{table: table, charset: "utf8mb4", collation: "utf8mb4_general_ci"} } // 字符串 func (this *Blueprint) Char(column string, length int) *ColumnDefinition { if length == 0 { length = 255 } return this.addColumn("char", column, &ColumnOptions{Length: length}) } // 可变长度字符串 func (this *Blueprint) String(column string, length int) *ColumnDefinition { if length == 0 { length = 255 } return this.addColumn("string", column, &ColumnOptions{Length: length}) } // 文本 func (this *Blueprint) Text(column string) *ColumnDefinition { return this.addColumn("text", column, nil) } // 整型 func (this *Blueprint) Integer(column string, params ...bool) *ColumnDefinition { autoIncrement := false unsigned := false if len(params) > 0 { autoIncrement = params[0] } if len(params) > 1 { unsigned = params[1] } return this.addColumn("integer", column, &ColumnOptions{autoIncrement: autoIncrement, unsigned: unsigned}) } // 迷你整型 1 byte func (this *Blueprint) TinyInteger(column string, params ...bool) *ColumnDefinition { autoIncrement := false unsigned := false if len(params) > 0 { autoIncrement = params[0] } if len(params) > 1 { unsigned = params[1] } return this.addColumn("tinyInteger", column, &ColumnOptions{autoIncrement: autoIncrement, unsigned: unsigned}) } // 小整型 2 byte func (this *Blueprint) SmallInteger(column string, params ...bool) *ColumnDefinition { autoIncrement := false unsigned := false if len(params) > 0 { autoIncrement = params[0] } if len(params) > 1 { unsigned = params[1] } return this.addColumn("smallInteger", column, &ColumnOptions{autoIncrement: autoIncrement, unsigned: unsigned}) } // 大整型 2 byte func (this *Blueprint) BigInteger(column string, params ...bool) *ColumnDefinition { autoIncrement := false unsigned := false if len(params) > 0 { autoIncrement = params[0] } if len(params) > 1 { unsigned = params[1] } return this.addColumn("bigInteger", column, &ColumnOptions{autoIncrement: autoIncrement, unsigned: unsigned}) } // 无符号整型 func (this *Blueprint) UnsignedInteger(column string, params ...bool) *ColumnDefinition { autoIncrement := false if len(params) > 0 { autoIncrement = params[0] } return this.Integer(column, autoIncrement, true) } // 无符号迷你整型 1 byte func (this *Blueprint) UnsignedTinyInteger(column string, params ...bool) *ColumnDefinition { autoIncrement := false if len(params) > 0 { autoIncrement = params[0] } return this.TinyInteger(column, autoIncrement, true) } // 无符号小整型 2 byte func (this *Blueprint) UnsignedSmallInteger(column string, params ...bool) *ColumnDefinition { autoIncrement := false if len(params) > 0 { autoIncrement = params[0] } return this.SmallInteger(column, autoIncrement, true) } // 无符号大整型 func (this *Blueprint) UnsignedBigInteger(column string, params ...bool) *ColumnDefinition { autoIncrement := false if len(params) > 0 { autoIncrement = params[0] } return this.BigInteger(column, autoIncrement, true) } // 精确小数 func (this *Blueprint) Decimal(column string, total, places int) *ColumnDefinition { if total == 0 { total = 8 } if places == 0 { places = 2 } return this.addColumn("decimal", column, &ColumnOptions{Total: total, Places: places}) } // 无符号精确销售 func (this *Blueprint) UnsignedDecimal(column string, total, places int) *ColumnDefinition { if total == 0 { total = 8 } if places == 0 { places = 2 } return this.addColumn("decimal", column, &ColumnOptions{Total: total, Places: places, unsigned: true}) } // 布尔值 func (this *Blueprint) Boolean(column string) *ColumnDefinition { return this.addColumn("boolean", column, nil) } // 枚举类型 func (this *Blueprint) Enum(column string, allowed []string) *ColumnDefinition { return this.addColumn("enum", column, &ColumnOptions{Allowed: allowed}) } // JSON func (this *Blueprint) Json(column string) *ColumnDefinition { return this.addColumn("json", column, nil) } // 日期类型 func (this *Blueprint) Date(column string) *ColumnDefinition { return this.addColumn("date", column, nil) } // 日期时间类型 func (this *Blueprint) DateTime(column string, precision ...int) *ColumnDefinition { if len(precision) > 0 { return this.addColumn("datetime", column, &ColumnOptions{Precision: precision[0]}) } return this.addColumn("datetime", column, nil) } // 时间类型 func (this *Blueprint) Time(column string, precision ...int) *ColumnDefinition { if len(precision) > 0 { return this.addColumn("time", column, &ColumnOptions{Precision: precision[0]}) } return this.addColumn("time", column, nil) } // 时间戳 func (this *Blueprint) Timestamp(column string, precision ...int) *ColumnDefinition { if len(precision) > 0 { return this.addColumn("timestamp", column, &ColumnOptions{Precision: precision[0]}) } return this.addColumn("timestamp", column, nil) } // 年 func (this *Blueprint) Year(column string) *ColumnDefinition { return this.addColumn("year", column, nil) } // 二进制数据 func (this *Blueprint) Binary(column string) *ColumnDefinition { return this.addColumn("binary", column, nil) } // UUID func (this *Blueprint) Uuid(column string) *ColumnDefinition { return this.addColumn("uuid", column, nil) } // 自增字段 func (this *Blueprint) Increments(column string) *ColumnDefinition { return this.UnsignedInteger(column, true) } // 自增Big字段 func (this *Blueprint) BigIncrements(column string) *ColumnDefinition { return this.UnsignedBigInteger(column, true) } // 添加主键 func (this *Blueprint) Primary(columns ...string) *Command { return this.addCommand("primary", CommandOptions{Index: this.generateIndexName("pk", columns), Columns: columns}) } // 唯一键 func (this *Blueprint) Unique(columns ...string) *Command { return this.addCommand("unique", CommandOptions{Index: this.generateIndexName("unique", columns), Columns: columns}) } // 普通索引 func (this *Blueprint) Index(columns ...string) *Command { return this.addCommand("index", CommandOptions{Index: this.generateIndexName("index", columns), Columns: columns}) } // 空间索引 func (this *Blueprint) SpatialIndex(columns ...string) *Command { return this.addCommand("spatialIndex", CommandOptions{Index: this.generateIndexName("spatial_index", columns), Columns: columns}) } // 删除列 func (this *Blueprint) DropColumn(columns ...string) *Command { return this.addCommand("dropColumn", CommandOptions{Columns: columns}) } // 创建表 func (this *Blueprint) Create() *Command { return this.addCommand("create", CommandOptions{}) } // 设置临时表标记 func (this *Blueprint) Temporary() { this.temporary = true } // 设置表字符集 func (this *Blueprint) Charset(charset string) { this.charset = charset } // 修改表名 func (this *Blueprint) Rename(to string) *Command { return this.addCommand("rename", CommandOptions{To: to}) } // 删除表 func (this *Blueprint) Drop() *Command { return this.addCommand("drop", CommandOptions{}) } // 删除表, 先判断再删除 func (this *Blueprint) DropIfExists() *Command { return this.addCommand("dropIfExists", CommandOptions{}) } // 执行SQL查询 func (this *Blueprint) Build(conn *db.Connection, grammar IGrammar) error { _, err := conn.Transaction(func(tx *db.Transaction) (result any, err error) { for _, sql := range this.ToSql(conn, grammar) { if _, err := tx.Statement(sql, []any{}); err != nil { tx.Rollback() return nil, err } } tx.Commit() return }) return err } func (this *Blueprint) ToSql(conn *db.Connection, grammar IGrammar) (statements []string) { this.addImpliedCommands(grammar) for _, cmd := range this.commands { switch cmd.Type { case "create": statements = append(statements, grammar.CompileCreate(this, cmd, conn)) case "add": statements = append(statements, grammar.CompileAdd(this, cmd, conn)...) case "change": statements = append(statements, grammar.CompileChange(this, cmd, conn)...) case "primary": // statements = append(statements, grammar.CompilePrimary(this, cmd, conn)) case "unique": statements = append(statements, grammar.CompileUnique(this, cmd, conn)) case "index": statements = append(statements, grammar.CompileIndex(this, cmd, conn)) case "spatialIndex": statements = append(statements, grammar.CompileSpatialIndex(this, cmd, conn)) case "drop": statements = append(statements, grammar.CompileDrop(this, cmd, conn)) case "dropIfExists": statements = append(statements, grammar.CompileDropIfExists(this, cmd, conn)) case "dropColumn": statements = append(statements, grammar.CompileDropColumn(this, cmd, conn)...) case "dropPrimary": // statements = append(statements, grammar.CompileDropPrimary(this, cmd, conn)) case "dropUnique": statements = append(statements, grammar.CompileDropUnique(this, cmd, conn)) case "dropIndex": statements = append(statements, grammar.CompileDropIndex(this, cmd, conn)) case "dropSpatialIndex": statements = append(statements, grammar.CompileDropSpatialIndex(this, cmd, conn)) case "rename": statements = append(statements, grammar.CompileRename(this, cmd, conn)) case "renameIndex": statements = append(statements, grammar.CompileRenameIndex(this, cmd, conn)) case "dropAllTables": case "dropAllViews": case "getAllTables": case "getAllViews": } } return statements } // 判断是否是创建表 func (this *Blueprint) creating() bool { return lo.SomeBy(this.commands, func(item *Command) bool { if item.Type == "create" { return true } return false }) } func (this *Blueprint) addColumn(typ, name string, options *ColumnOptions) (definition *ColumnDefinition) { definition = &ColumnDefinition{Type: typ, Name: name} if options != nil { if options.Length > 0 { definition.Length = options.Length } if options.autoIncrement { definition.autoIncrement = true } if options.unsigned { definition.unsigned = true } if options.Total > 0 { definition.Total = options.Total } if options.Places > 0 { definition.Places = options.Places } if len(options.Allowed) > 0 { definition.Allowed = options.Allowed } if options.Precision > 0 { definition.Precision = options.Precision } if options.change { definition.change = true } } this.columns = append(this.columns, definition) return definition } func (this *Blueprint) addImpliedCommands(grammar IGrammar) { if !this.creating() { if len(this.getAddedColumns()) > 0 { this.commands = append([]*Command{this.createCommand("add", CommandOptions{})}, this.commands...) } if len(this.getChangedColumns()) > 0 { this.commands = append([]*Command{this.createCommand("change", CommandOptions{})}, this.commands...) } } this.addFluentIndexes() } // 添加索引字段 func (this *Blueprint) addFluentIndexes() { for _, column := range this.columns { if column.primary { this.Primary(column.Name) continue } else if column.unique { this.Unique(column.Name) continue } else if column.index { this.Index(column.Name) continue } else if column.spatialIndex { this.SpatialIndex(column.Name) continue } } } func (this *Blueprint) addCommand(name string, options CommandOptions) (command *Command) { command = this.createCommand(name, options) this.commands = append(this.commands, command) return command } func (this *Blueprint) createCommand(name string, options CommandOptions) *Command { return &Command{Type: name, CommandOptions: options} } // 生成索引名称 func (this *Blueprint) generateIndexName(typ string, columns []string) string { return strings.ToLower(typ + "_" + strings.Join(columns, "_")) } func (this *Blueprint) getAddedColumns() []*ColumnDefinition { return lo.Filter(this.columns, func(item *ColumnDefinition, _ int) bool { return !item.change }) } func (this *Blueprint) getChangedColumns() []*ColumnDefinition { return lo.Filter(this.columns, func(item *ColumnDefinition, _ int) bool { return item.change }) }