eorm
eorm copied to clipboard
eorm: Migrate 功能的支持
åºæ¯åæ
è½ç¶å¨ä¸äºå¤§åä¸ï¼å¾å¾ä¿®æ¹ä¸ä¸ªä¸å¡è¡¨çå段é½è¦èµ°å¤æçæµç¨ï¼ä½æ¯å¨ç»å¤§å¤æ°ä¸å°å ¬å¸æ¯æ²¡æå¦æ¤å¤æçæµç¨äºï¼æ以为äºå¼åè è½ç»å¼å¤æç建表è¯å¥ä¸ä¿®æ¹è¡¨çè¯å¥ï¼æ¹ä¾¿ç´æ¥æ建 object å æ°æ®åº table çæ å°ï¼å¤§å¤æ° orm æ¡æ¶ æä¾ç»äºä½¿ç¨è Migrate çåè½ã æ°æ®åº Migrate ä¸ç´æ¯æ°æ®åºè¿ç»´äººåæ为头ççé®é¢ï¼å¦æä» ä» æ¯ä¸å¼ 表å¢å å段è¿æ¯è¾å®¹æï¼é£å¦ææ¶åå°å¤é®çå¤æçå ³èå ³ç³»ï¼æ°æ®åºç Migrate å°±ä¼åå¾é常å°é¾ã
éæ±åæ
å®ç°ä¸ä¸ª Migrator é常æ¶å以ä¸æ¥éª¤ï¼
- ç¡®å®è¿ç§»å·¥å ·çåè½åéæ±ï¼é¦å ï¼éè¦æç¡®è¿ç§»å·¥å ·çç®æ ååè½ãè¿å æ¬ç¡®å®æ¯æçæ°æ®åºç±»åãéè¦æ§è¡çè¿ç§»æä½ï¼å建表ãæ·»å åãä¿®æ¹åãå é¤åçï¼ä»¥åè¿ç§»æä½ç顺åºåä¾èµå ³ç³»ã
- è¿æ¥å°ç®æ æ°æ®åºï¼ä½¿ç¨æ°æ®åºé©±å¨ç¨åºï¼å»ºç«ä¸ç®æ æ°æ®åºçè¿æ¥ãæ ¹æ®éæ©çå·¥å ·ï¼æ¨å¯è½éè¦æä¾æ°æ®åºçè¿æ¥å符串ã认è¯ä¿¡æ¯åå ¶ä»é ç½®åæ°ã
- å®ç°è¿ç§»æä½ï¼æ ¹æ®æ°æ®åºè¿ç§»çéæ±ï¼ç¼åç¸åºçè¿ç§»æä½ãè¿æ¶å使ç¨é¢åç¹å®è¯è¨ï¼DSLï¼æAPIæ¥æè¿°è¿ç§»æä½ãä¾å¦ï¼å建表å¯ä»¥éè¿å®ä¹è¡¨çç»æå约ææ¥è¿è¡ï¼æ·»å åå¯ä»¥æå®ååãæ°æ®ç±»åå约æçã
- 管çè¿ç§»çæ¬ï¼è¿½è¸ªå管çæ°æ®åºè¿ç§»ççæ¬æ¯éè¦çãæ¨å¯ä»¥ä½¿ç¨çæ¬å·ãæ¶é´æ³æå ¶ä»æ è¯ç¬¦æ¥æ è®°æ¯ä¸ªè¿ç§»æä½ãå¨æ§è¡è¿ç§»æ¶ï¼è¿ç§»å·¥å ·å¯ä»¥æ£æ¥å½åæ°æ®åºççæ¬ï¼å¹¶æ ¹æ®æéççæ¬æ§è¡ç¸åºçè¿ç§»æä½ãè¿å¯ä»¥è®°å½å·²ç»æ§è¡çè¿ç§»æä½ï¼ä»¥ä¾¿å¨åæ»æéæ°æ§è¡è¿ç§»æ¶ä½¿ç¨ã
- æä¾å½ä»¤è¡æ¥å£æAPIï¼ä¸ºè¿ç§»å·¥å ·æä¾æ¹ä¾¿ççé¢ï¼ä½¿ç¨æ·å¯ä»¥æ§è¡è¿ç§»æä½ãè¿å¯ä»¥æ¯å½ä»¤è¡å·¥å ·ï¼æ¥ååæ°åé项æ¥æå®è¦æ§è¡çè¿ç§»æä½ï¼å¹¶æ¾ç¤ºç»æåé误信æ¯ãä¹å¯ä»¥æ¯æä¾APIï¼å 许åºç¨ç¨åºå¨ä»£ç ä¸è°ç¨è¿ç§»æä½ã
åè½éæ±
- å建表
- å¦æç¨æ·æ²¡æå®ä¹ä¸»é®æä¹åï¼
- è¦ä¸è¦ææä¾ API å®ä¹è¡¨çå±æ§ï¼
- å é¤è¡¨
- æ°å¢å段
- å é¤å段
- ä¿®æ¹å段
- ä¿®æ¹å段类å
- 为å段添å /å é¤ç´¢å¼
- ä¿®æ¹åæ®µå ¶ä»å±æ§ (æ¯å¦ä¸ºç©ºãæ¯å¦èªå¢ãæ¯å¦ä¸ºé»è®¤å¼)
- æ¯å¦èèä¸åç sql æ¹è¨
å¼æºå®ä¾
GORM
GORM æä¾äº Migrator æ¥å£ï¼è¯¥æ¥å£ä¸ºæ¯ä¸ªæ°æ®åºæä¾äºç»ä¸ç API æ¥å£ï¼å¯ç¨æ¥ä¸ºæ¨çæ°æ®åºæ建ç¬ç«è¿ç§»ï¼ ä¾å¦ï¼ SQLite ä¸æ¯æ ALTER COLUMNãDROP COLUMNï¼å½ä½ è¯å¾ä¿®æ¹è¡¨ç»æï¼GORM å°å建ä¸ä¸ªæ°è¡¨ãå¤å¶æææ°æ®ãå é¤æ§è¡¨ãéå½åæ°è¡¨ã ä¸äºçæ¬ç MySQL ä¸æ¯æ rename åï¼ç´¢å¼ãGORM å°åºäºæ¨ä½¿ç¨ MySQL ççæ¬æ§è¡ä¸å SQLã
type Migrator interface {
// AutoMigrate
AutoMigrate(dst ...interface{}) error
// Database
CurrentDatabase() string
FullDataTypeOf(*schema.Field) clause.Expr
// Tables
CreateTable(dst ...interface{}) error
DropTable(dst ...interface{}) error
HasTable(dst interface{}) bool
RenameTable(oldName, newName interface{}) error
GetTables() (tableList []string, err error)
// Columns
AddColumn(dst interface{}, field string) error
DropColumn(dst interface{}, field string) error
AlterColumn(dst interface{}, field string) error
MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
HasColumn(dst interface{}, field string) bool
RenameColumn(dst interface{}, oldName, field string) error
ColumnTypes(dst interface{}) ([]ColumnType, error)
// Views
CreateView(name string, option ViewOption) error
DropView(name string) error
// Constraints
CreateConstraint(dst interface{}, name string) error
DropConstraint(dst interface{}, name string) error
HasConstraint(dst interface{}, name string) bool
// Indexes
CreateIndex(dst interface{}, name string) error
DropIndex(dst interface{}, name string) error
HasIndex(dst interface{}, name string) bool
RenameIndex(dst interface{}, oldName, newName string) error
}
使ç¨ä¾å æ§è¡è¿ç§»æä½ï¼
type User struct {
ID uint `gorm:"primary_key"`
ProfileID uint `gorm:"unique"`
Profile Profile
Posts []Post
Roles []Role `gorm:"many2many:user_roles;"`
}
type Profile struct {
ID uint `gorm:"primary_key"`
UserID uint
}
type Post struct {
ID uint `gorm:"primary_key"`
UserID uint
}
type Role struct {
ID uint `gorm:"primary_key"`
Name string
}
func main() {
db, err := gorm.Open(mysql.Open("root:123456@tcp(localhost:3306)/ebook"))
if err != nil {
// æåªä¼å¨åå§åè¿ç¨ä¸ panic
// panic ç¸å½äºæ´ä¸ª goroutine ç»æ
// ä¸æ¦åå§åè¿ç¨åºéï¼åºç¨å°±ä¸è¦å¯å¨äº
panic(err)
}
err = InitTables(db)
if err != nil {
panic(err)
}
}
func InitTables(db *gorm.DB) error {
return db.AutoMigrate(&User{}, &Profile{}, &Post{}, &Role{})
}
åæ»è¿ç§»æä½ï¼
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"migrations" // å¼å
¥èªå®ä¹çè¿ç§»æ件å
_ "github.com/go-sql-driver/mysql"
)
func main() {
// è¿æ¥æ°æ®åº
dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
panic(err)
}
// åæ»è¿ç§»æä½
err = db.Migrator().Rollback()
if err != nil {
panic(err)
}
// å
³éæ°æ®åºè¿æ¥
db.Close()
}
è¿äºç¤ºä¾å±ç¤ºäºä½¿ç¨Gormè¿è¡æ°æ®åºè¿ç§»çä¸è¬æ¨¡å¼ãå¯ä»¥æ ¹æ®èªå·±çéæ±å®ä¹æ´å¤çè¿ç§»æ件åæä½ãè®°å¾å¨æ§è¡è¿ç§»æä½ä¹åï¼éè¦ç¡®ä¿æ£ç¡®é ç½®æ°æ®åºè¿æ¥ï¼å¼å ¥Gormåç¸å ³æ°æ®åºé©±å¨ï¼å¹¶æç §ç¤ºä¾ä¸çæ¹å¼è°ç¨ç¸åºçè¿ç§»å½æ°åæ¹æ³ã
çæ¬æ§å¶
éè¦æ³¨æçæ¯ï¼GORM è½ç¶æä¾äºä¸éçæ°æ®åºè¿ç§»åè½ï¼ä½æ¯è·ç¦»çæ³ç âçæ¬æ§å¶â ä»æè·ç¦»ãä¸æ¯æï¼çæ¬è®°å½ãçæ¬åéãçæ¬éæ©ãè¿äºé½éè¦å¼åè èªè¡å°è£ ã
Beego ORM
Beego ç migrate çæ¥å£è®¾è®¡ågormå·®ä¸å¤ï¼ä½æ ¸å¿é»è¾ä¸»è¦æ¯åºäºå½ä»¤è¡å·¥å ·å代ç çæå®ç°çã
// CREATE TABLE `migrations` (
// `id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
// `name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
// `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back',
// `statements` longtext COMMENT 'SQL statements for this migration',
// `rollback_statements` longtext,
// `status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back',
// PRIMARY KEY (`id_migration`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
// Migrationer is an interface for all Migration struct
type Migrationer interface {
Up()
Down()
Reset()
Exec(name, status string) error
GetCreated() int64
}
// Migration defines the migrations by either SQL or DDL
type Migration struct {
sqls []string
Created string
TableName string
Engine string
Charset string
ModifyType string
Columns []*Column
Indexes []*Index
Primary []*Column
Uniques []*Unique
Foreigns []*Foreign
Renames []*RenameColumn
RemoveColumns []*Column
RemoveIndexes []*Index
RemoveUniques []*Unique
RemoveForeigns []*Foreign
}
var migrationMap map[string]Migrationer
func init() {
migrationMap = make(map[string]Migrationer)
}
çæ¬æ§å¶
Beego ORMï¼çæ¬æ§å¶æ¯åºäº Migration ç»æä½æ¨¡å表ï¼è¯¥è¡¨å¨åå§åæ¶ä¼å建å¨æ°æ®åºä¸ï¼ç¨æ¥è®°å½æ°æ®åºè¿ç§»ççæ¬çåå ï¼å æ¬å建ãæ´æ°ãæ»çæä½ã 使ç¨æ ·ä¾ çææ°æ®åºè¿ç§»æ件
bee generate migration User
æ°æ®åºè¿ç§»
// Run the migrations
func (m *User_20210806_105600) Up() {
// use m.SQL("CREATE TABLE ...") to make schema update
m.CreateTable("user", "innodb", "utf8mb4")
m.PriCol("id").SetAuto(true).SetDataType("int").SetUnsigned(true)
m.NewCol("username").SetDataType("varchar(255)")
m.NewCol("password").SetDataType("varchar(255)")
m.NewCol("email").SetDataType("varchar(255)").SetNullable(true)
m.NewCol("login_count").SetDataType("int").SetUnsigned(true)
m.NewCol("last_time").SetDataType("datetime")
m.NewCol("last_ip").SetDataType("varchar(255)").SetNullable(true)
m.NewCol("state").SetDataType("smallint(2)")
m.NewCol("created_at").SetDataType("datetime")
m.NewCol("updated_at").SetDataType("datetime")
m.SQL(m.GetSQL())
}
// Reverse the migrations
func (m *User_20210806_105600) Down() {
// use m.SQL("DROP TABLE ...") to reverse schema update
m.SQL("DROP TABLE IF EXISTS user")
}
bee migrate -driver=mysql -conn=root:123456@tcp(127.0.0.1:3306)/beego-admin
golang-migrate
**golang-migrate **æ¯æé常å¤çæ°æ®åºç±»åï¼å æ¬: cockroachdb, firebird, postgresql, redshift, clickhouse, postgres, cockroach, firebirdsql, mysql, crdb-postgres, mongodb, mongodb+srv, neo4j, pgx, spanner, sqlserver, stub, cassandra
type Migrate struct {
sourceName string
sourceDrv source.Driver
databaseName string
databaseDrv database.Driver
// Log accepts a Logger interface
Log Logger
// GracefulStop accepts `true` and will stop executing migrations
// as soon as possible at a safe break point, so that the database
// is not corrupted.
GracefulStop chan bool
isLockedMu *sync.Mutex
isGracefulStop bool
isLocked bool
// PrefetchMigrations defaults to DefaultPrefetchMigrations,
// but can be set per Migrate instance.
PrefetchMigrations uint
// LockTimeout defaults to DefaultLockTimeout,
// but can be set per Migrate instance.
LockTimeout time.Duration
}
// Migration holds information about a migration.
// It is initially created from data coming from the source and then
// used when run against the database.
type Migration struct {
// Identifier can be any string to help identifying
// the migration in the source.
Identifier string
// Version is the version of this migration.
Version uint
// TargetVersion is the migration version after this migration
// has been applied to the database.
// Can be -1, implying that this is a NilVersion.
TargetVersion int
// Body holds an io.ReadCloser to the source.
Body io.ReadCloser
// BufferedBody holds an buffered io.Reader to the underlying Body.
BufferedBody io.Reader
// BufferSize defaults to DefaultBufferSize
BufferSize uint
// bufferWriter holds an io.WriteCloser and pipes to BufferBody.
// It's an *Closer for flow control.
bufferWriter io.WriteCloser
// Scheduled is the time when the migration was scheduled/ queued.
Scheduled time.Time
// StartedBuffering is the time when buffering of the migration source started.
StartedBuffering time.Time
// FinishedBuffering is the time when buffering of the migration source finished.
FinishedBuffering time.Time
// FinishedReading is the time when the migration source is fully read.
FinishedReading time.Time
// BytesRead holds the number of Bytes read from the migration source.
BytesRead int64
}
è¿æ¯ä¸ä¸ªç®åçå·¥å ·ï¼å¯åºäºæ件è¿è¡è¿ç§»ãå®å¸¦æ Go åºå CLI å·¥å ·ï¼å¯å建 SQL è¿ç§»æ件并管çæ¶æçæ¬ã
设计
æ¹æ¡ä¸ï¼ æä¾ä¸ä¸ª Migrate APIï¼æ¯ææ ¹æ® Go Struct ç»æèªå¨çæ对åºç表ç»æ ã æ¹æ¡äºï¼ éè¿ CLI å·¥å · è¾ å©çæ sql è¿ç§»æ件ï¼ç¨æ·éè¦æå¨å°è¿ç§»sql æ·»å è³è¿ç§»æ件ä¸ãæ¯æçæ¬ç®¡çï¼ ç±»ä¼¼äº golang-migrate ã æ¬æ¡æ¶å éæ©æ¹æ¡ä¸(gormçæ¡æ¶ççæ¹å¼)ï¼éè¿æ³¨åç»æä½æ¥å®ç°å¯¹æ°æ®åºç模åæ å°ã è¿ä¸ªåè½çå®ç°è¦åæ两个é¨åï¼
- ä¸ä¸ªæ¯çº¯ç²¹ç DDL è¯å¥çæï¼ä¹å°±æ¯å¯¹åºå°æ们çåç§ sql Builderã
- 第äºä¸ªæ¯å©ç¨ç¬¬ä¸ä¸ªé¨åï¼æ¥çæ对åºç DDLã
DDL è¯å¥çæ
DDLï¼Data Definition Languageï¼ï¼æ°æ®å®ä¹è¯è¨ï¼å®ä¹è¯è¨å°±æ¯å®ä¹å ³ç³»æ¨¡å¼ãå é¤å ³ç³»ãä¿®æ¹å ³ç³»æ¨¡å¼ä»¥åå建æ°æ®åºä¸çåç§å¯¹è±¡ï¼æ¯å¦è¡¨ãèç°ãç´¢å¼ãè§å¾ãå½æ°ãåå¨è¿ç¨å触åå¨ççã æ°æ®å®ä¹è¯è¨æ¯ç±SQLè¯è¨éä¸è´è´£æ°æ®ç»æå®ä¹ä¸æ°æ®åºå¯¹è±¡å®ä¹çè¯è¨ï¼å¹¶ä¸ç±CREATEãALTERãDROPåTRUNCATEå个è¯æ³ç»æãæ¯å¦ï¼
--å建ä¸ä¸ªstudent表
create table student(
id int identity(1,1) not null,
name varchar(20) null,
course varchar(20) null,
grade numeric null
)
--student表å¢å ä¸ä¸ªå¹´é¾å段
alter table student add age int NULL
--student表å é¤å¹´é¾å段ï¼å é¤çå段åé¢éè¦å columnï¼ä¸ç¶ä¼æ¥éï¼èæ·»å å段ä¸éè¦å column
alter table student drop Column age
--å é¤student表
drop table student --å é¤è¡¨çæ°æ®å表çç»æ
truncate table student -- åªæ¯æ¸
空表çæ°æ®ï¼ï¼ä½å¹¶ä¸å é¤è¡¨çç»æï¼student表è¿å¨åªæ¯æ°æ®ä¸ºç©º
Creater
MySQL
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
(create_definition,...)
[table_options]
[partition_options]
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
[(create_definition,...)]
[table_options]
[partition_options]
[IGNORE | REPLACE]
[AS] query_expression
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name
{ LIKE old_tbl_name | (LIKE old_tbl_name) }
create_definition: {
col_name column_definition
| {INDEX | KEY} [index_name] [index_type] (key_part,...)
[index_option] ...
| {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
[index_option] ...
| [CONSTRAINT [symbol]] PRIMARY KEY
[index_type] (key_part,...)
[index_option] ...
| [CONSTRAINT [symbol]] UNIQUE [INDEX | KEY]
[index_name] [index_type] (key_part,...)
[index_option] ...
| [CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (col_name,...)
reference_definition
| check_constraint_definition
}
column_definition: {
data_type [NOT NULL | NULL] [DEFAULT {literal | (expr)} ]
[VISIBLE | INVISIBLE]
[AUTO_INCREMENT] [UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']
[COLLATE collation_name]
[COLUMN_FORMAT {FIXED | DYNAMIC | DEFAULT}]
[ENGINE_ATTRIBUTE [=] 'string']
[SECONDARY_ENGINE_ATTRIBUTE [=] 'string']
[STORAGE {DISK | MEMORY}]
[reference_definition]
[check_constraint_definition]
| data_type
[COLLATE collation_name]
[GENERATED ALWAYS] AS (expr)
[VIRTUAL | STORED] [NOT NULL | NULL]
[VISIBLE | INVISIBLE]
[UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']
[reference_definition]
[check_constraint_definition]
}
data_type:
(see Chapter 11, Data Types)
key_part: {col_name [(length)] | (expr)} [ASC | DESC]
index_type:
USING {BTREE | HASH}
index_option: {
KEY_BLOCK_SIZE [=] value
| index_type
| WITH PARSER parser_name
| COMMENT 'string'
| {VISIBLE | INVISIBLE}
|ENGINE_ATTRIBUTE [=] 'string'
|SECONDARY_ENGINE_ATTRIBUTE [=] 'string'
}
check_constraint_definition:
[CONSTRAINT [symbol]] CHECK (expr) [[NOT] ENFORCED]
reference_definition:
REFERENCES tbl_name (key_part,...)
[MATCH FULL | MATCH PARTIAL | MATCH SIMPLE]
[ON DELETE reference_option]
[ON UPDATE reference_option]
reference_option:
RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
table_options:
table_option [[,] table_option] ...
table_option: {
AUTOEXTEND_SIZE [=] value
| AUTO_INCREMENT [=] value
| AVG_ROW_LENGTH [=] value
| [DEFAULT] CHARACTER SET [=] charset_name
| CHECKSUM [=] {0 | 1}
| [DEFAULT] COLLATE [=] collation_name
| COMMENT [=] 'string'
| COMPRESSION [=] {'ZLIB' | 'LZ4' | 'NONE'}
| CONNECTION [=] 'connect_string'
| {DATA | INDEX} DIRECTORY [=] 'absolute path to directory'
| DELAY_KEY_WRITE [=] {0 | 1}
| ENCRYPTION [=] {'Y' | 'N'}
| ENGINE [=] engine_name
| ENGINE_ATTRIBUTE [=] 'string'
| INSERT_METHOD [=] { NO | FIRST | LAST }
| KEY_BLOCK_SIZE [=] value
| MAX_ROWS [=] value
| MIN_ROWS [=] value
| PACK_KEYS [=] {0 | 1 | DEFAULT}
| PASSWORD [=] 'string'
| ROW_FORMAT [=] {DEFAULT | DYNAMIC | FIXED | COMPRESSED | REDUNDANT | COMPACT}
| START TRANSACTION
| SECONDARY_ENGINE_ATTRIBUTE [=] 'string'
| STATS_AUTO_RECALC [=] {DEFAULT | 0 | 1}
| STATS_PERSISTENT [=] {DEFAULT | 0 | 1}
| STATS_SAMPLE_PAGES [=] value
| tablespace_option
| UNION [=] (tbl_name[,tbl_name]...)
}
partition_options:
PARTITION BY
{ [LINEAR] HASH(expr)
| [LINEAR] KEY [ALGORITHM={1 | 2}] (column_list)
| RANGE{(expr) | COLUMNS(column_list)}
| LIST{(expr) | COLUMNS(column_list)} }
[PARTITIONS num]
[SUBPARTITION BY
{ [LINEAR] HASH(expr)
| [LINEAR] KEY [ALGORITHM={1 | 2}] (column_list) }
[SUBPARTITIONS num]
]
[(partition_definition [, partition_definition] ...)]
partition_definition:
PARTITION partition_name
[VALUES
{LESS THAN {(expr | value_list) | MAXVALUE}
|
IN (value_list)}]
[[STORAGE] ENGINE [=] engine_name]
[COMMENT [=] 'string' ]
[DATA DIRECTORY [=] 'data_dir']
[INDEX DIRECTORY [=] 'index_dir']
[MAX_ROWS [=] max_number_of_rows]
[MIN_ROWS [=] min_number_of_rows]
[TABLESPACE [=] tablespace_name]
[(subpartition_definition [, subpartition_definition] ...)]
subpartition_definition:
SUBPARTITION logical_name
[[STORAGE] ENGINE [=] engine_name]
[COMMENT [=] 'string' ]
[DATA DIRECTORY [=] 'data_dir']
[INDEX DIRECTORY [=] 'index_dir']
[MAX_ROWS [=] max_number_of_rows]
[MIN_ROWS [=] min_number_of_rows]
[TABLESPACE [=] tablespace_name]
tablespace_option:
TABLESPACE tablespace_name [STORAGE DISK]
| [TABLESPACE tablespace_name] STORAGE MEMORY
query_expression:
SELECT ... (Some valid select or union statement)
å çç®ååçè¯æ³è§£æ
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] table_name
[(
COLUMN_DEFINITION,
...
)]
[TABLE_OPTIONS]
[SELECT_STATEMENT]
- TEMPORARY
ç¨äºå建临æ¶è¡¨ï¼CREATE TEMPORARY TABLE table_name;è¿ä¸ªä¸´æ¶è¡¨å¨å½åä¼è¯ç»ææè è¿æ¥æå¼åå°èªå¨æ¶å¤±ã
- IF NOT EXISTS
å®é ä¸å°±æ¯å¨å»ºè¡¨åå ä¸ä¸ä¸ªå¤æï¼åªæ该表ç®åå°ä¸åå¨æ¶ææ§è¡CREATE TABLEæä½ãç¨æ¤é项å¯ä»¥é¿å åºç°è¡¨å·²ç»åå¨æ æ³åæ°å»ºçé误ã
- table_name
éè¦å建ç表åã该表åå¿ é¡»ç¬¦åæ è¯ç¬¦è§åï¼é常çåæ³æ¯å¨è¡¨åä¸ä» 使ç¨åæ¯ãæ°ååä¸å线ã
- COLUMN_DEFINITION
æå建ç表ä¸ååçç¸å ³å±æ§å®ä¹ã è¯æ³å¦ä¸ï¼
column_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
[PRIMARY KEY]
| PRIMARY KEY (index_col_name,...)
| KEY [index_name] (index_col_name,...)
| INDEX [index_name] (index_col_name,...)
| UNIQUE [INDEX] [index_name] (index_col_name,...)
| [CONSTRAINT symbol] FOREIGN KEY index_name (index_col_name,...)
[COMMENT reference_definition]
or CHECK (expr)
å¦ä¸æ示ï¼åçç¸å ³å±æ§å®ä¹è¯æ³å 容ç¸å½ä¸°å¯ã 1ï¼column_nameï¼ è¡¨ä¸åçååãå¿ é¡»ç¬¦åæ è¯ç¬¦è§åï¼èä¸å¨è¡¨ä¸è¦å¯ä¸ã 2ï¼typeï¼ åçæ°æ®ç±»åãæçæ°æ®ç±»åéè¦ææé¿åº¦nï¼å¹¶ç¨æ¬å·æ¬èµ·ã 3ï¼NOT NULL | NULLï¼ æå®è¯¥åæ¯å¦å 许为空ãå¦ææ¢ä¸æå®NULLä¹ä¸æå®NOT NULLï¼å被认为æå®äºNULLã 4ï¼DEFAULT default_valueï¼ ä¸ºåæå®é»è®¤å¼ãå¦æ没æ为åæå®é»è®¤å¼ï¼MySQLèªå¨å°åé ä¸ä¸ªã å¦æåå¯ä»¥åNULLä½ä¸ºå¼ï¼ç¼ºçå¼æ¯NULLã å¦æå被声æ为NOT NULLï¼ç¼ºçå¼åå³äºåç±»åï¼
- 对äºæ²¡æ声æAUTO_INCREMENTå±æ§çæ°åç±»åï¼ç¼ºçå¼æ¯0ã对äºä¸ä¸ªAUTO_INCREMENTåï¼ç¼ºçå¼æ¯å¨é¡ºåºä¸çä¸ä¸ä¸ªå¼ã
- 对äºé¤TIMESTAMPçæ¥æåæ¶é´ç±»åï¼ç¼ºçå¼æ¯è¯¥ç±»åéå½çâé¶âå¼ã对äºè¡¨ä¸ç¬¬ä¸ä¸ªTIMESTAMPåï¼ç¼ºçå¼æ¯å½åçæ¥æåæ¶é´ã
- 对äºé¤ENUMçå符串类åï¼ç¼ºçæ¯ç©ºå符串ã对äºENUMï¼ç¼ºçå¼æ¯ç¬¬ä¸ä¸ªæ举å¼ã
5ï¼AUTO_INCREMENTï¼ è®¾ç½®è¯¥åæèªå¢å±æ§ï¼åªææ´ååæè½è®¾ç½®æ¤å±æ§ã å½ä½ æå ¥NULLå¼æ0å°ä¸ä¸ªAUTO_INCREMENTåä¸æ¶ï¼å被设置为value+1ï¼å¨è¿évalueæ¯æ¤å表ä¸è¯¥åçæ大å¼ãAUTO_INCREMENT顺åºä»1å¼å§ãæ¯ä¸ªè¡¨åªè½æä¸ä¸ªAUTO_INCREMENTåï¼å¹¶ä¸å®å¿ 须被索å¼ã
- TABLE_OPTIONS
设置表çä¸äºå±æ§å®ä¹ï¼ 1ï¼å¼æ设置 ä¾å¦ï¼ENGINE|TYPE = MYISAM 2ï¼å符é设置 ä¾å¦ï¼CHARACTER SET gbk 3ï¼æåºè§å设置 ä¾å¦ï¼COLLATE gbk_chinese_ci COLLATEæ¯ç¨æ¥åä»ä¹çï¼ æ¯ç¨æ¥æåºçè§åã对äºmysqlä¸é£äºå符类åçåï¼å¦VARCHARï¼CHARï¼TEXTç±»åçåï¼é½éè¦æä¸ä¸ªCOLLATEç±»åæ¥åç¥mysqlå¦ä½å¯¹è¯¥åè¿è¡æåºåæ¯è¾ãç®èè¨ä¹ï¼COLLATEä¼å½±åå°ORDER BYè¯å¥ç顺åºï¼ä¼å½±åå°WHEREæ¡ä»¶ä¸å¤§äºå°äºå·çéåºæ¥çç»æï¼ä¼å½±åDISTINCTãGROUP BYãHAVINGè¯å¥çæ¥è¯¢ç»æãå¦å¤ï¼mysql建索å¼çæ¶åï¼å¦æç´¢å¼åæ¯å符类åï¼ä¹ä¼å½±åç´¢å¼å建ï¼åªä¸è¿è¿ç§å½±åæ们æç¥ä¸å°ãæ»ä¹ï¼å¡æ¯æ¶åå°å符类åæ¯è¾ææåºçå°æ¹ï¼é½ä¼åCOLLATEæå ³ã
- SELECT_STATEMENT
//å¦ä¸ç´æ¥éè¿selectæ¥è¯¢åºæå®æ°æ®åå ¥æ°å»ºç表ä¸
CREATE TABLE ...
SELECT ...
é常ï¼MySQLç create è¯å¥å¦ä¸ï¼
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`user_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`quantity` int(11) NOT NULL,
`code` varchar(50) NOT NULL UNIQUE,
`status` tinyint(1) NOT NULL DEFAULT 0,
`detail` text NOT NULL,
PRIMARY KEY (`id`),
INDEX `idx_name` (`name`),
FULLTEXT INDEX `idx_detail` (`detail`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`product_id`) REFERENCES `products`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`age` int(11) NOT NULL,
CHECK (`age` >= 18),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SQLite
âCREATE TABLEâå½ä»¤ç¨äºå¨ SQLite æ°æ®åºä¸å建ä¸ä¸ªæ°è¡¨ãCREATE TABLE å½ä»¤æå®æ°è¡¨ç以ä¸å±æ§ï¼
- æ°è¡¨çå称ã
- å¨å ¶ä¸å建æ°è¡¨çæ°æ®åºãå¯ä»¥å¨ä¸»æ°æ®åºã临æ¶æ°æ®åºæä»»ä½éå æ°æ®åºä¸å建表ã
- 表ä¸æ¯ä¸åçå称ã
- 表ä¸æ¯ä¸åç声æç±»åã
- 表ä¸æ¯ä¸åçé»è®¤å¼æ表达å¼ã
- ç¨äºæ¯åçé»è®¤æåºè§ååºåã
- ï¼å¯éï¼è¡¨ç PRIMARY KEYãæ¯æåååå¤åï¼å¤åï¼ä¸»é®ã
- æ¯ä¸ªè¡¨çä¸ç» SQL 约æãSQLite æ¯æ UNIQUEãNOT NULLãCHECK å FOREIGN KEY 约æã
- ï¼å¯éï¼çæçå约æã
- 该表æ¯å¦ä¸ºWITHOUT ROWID表ã
- 表æ¯å¦ç»è¿ä¸¥æ ¼çç±»åæ£æ¥ã
create table çåºæ¬è¯æ³å¦ä¸ï¼
CREATE TABLE database_name.table_name(
column1 datatype PRIMARY KEY(one or more columns),
column2 datatype,
column3 datatype,
.....
columnN datatype,
);
éç¨å®ä¾å¦ä¸ï¼
sqlite> CREATE TABLE COMPANY(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);
SQLite åæ ·ä¹å æ¬ç±»å约æï¼ç´¢å¼çº¦æãå¤é®çº¦æçè¯æ³ã约ææ¯å¨è¡¨çæ°æ®åä¸å¼ºå¶æ§è¡çè§åãè¿äºæ¯ç¨æ¥éå¶å¯ä»¥æå ¥å°è¡¨ä¸çæ°æ®ç±»åãè¿ç¡®ä¿äºæ°æ®åºä¸æ°æ®çåç¡®æ§åå¯é æ§ã 约æå¯ä»¥æ¯å级æ表级ãå级约æä» éç¨äºåï¼è¡¨çº§çº¦æ被åºç¨å°æ´ä¸ªè¡¨ã 以ä¸æ¯å¨ SQLite ä¸å¸¸ç¨ç约æã
- NOT NULL 约æï¼ç¡®ä¿æåä¸è½æ NULL å¼ã
- DEFAULT 约æï¼å½æå没ææå®å¼æ¶ï¼ä¸ºè¯¥åæä¾é»è®¤å¼ã
- UNIQUE 约æï¼ç¡®ä¿æåä¸çææå¼æ¯ä¸åçã
- PRIMARY Key 约æï¼å¯ä¸æ è¯æ°æ®åºè¡¨ä¸çåè¡/è®°å½ã
- CHECK 约æï¼CHECK 约æç¡®ä¿æåä¸çææå¼æ»¡è¶³ä¸å®æ¡ä»¶ã
- INDEX 约æï¼ éè¿å建è¯å¥æ¶çINDEXè¯æ³å建该åçç´¢å¼
NOT NULL 约æ é»è®¤æ åµä¸ï¼åå¯ä»¥ä¿å NULL å¼ãå¦ææ¨ä¸æ³æåæ NULL å¼ï¼é£ä¹éè¦å¨è¯¥åä¸å®ä¹æ¤çº¦æï¼æå®å¨è¯¥åä¸ä¸å 许 NULL å¼ãNULL ä¸æ²¡ææ°æ®æ¯ä¸ä¸æ ·çï¼å®ä»£è¡¨çæªç¥çæ°æ®ã
CREATE TABLE COMPANY(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);
DEFAULT 约æ DEFAULT 约æå¨ INSERT INTO è¯å¥æ²¡ææä¾ä¸ä¸ªç¹å®çå¼æ¶ï¼ä¸ºåæä¾ä¸ä¸ªé»è®¤å¼ã
CREATE TABLE COMPANY(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL DEFAULT 50000.00
);
UNIQUE 约æ UNIQUE 约æé²æ¢å¨ä¸ä¸ªç¹å®çååå¨ä¸¤ä¸ªè®°å½å ·æç¸åçå¼ãå¨ COMPANY 表ä¸ï¼ä¾å¦ï¼æ¨å¯è½è¦é²æ¢ä¸¤ä¸ªæ两个以ä¸çäººå ·æç¸åçå¹´é¾ã
CREATE TABLE COMPANY(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL UNIQUE,
ADDRESS CHAR(50),
SALARY REAL DEFAULT 50000.00
);
INDEX 约æ æ®éç´¢å¼ CREATE TABLE è¯å¥ä¸ï¼æ¨å¯ä»¥ä½¿ç¨ INDEX åå¥ä¸ºè¡¨çæ个åå建索å¼ã以ä¸æ¯ä¸ä¸ªç¤ºä¾ï¼æ¼ç¤ºäºå¦ä½å¨å建表æ¶ä¸ºæ个åå建索å¼ï¼
CREATE TABLE your_table_name (
column1 datatype,
column2 datatype,
column3 datatype,
...
INDEX index_name (column_name),
...
);
èåç´¢å¼ å½å¨ MySQL ä¸å建表æ¶ï¼æ¨å¯ä»¥ä½¿ç¨ CREATE TABLE è¯å¥ï¼å¹¶éè¿ INDEX åå¥å®ä¹èåç´¢å¼ã以ä¸æ¯ä¸ä¸ªæ´è¯¦ç»ç示ä¾ï¼å±ç¤ºäºå¦ä½å建ä¸ä¸ªè¡¨å¹¶æ·»å èåç´¢å¼ï¼
CREATE TABLE your_table_name (
column1 datatype,
column2 datatype,
column3 datatype,
...
INDEX index_name (column1, column2, column3),
...
);
å¨ä¸é¢ç示ä¾ä¸ï¼éè¦å° your_table_name æ¿æ¢ä¸ºè¡¨çå®é å称ãç¶åï¼æ ¹æ®éè¦å®ä¹è¡¨çåï¼å¹¶å° column1ãcolumn2ãcolumn3 æ¿æ¢ä¸ºå®é çååï¼ä»¥å datatype æ¿æ¢ä¸ºç¸åºçæ°æ®ç±»åã æ¥ä¸æ¥ï¼å¨ INDEX åå¥ä¸å®ä¹èåç´¢å¼ãæ¨å¯ä»¥ä¸ºèåç´¢å¼æå®ä¸ä¸ªå称ï¼å° index_name æ¿æ¢ä¸ºç´¢å¼çå®é å称ãç¶åï¼ååºè¦å å«å¨èåç´¢å¼ä¸çååï¼æç §æéç顺åºååºãå¨ç¤ºä¾ä¸ï¼æä»¬ä½¿ç¨ column1ãcolumn2 å column3 åä½ä¸ºèåç´¢å¼çç»æé¨åã éè¦æ³¨æçæ¯ï¼å¯ä»¥å¨ä¸ä¸ª CREATE TABLE è¯å¥ä¸å®ä¹å¤ä¸ªç´¢å¼ï¼å æ¬ååç´¢å¼åèåç´¢å¼ãæ¯ä¸ªç´¢å¼é½å¯ä»¥ä½¿ç¨ä¸åçåç»åãå¦æè¦å®ä¹å¤ä¸ªç´¢å¼ï¼è¯·ç¡®ä¿ä¸ºæ¯ä¸ªç´¢å¼æå®å¯ä¸çå称ã éè¿ä½¿ç¨éå½çååæ°æ®ç±»åï¼ä»¥åå®ä¹æéçèåç´¢å¼ï¼å¯ä»¥æ ¹æ®æ¨ç表ç»æåæ¥è¯¢éæ±æ¥åå»ºå ·æèåç´¢å¼ç表ã PRIMARY KEY 约æ PRIMARY KEY 约æå¯ä¸æ è¯æ°æ®åºè¡¨ä¸çæ¯ä¸ªè®°å½ãå¨ä¸ä¸ªè¡¨ä¸å¯ä»¥æå¤ä¸ª UNIQUE åï¼ä½åªè½æä¸ä¸ªä¸»é®ãå¨è®¾è®¡æ°æ®åºè¡¨æ¶ï¼ä¸»é®æ¯å¾éè¦çã主é®æ¯å¯ä¸ç IDã 使ç¨ä¸»é®æ¥å¼ç¨è¡¨ä¸çè¡ãå¯éè¿æ主é®è®¾ç½®ä¸ºå ¶ä»è¡¨çå¤é®ï¼æ¥å建表ä¹é´çå ³ç³»ãç±äº"é¿æåå¨ç¼ç çç£"ï¼å¨ SQLite ä¸ï¼ä¸»é®å¯ä»¥æ¯ NULLï¼è¿æ¯ä¸å ¶ä»æ°æ®åºä¸åçå°æ¹ã 主é®æ¯è¡¨ä¸çä¸ä¸ªå段ï¼å¯ä¸æ è¯æ°æ®åºè¡¨ä¸çåè¡/è®°å½ã主é®å¿ é¡»å å«å¯ä¸å¼ã主é®åä¸è½æ NULL å¼ã ä¸ä¸ªè¡¨åªè½æä¸ä¸ªä¸»é®ï¼å®å¯ä»¥ç±ä¸ä¸ªæå¤ä¸ªå段ç»æãå½å¤ä¸ªå段ä½ä¸ºä¸»é®ï¼å®ä»¬è¢«ç§°ä¸ºå¤åé®ã å¦æä¸ä¸ªè¡¨å¨ä»»ä½å段ä¸å®ä¹äºä¸ä¸ªä¸»é®ï¼é£ä¹å¨è¿äºå段ä¸ä¸è½æ两个记å½å ·æç¸åçå¼ã
CREATE TABLE COMPANY(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
);
CHECK 约æ CHECK 约æå¯ç¨è¾å ¥ä¸æ¡è®°å½è¦æ£æ¥å¼çæ¡ä»¶ãå¦ææ¡ä»¶å¼ä¸º falseï¼åè®°å½è¿åäºçº¦æï¼ä¸ä¸è½è¾å ¥å°è¡¨ã
CREATE TABLE COMPANY3(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL CHECK(SALARY > 0)
);
å é¤çº¦æ SQLite æ¯æ ALTER TABLE çæéåéãå¨ SQLite ä¸ï¼ALTER TABLE å½ä»¤å 许ç¨æ·éå½å表ï¼æåç°æ表添å ä¸ä¸ªæ°çåãéå½ååï¼å é¤ä¸åï¼æä»ä¸ä¸ªè¡¨ä¸æ·»å æå é¤çº¦æé½æ¯ä¸å¯è½çã å¤é®çº¦æ å¨ SQLite ä¸å¯ç¨å¤é®åè½éè¦è¿è¡ä¸äºè®¾ç½®ãé»è®¤æ åµä¸ï¼SQLite ä¸çå¤é®åè½æ¯ç¦ç¨çï¼æ们éè¦æå¨å¼å¯å®ãå¨ä½¿ç¨å¤é®ä¹åï¼æ们éè¦ç¡®è®¤ SQLite çæ¬æ¯å¦æ¯æå¤é®åè½ãæ§è¡ä»¥ä¸å½ä»¤å¯ä»¥è·åå½åçæ¬ç SQLite æ¯ææ åµï¼
sqlite> SELECT sqlite_version();
å¦æè¿åççæ¬å·ä¸å å«âforeign_keyâï¼å说æ SQLite æ¯æå¤é®åè½ã ä¸é¢æ¯ä¸ä¸ªç¤ºä¾ï¼æ¼ç¤ºäºå¦ä½å¨ SQLite ä¸å建表并添å å¤é®çº¦æï¼
-- å建产å表
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT,
price REAL
);
-- å建订å表并添å å¤é®çº¦æ
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
product_id INTEGER,
quantity INTEGER,
FOREIGN KEY (product_id) REFERENCES products(id)
);
设计åæ CREATE æä½éè¦èè表çå段å±æ§ï¼å段é¿åº¦ãæ¯å¦ä¸ºNullãæ¯å¦æé»è®¤å¼ãæ¯å¦æ¯ä¸»é®ãæ¯å¦èªå¢ãç¨ä»ä¹åå¨å¼æãæ¯å¦å¤é®çº¦æççã ç¨æ¡æ¶å·²æçåè½ï¼é£å°±æ¯éè¿å¨æ¨¡åç»æä½çå段ä¸ä½¿ç¨ tag æ è¯ï¼éè¿è§£æç»æä½æ¶æ£æµã æä»¥å¿ é¡»å¨è¡¨å æ°æ®ååå æ°æ®çç»æä½ä¸å ä¸ï¼è¡¨ç»æå®ä¹æ¶åç Tag å±æ§ã å®ä¹å¦ä¸å¸¸è§çeorm模åæ ç¾ï¼
- eorm:"primary_key": å°å段设置为主é®ã
- eorm:"auto_increment": å°å段设置为èªå¢ã
- eorm:"type:data_type": æå®å段çæ°æ®åºæ°æ®ç±»åã
- eorm:"size:length": æå®å段çé¿åº¦æ大å°ã
- eorm:"default:default_value": æå®å段çé»è®¤å¼ã
- eorm:"not null": æå®å段ä¸è½ä¸ºç©ºã
- eorm:"unique": æå®å段çå¼å¨è¡¨ä¸å¿ é¡»å¯ä¸ã
- eorm:"index": 为å段å建索å¼ã
- eorm:"uniqueIndex": 为å段å建å¯ä¸ç´¢å¼ã
- eorm:"primary_key;auto_increment": å°å段åæ¶è®¾ç½®ä¸ºä¸»é®åèªå¢ã
- eorm:"-": 忽ç¥å段ï¼ä¸ä¼æ å°å°æ°æ®åºè¡¨ä¸ã
- eorm:"comment:text" 为å段添å 注éã
以ä¸æ¯ç¤ºä¾ç»æä½æ¨¡åï¼
type User struct {
ID uint `eorm:"id;primary_key;auto_increment"`
Name string `eorm:"size:255"`
Age int `eorm:"age"`
Email string `eorm:"uniqueIndex"`
CreatedAt time.Time
UpdatedAt time.Time
}
ç´¢å¼ç¤ºä¾
type User struct {
ID uint `gorm:"primary_key;auto_increment"`
Name string `gorm:"index"`
Age int `gorm:"index:idx_age"`
Email string `gorm:"uniqueIndex"`
CreatedAt time.Time
UpdatedAt time.Time
}
type User struct {
ID uint `gorm:"column:id;primary_key;auto_increment"`
Name string `gorm:"column:name;uniqueIndex:idx_name"`
// ...
}
èåç´¢å¼ç¤ºä¾
type User struct {
ID uint `eorm:"primary_key;auto_increment"`
Name string `eorm:"index:idx_name_age"`
Age int `eorm:"index:idx_name_age"`
Email string `eorm:"uniqueIndex"`
CreatedAt time.Time
UpdatedAt time.Time
}
æå®ç±»å示ä¾
type User struct {
ID uint `eorm:"column:id;primary_key;auto_increment"`
Name string `eorm:"column:name;type:string"`
Age int `eorm:"column:age;type:int"`
// ...
}
é»è®¤å¼
type User struct {
ID uint `eorm:"primary_key;auto_increment"`
Name string `eorm:"default:'John Doe'"`
Age int `eorm:"default:18"`
IsActive bool `eorm:"default:true"`
// .
å段注é
type User struct {
ID uint `eorm:"column:id;primary_key;auto_increment" comment:"ç¨æ·ID"`
Name string `eorm:"comment:"ç¨æ·å§å"`
Age int `eorm:"comment:"ç¨æ·å¹´é¾"`
// ...
}
å æ°æ®æ¨¡åï¼éè¦å¼å ¥ç´¢å¼ã约æãå段类åãé»è®¤å¼çå¤æçç»æä½å段ï¼æ¥ç»ä¸å±ç build è¯å¥æ建è¯æ³
var (
TimeReflectType = reflect.TypeOf(time.Time{})
TimePtrReflectType = reflect.TypeOf(&time.Time{})
ByteReflectType = reflect.TypeOf(uint8(0))
)
type (
DataType string
TimeType int64
)
const (
Bool DataType = "bool"
Int DataType = "int"
Uint DataType = "uint"
Float DataType = "float"
String DataType = "string"
Time DataType = "time"
Bytes DataType = "bytes"
)
type ColumnIndex struct {
IsPrimaryKey bool
Type string
name string
Columns []string
}
// TableMeta represents data model, or a table
type TableMeta struct {
TableName string
Engine string
Charset string
Columns []*ColumnMeta
// FieldMap æ¯å段åå°åå
æ°æ®çæ å°
FieldMap map[string]*ColumnMeta
// ColumnMap æ¯ååå°åå
æ°æ®çæ å°
ColumnMap map[string]*ColumnMeta
Typ reflect.Type
ShardingAlgorithm sharding.Algorithm
}
// ColumnMeta represents model's field, or column
type ColumnMeta struct {
ColumnName string
FieldName string
Type reflect.Type
IndirectFieldType reflect.Type
Val reflect.Value
Tag reflect.StructTag
TagMap map[string]string
FieldType string
DataType DataType
// æ¯å¦ä¸»é®
IsPrimaryKey bool
// æ¯å¦èªå¢
IsAutoIncrement bool
AutoIncrementIncrement int64
// é»è®¤å¼
HasDefaultValue bool
DefaultValue string
DefaultValueInterface any
// æ¯å¦é空
IsNull bool
// æ¯å¦å¯ä¸ç´¢å¼
Unique bool
// å注é
Comment string
// å大å°
Size int
// æ¯å¦å¿½ç¥è¿ç§»
IgnoreMigration bool
// decimal
Precision int
Scale int
Offset uintptr
// FieldPos
// FieldIndexes ç¨äºè¡¨è¾¾ä»æå¤å±ç»æä½æ¾å°å½åColumnMeta对åºçFieldæéè¦çç´¢å¼é
FieldPos []int
ColumnIndexes map[string]ColumnIndex
}
ç´¢å¼ç解æï¼æ³¨æï¼ç´¢å¼ç解æä¹æ¯éè¿æ¨¡åç»æä½çtagæ¥è§£æçã
type ColumnIndex struct {
Name string
IsPrimaryKey bool
IsUnique bool
ColumnName string
ColumnList []string
IndexClass string // UNIQUE | FULLTEXT | SPATIAL
Comment string
Length int
Columns []*ColumnMeta
}
func (meta *ColumnMeta) ParseIndexes() (map[string]ColumnIndex, error) {
var (
err error
indexes map[string]ColumnIndex
)
if meta.TagMap["INDEX"] != "" || meta.TagMap["UNIQUEINDEX"] != "" {
fieldIndexes, err := parseColumnIndexes(meta)
if err != nil {
return indexes, err
}
for _, index := range fieldIndexes {
idx := indexes[index.Name]
idx.Name = index.Name
if idx.IndexClass == "" {
idx.IndexClass = index.IndexClass
}
if idx.Comment == "" {
idx.Comment = index.Comment
}
idx.Columns = append(idx.Columns, index.Columns...)
indexes[index.Name] = idx
if index.IndexClass == "UNIQUE" && len(index.Columns) == 1 {
index.Columns[0].Unique = true
}
}
}
return indexes, err
}
func (meta *TableMeta) LoadIndex(name string) *ColumnIndex {
if meta != nil {
for _, colMeta := range meta.Columns {
idx := colMeta.LoadColumnIndex(name)
if idx != nil {
return idx
}
}
}
return nil
}
func (meta *ColumnMeta) LoadColumnIndex(colName string) *ColumnIndex {
if meta != nil {
for _, index := range meta.ColumnIndexes {
if index.Name == colName {
return &index
}
for _, field := range index.Columns {
if field.ColumnName == colName {
return &index
}
}
}
}
return nil
}
func parseColumnIndexes(field *ColumnMeta) (indexes []ColumnIndex, err error) {
for _, value := range strings.Split(field.Tag.Get("eorm"), ";") {
if value != "" {
v := strings.Split(value, ":")
k := strings.TrimSpace(strings.ToUpper(v[0]))
if k == "INDEX" || k == "UNIQUEINDEX" {
var (
name string
tag = strings.Join(v[1:], ":")
idx = strings.Index(tag, ",")
tagSetting = strings.Join(strings.Split(tag, ",")[1:], ",")
settings = ParseTagMap(tagSetting, ",")
length, _ = strconv.Atoi(settings["LENGTH"])
)
if idx == -1 {
idx = len(tag)
}
if idx != -1 {
name = tag[0:idx]
}
var isUnique bool
if (k == "UNIQUEINDEX") || settings["UNIQUE"] != "" {
settings["CLASS"] = "UNIQUE"
isUnique = true
}
indexes = append(indexes, ColumnIndex{
Name: name,
ColumnName: field.ColumnName,
IsUnique: isUnique,
IndexClass: settings["CLASS"],
Comment: settings["COMMENT"],
Length: length,
Columns: []*ColumnMeta{field},
})
}
}
}
err = nil
return
}
æ¹é 注å模åä¸ç解æå段æ¹æ³ parseFields ã
func ParseTagMap(str string, sep string) map[string]string {
tagMap := map[string]string{}
names := strings.Split(str, sep)
for i := 0; i < len(names); i++ {
j := i
if len(names[j]) > 0 {
for {
if names[j][len(names[j])-1] == '\\' {
i++
names[j] = names[j][0:len(names[j])-1] + sep + names[i]
names[i] = ""
} else {
break
}
}
}
values := strings.Split(names[j], ":")
// k := strings.TrimSpace(strings.ToUpper(values[0]))
ks := strings.Split(values[0], "_")
k := strings.TrimSpace(strings.ToUpper(strings.Join(ks, "")))
if len(values) >= 2 {
tagMap[k] = strings.Join(values[1:], ":")
} else if k != "" {
tagMap[k] = k
}
}
return tagMap
}
// CheckTruth check string true or not
func CheckTruth(vals ...string) bool {
for _, val := range vals {
if val != "" && !strings.EqualFold(val, "false") {
return true
}
}
return false
}
func (t *tagMetaRegistry) parseFields(v reflect.Type, fieldPos []int,
columnMetas *[]*ColumnMeta, fieldMap map[string]*ColumnMeta,
pOffset uintptr) error {
var err error
lens := v.NumField()
for i := 0; i < lens; i++ {
structField := v.Field(i)
// tag := structField.Tag.Get("eorm")
tagMap := ParseTagMap(structField.Tag.Get("eorm"), ";")
if CheckTruth(tagMap["-"]) {
// skip the field
continue
}
// æ£æ¥åæ没æå²çª
if fieldMap[structField.Name] != nil {
return errs.NewFieldConflictError(v.Name() + "." + structField.Name)
}
// æ¯ç»å
if structField.Anonymous {
// ä¸æ¯æ使ç¨æéçç»å
if structField.Type.Kind() != reflect.Struct {
return errs.ErrCombinationIsNotStruct
}
// éå½è§£æ
o := structField.Offset + pOffset
err = t.parseFields(structField.Type, append(fieldPos, i), columnMetas, fieldMap, o)
if err != nil {
return err
}
continue
}
size, err := strconv.Atoi(tagMap["SIZE"])
if err != nil {
return err
}
columnMeta := &ColumnMeta{
//FieldType: fieldType,
Size: size,
Tag: structField.Tag,
TagMap: tagMap,
FieldName: structField.Name,
Type: structField.Type,
IndirectFieldType: structField.Type,
Offset: structField.Offset + pOffset,
FieldPos: append(fieldPos, i),
IsPrimaryKey: CheckTruth(tagMap["PRIMARYKEY"], tagMap["PRIMARY_KEY"]),
IsAutoIncrement: CheckTruth(tagMap["AUTOINCREMENT"]),
HasDefaultValue: CheckTruth(tagMap["DEFAULT"]),
IsNull: CheckTruth(tagMap["NULL"]),
ColumnName: underscoreName(structField.Name),
Comment: tagMap["COMMENT"],
}
if v, ok := columnMeta.TagMap["DEFAULT"]; ok {
columnMeta.HasDefaultValue = true
columnMeta.DefaultValue = v
}
fieldValue := reflect.New(columnMeta.IndirectFieldType)
valuer, isValuer := fieldValue.Interface().(driver.Valuer)
if isValuer {
if _, ok := fieldValue.Interface().(ColumnTypeInterface); !ok {
if v, err := valuer.Value(); reflect.ValueOf(v).IsValid() && err == nil {
fieldValue = reflect.ValueOf(v)
}
// Use the field struct's first field type as data type, e.g: use `string` for sql.NullString
var getRealFieldValue func(reflect.Value)
getRealFieldValue = func(v reflect.Value) {
var (
rv = reflect.Indirect(v)
rvType = rv.Type()
)
if rv.Kind() == reflect.Struct && !rvType.ConvertibleTo(TimeReflectType) {
for i := 0; i < rvType.NumField(); i++ {
for key, value := range ParseTagMap(rvType.Field(i).Tag.Get("eorm"), ";") {
if _, ok := columnMeta.TagMap[key]; !ok {
columnMeta.TagMap[key] = value
}
}
}
for i := 0; i < rvType.NumField(); i++ {
newFieldType := rvType.Field(i).Type
for newFieldType.Kind() == reflect.Ptr {
newFieldType = newFieldType.Elem()
}
fieldValue = reflect.New(newFieldType)
if rvType != reflect.Indirect(fieldValue).Type() {
getRealFieldValue(fieldValue)
}
if fieldValue.IsValid() {
return
}
}
}
}
getRealFieldValue(fieldValue)
}
}
// default value is function or null or blank (primary keys)
columnMeta.DefaultValue = strings.TrimSpace(columnMeta.DefaultValue)
skipParseDefaultValue := strings.Contains(columnMeta.DefaultValue, "(") &&
strings.Contains(columnMeta.DefaultValue, ")") ||
strings.ToLower(columnMeta.DefaultValue) == "null" || columnMeta.DefaultValue == ""
switch reflect.Indirect(fieldValue).Kind() {
case reflect.Bool:
columnMeta.DataType = Bool
if columnMeta.HasDefaultValue && !skipParseDefaultValue {
if columnMeta.DefaultValueInterface, err = strconv.ParseBool(columnMeta.DefaultValue); err != nil {
err = fmt.Errorf("failed to parse %s as default value for bool, got error: %v", columnMeta.DefaultValue, err)
}
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
columnMeta.DataType = Int
if columnMeta.HasDefaultValue && !skipParseDefaultValue {
if columnMeta.DefaultValueInterface, err = strconv.ParseInt(columnMeta.DefaultValue, 0, 64); err != nil {
err = fmt.Errorf("failed to parse %s as default value for int, got error: %v", columnMeta.DefaultValue, err)
}
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
columnMeta.DataType = Uint
if columnMeta.HasDefaultValue && !skipParseDefaultValue {
if columnMeta.DefaultValueInterface, err = strconv.ParseUint(columnMeta.DefaultValue, 0, 64); err != nil {
err = fmt.Errorf("failed to parse %s as default value for uint, got error: %v", columnMeta.DefaultValue, err)
}
}
case reflect.Float32, reflect.Float64:
columnMeta.DataType = Float
if columnMeta.HasDefaultValue && !skipParseDefaultValue {
if columnMeta.DefaultValueInterface, err = strconv.ParseFloat(columnMeta.DefaultValue, 64); err != nil {
err = fmt.Errorf("failed to parse %s as default value for float, got error: %v", columnMeta.DefaultValue, err)
}
}
case reflect.String:
columnMeta.DataType = String
if columnMeta.HasDefaultValue && !skipParseDefaultValue {
columnMeta.DefaultValue = strings.Trim(columnMeta.DefaultValue, "'")
columnMeta.DefaultValue = strings.Trim(columnMeta.DefaultValue, `"`)
columnMeta.DefaultValueInterface = columnMeta.DefaultValue
}
case reflect.Struct:
if _, ok := fieldValue.Interface().(*time.Time); ok {
columnMeta.DataType = Time
} else if fieldValue.Type().ConvertibleTo(TimeReflectType) {
columnMeta.DataType = Time
} else if fieldValue.Type().ConvertibleTo(TimePtrReflectType) {
columnMeta.DataType = Time
}
if columnMeta.HasDefaultValue && !skipParseDefaultValue && columnMeta.DataType == Time {
if t, err := now.Parse(columnMeta.DefaultValue); err == nil {
columnMeta.DefaultValueInterface = t
}
}
case reflect.Array, reflect.Slice:
if reflect.Indirect(fieldValue).Type().Elem() == ByteReflectType && columnMeta.DataType == "" {
columnMeta.DataType = Bytes
}
}
if val, ok := columnMeta.TagMap["TYPE"]; ok {
switch DataType(strings.ToLower(val)) {
case Bool, Int, Uint, Float, String, Time, Bytes:
columnMeta.DataType = DataType(strings.ToLower(val))
columnMeta.FieldType = strings.ToUpper(val)
default:
columnMeta.FieldType = strings.ToUpper(val)
}
}
if columnMeta.Size == 0 {
switch reflect.Indirect(fieldValue).Kind() {
case reflect.Int, reflect.Int64, reflect.Uint, reflect.Uint64, reflect.Float64:
columnMeta.Size = 64
case reflect.Int8, reflect.Uint8:
columnMeta.Size = 8
case reflect.Int16, reflect.Uint16:
columnMeta.Size = 16
case reflect.Int32, reflect.Uint32, reflect.Float32:
columnMeta.Size = 332
}
}
// 为äºç» builder è°ç¨ï¼æ以åå解æçç´¢å¼ç»æå¿
é¡»ä¿åä¸æ¥ã
if columnMeta.TagMap["INDEX"] != "" || columnMeta.TagMap["UNIQUEINDEX"] != "" {
columnIndexes, err := columnMeta.ParseIndexes()
if err != nil {
return err
}
columnMeta.ColumnIndexes = columnIndexes
}
columnMeta.Val = fieldValue
*columnMetas = append(*columnMetas, columnMeta)
fieldMap[columnMeta.FieldName] = columnMeta
}
return err
}
å¢å FieldType çç®çæ¯ç¨äºä¿å该å段åæ°æ®åºä¸çå®é ç±»åï¼ä¾å¦ âvarcharâï¼å¨æ建è¯å¥æ¶æ ¹æ®DataType转æ¢å¾æ¥ï¼é»è®¤DataTypeæ¯æ ¹æ®ç»æä½å段çç±»åï¼ä¹æ¯æ使ç¨è æ ¹æ®tagå®ä¹çç±»åã å½æ¨¡åç»æä½ä¸çtagåºè§£æåºç±»ä¼¼ä¸ âtype:stringâè¿é¨åæè¿°æ¶ï¼DataTypeç¨æ¥ä¿å stringï¼ä¹åçæ建ä¸å°ä¼è½¬æ¢ææ°æ®åºä¸çå®é ç±»åï¼å¹¶ä¿åå¨FieldTypeä¸ã å®ä¹ä¸ä¸ª ColumnTypeInterfaceï¼ åæ¶ç¨æ¥æ¯æ使ç¨è ç´æ¥èªå·±å®ä¹æ°æ®åºçå段çç±»åã
type ColumnTypeInterface interface {
ColumnType() string
}
示ä¾ï¼
type MyStringType string
func (t MyCustomType) DataType() string {
return "varchar(255)"
}
对äºç±»åç转æ¢ï¼ä¸åçæ°æ®åºè¯æ³ä¸çç±»åæ述符æ¯ä¸ä¸æ ·çï¼æ以éè¦æ¹é æ¹è¨æ¨¡åï¼åæç»ä¸æ¥å£æ½è±¡ï¼ç¨äºæ¯æç±»å转æ¢ç1æ¹æ³ï¼ä¹æ¹ä¾¿åç»çæ¹è¨ç¸å ³åè½æ©å±ã
var (
MySQL Dialect = (*mysqlDialect)(nil)
SQLite3 Dialect = (*sqlite3Dialect)(nil)
)
type Dialect interface {
// quoter è¿åä¸ä¸ªå¼å·ï¼å¼ç¨ååï¼è¡¨åçå¼å·
quoter() byte
ColumnTypeOf(columnMeta *model.ColumnMeta) string
ColumnDesc(columnMeta *model.ColumnMeta) string
}
type standardSQL struct{}
func (d *standardSQL) quoter() byte {
// TODO implement me
panic("implement me")
}
func (d *standardSQL) ColumnTypeOf(columnMeta *model.ColumnMeta) string {
// TODO implement me
panic("implement me")
}
func (d *standardSQL) ColumnDesc(columnMeta *model.ColumnMeta) string {
// TODO implement me
panic("implement me")
}
mysql ç±»å
const (
AutoRandomTag = "auto_random()" // Treated as an auto_random field for tidb
)
type mysqlDialect struct {
standardSQL
}
func (d *mysqlDialect) quoter() byte {
return '`'
}
func (d *mysqlDialect) ColumnTypeOf(columnMeta *model.ColumnMeta) string {
switch columnMeta.DataType {
case model.Bool:
return "BOOLEAN"
case model.Int, model.Uint:
return d.getIntAndUnitType(columnMeta)
case model.Float:
return d.getFloatType(columnMeta)
case model.String:
return d.getStringType(columnMeta)
case model.Time:
return d.getTimeType(columnMeta)
case model.Bytes:
return d.getBytesType(columnMeta)
default:
return d.getCustomType(columnMeta)
}
}
func (d *mysqlDialect) getFloatType(columnMeta *model.ColumnMeta) string {
if columnMeta.Precision > 0 {
return fmt.Sprintf("DECIMAL(%d, %d)", columnMeta.Precision, columnMeta.Scale)
}
if columnMeta.Size <= 32 {
return "FLOAT"
}
return "DOUBLE"
}
func (d *mysqlDialect) getStringType(columnMeta *model.ColumnMeta) string {
size := columnMeta.Size
if size == 0 {
size = 255
} else {
hasIndex := columnMeta.TagMap["INDEX"] != "" || columnMeta.TagMap["UNIQUE"] != ""
// TEXT, GEOMETRY or JSON column can't have a default value
if columnMeta.IsPrimaryKey || columnMeta.HasDefaultValue || hasIndex {
size = 191 // utf8mb4
}
}
if size >= 65536 && size <= int(math.Pow(2, 24)) {
return "MEDIUMTEXT"
}
if size > int(math.Pow(2, 24)) || size <= 0 {
return "LONGTEXT"
}
return fmt.Sprintf("VARCHAR(%d)", size)
}
func (d *mysqlDialect) getTimeType(columnMeta *model.ColumnMeta) string {
var precision string
if columnMeta.Precision > 0 {
precision = fmt.Sprintf("(%d)", columnMeta.Precision)
}
return "DATETIME" + precision
}
func (d *mysqlDialect) getBytesType(columnMeta *model.ColumnMeta) string {
if columnMeta.Size > 0 && columnMeta.Size < 65536 {
return fmt.Sprintf("VARBINARY(%d)", columnMeta.Size)
}
if columnMeta.Size >= 65536 && columnMeta.Size <= int(math.Pow(2, 24)) {
return "MEDIUMBLOB"
}
return "LONGBLOB"
}
func autoRandomType(columnMeta *model.ColumnMeta) (bool, string) {
if columnMeta.IsPrimaryKey && columnMeta.HasDefaultValue &&
strings.ToLower(strings.TrimSpace(columnMeta.DefaultValue)) == AutoRandomTag {
columnMeta.DefaultValue = ""
sqlType := "BIGINT"
if columnMeta.DataType == model.Uint {
sqlType += " UNSIGNED"
}
sqlType += " AUTO_RANDOM"
return true, sqlType
}
return false, ""
}
func (d *mysqlDialect) getIntAndUnitType(columnMeta *model.ColumnMeta) string {
if autoRandom, typeString := autoRandomType(columnMeta); autoRandom {
return typeString
}
constraint := func(sqlType string) string {
if columnMeta.DataType == model.Uint {
sqlType += " UNSIGNED"
}
if columnMeta.IsAutoIncrement {
sqlType += " AUTO_INCREMENT"
}
return sqlType
}
switch {
case columnMeta.Size <= 8:
return constraint("TINYINT")
case columnMeta.Size <= 16:
return constraint("SMALLINT")
case columnMeta.Size <= 24:
return constraint("MEDIUMINT")
case columnMeta.Size <= 32:
return constraint("INT")
default:
return constraint("BIGINT")
}
}
func (d *mysqlDialect) getCustomType(columnMeta *model.ColumnMeta) string {
sqlType := string(columnMeta.DataType)
if columnMeta.IsAutoIncrement && !strings.Contains(strings.ToLower(sqlType), " auto_increment") {
sqlType += " AUTO_INCREMENT"
}
return sqlType
}
func (d *mysqlDialect) ColumnDesc(columnMeta *model.ColumnMeta) string {
var sb strings.Builder
if columnMeta.IsPrimaryKey {
if columnMeta.IsAutoIncrement {
sb.WriteString("AUTO_INCREMENT")
}
sb.WriteString(" PRIMARY KEY")
} else {
if columnMeta.IsNull {
sb.WriteString("NULL")
}
if columnMeta.HasDefaultValue {
sb.WriteString(" DEFAULT ")
sb.WriteString(columnMeta.DefaultValue)
}
if columnMeta.IsAutoIncrement {
sb.WriteString(" AUTO_INCREMENT")
}
}
if columnMeta.Comment != "" {
sb.WriteString(" COMMENT")
sb.WriteString("'")
sb.WriteString(columnMeta.Comment)
sb.WriteString("'")
}
return sb.String()
}
sqlite ç±»å示ä¾ä¹æ¯å¦æ¤
type sqlite3Dialect struct {
standardSQL
}
func (d *sqlite3Dialect) quoter() byte {
return '`'
}
func (d *sqlite3Dialect) ColumnTypeOf(columnMeta *model.ColumnMeta) string {
switch columnMeta.DataType {
case model.Bool:
return "BOOLEAN"
case model.Int, model.Uint:
return d.getIntAndUnitType(columnMeta)
case model.Float:
return d.getFloatType(columnMeta)
case model.String:
return d.getStringType(columnMeta)
case model.Time:
return d.getTimeType(columnMeta)
case model.Bytes:
return d.getBytesType(columnMeta)
default:
return d.getCustomType(columnMeta)
}
}
func (d *sqlite3Dialect) ColumnDesc(columnMeta *model.ColumnMeta) string {
// TODO implement me
panic("implement me")
}
æä¸å±ç builder å®ç°
type Creator[T any] struct {
builder
Session
table TableReference
}
func NewCreator[T any](sess Session) *Creator[T] {
return &Creator[T]{
builder: builder{
core: sess.getCore(),
buffer: bytebufferpool.Get(),
},
Session: sess,
}
}
func (c *Creator[T]) Table(tbl TableReference) *Creator[T] {
c.table = tbl
return c
}
func (c *Creator[T]) tableOf() any {
switch tb := c.table.(type) {
case Table:
return tb.entity
default:
// ä¸ä½¿ç¨ new(T) æ¥è§é¿å
ååé
return (*T)(nil)
}
}
func (c *Creator[T]) Build() (Query, error) {
defer bytebufferpool.Put(c.buffer)
var err error
c.meta, err = c.metaRegistry.Get(c.tableOf())
if err != nil {
return EmptyQuery, err
}
c.writeString("CREATE TABLE IF NOT EXISTS ")
c.quote(c.meta.TableName)
c.space()
c.writeByte('(')
err = c.buildColumns()
if err != nil {
return EmptyQuery, err
}
err = c.buildIndex()
if err != nil {
return EmptyQuery, err
}
c.writeByte(')')
c.writeString(" ENGINE=")
c.writeString(c.meta.Engine)
c.writeString(" DEFAULT ")
c.writeString("CHARSET=")
c.writeString(c.meta.Charset)
c.end()
return Query{SQL: c.buffer.String(), Args: c.args}, nil
}
func (c *Creator[T]) buildColumns() error {
for idx, colMeta := range c.meta.Columns {
c.quote(colMeta.FieldName)
c.space()
fieldValue := colMeta.Val
if valInterface, ok := fieldValue.Interface().(model.ColumnTypeInterface); ok {
fieldType := valInterface.ColumnType()
c.writeString(fieldType)
} else {
fieldType := c.dialect.ColumnTypeOf(colMeta)
c.writeString(fieldType)
}
c.space()
c.writeString(c.dialect.ColumnDesc(colMeta))
if idx < len(c.meta.Columns) {
c.writeByte(',')
}
}
return nil
}
func (c *Creator[T]) buildIndex() error {
for idx, colMeta := range c.meta.Columns {
index := colMeta.LoadColumnIndex(colMeta.ColumnName)
if index != nil {
if index.IsUnique && colMeta.Unique {
c.writeString("UNIQUE ")
}
c.writeString("INDEX")
c.space()
c.writeString(index.Name)
c.space()
c.writeByte('(')
for pos, colIdx := range index.Columns {
if pos > 0 {
c.writeByte(',')
}
c.writeString(colIdx.ColumnName)
}
c.writeByte(')')
if idx < len(c.meta.Columns) {
c.writeByte(',')
}
}
}
return nil
}
func (c *Creator[T]) Get(ctx context.Context) (*T, error) {
query, err := c.Build()
if err != nil {
return nil, err
}
return newQuerier[T](c.Session, query, c.meta, CREATE).Get(ctx)
}
Drop
MySQL
å é¤è¡¨è¯å¥
DROP [TEMPORARY] TABLE [IF EXISTS]
tbl_name [, tbl_name] ...
[RESTRICT | CASCADE]
DROP TABLE å é¤ä¸ä¸ªæå¤ä¸ªè¡¨ãæ¨å¿ é¡»å ·æ DROP æ¯ä¸ªè¡¨çæéã 对äºæ¯ä¸ªè¡¨ï¼å®å°å é¤è¡¨å®ä¹åææ表æ°æ®ãå¦æ表已ååºï¼å该è¯å¥å°å é¤è¡¨å®ä¹ãå ¶ææååºãåå¨å¨è¿äºååºä¸çæææ°æ®ä»¥åä¸å·²å é¤çè¡¨å ³èçææååºå®ä¹ãå é¤è¡¨ä¹ä¼å é¤è¡¨çææ触åå¨ã DROP TABLE 导è´éå¼æ交ï¼é¤éä¸ TEMPORARY å ³é®åä¸èµ·ä½¿ç¨ã å é¤ç´¢å¼è¯å¥
DROP INDEX index_name ON tbl_name
[algorithm_option | lock_option] ...
algorithm_option:
ALGORITHM [=] {DEFAULT | INPLACE | COPY}
lock_option:
LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}
DROP INDEX å é¤è¡¨ä¸å½å index_name çç´¢å¼ tbl_name ãæ¤è¯å¥æ å°å°è¦å é¤ç´¢å¼ç ALTER TABLE è¯å¥ã è¦å é¤ä¸»é®ï¼ç´¢å¼åå§ç» PRIMARY 为 ï¼å¿ é¡»å°å ¶æå®ä¸ºå¸¦å¼å·çæ è¯ç¬¦ï¼å 为 PRIMARY å®æ¯ä¸ä¸ªä¿çåï¼
DROP INDEX `PRIMARY` ON t;
SQLite
SQLite ç DROP TABLE è¯å¥ç¨æ¥å é¤è¡¨å®ä¹åå
¶ææç¸å
³æ°æ®ãç´¢å¼ã触åå¨ã约æå该表çæéè§èã
å é¤è¡¨
DROP TABLE IF EXISTS database_name.table_name;
å é¤ç´¢å¼
DROP INDEX index_name
å ·ä½ä»£ç
var _ QueryBuilder = &Drop[any]{}
type Drop[T any] struct {
builder
Session
ts []any
}
func NewDrop[T any](sess Session) *Drop[T] {
return &Drop[T]{
builder: builder{
core: sess.getCore(),
buffer: bytebufferpool.Get(),
},
Session: sess,
}
}
func (d *Drop[T]) tables(ts []any) *Drop[T] {
d.ts = ts
return d
}
func (d *Drop[T]) Build() (Query, error) {}
Alter
MySQL
æ´æ¹è¡¨çç»æãä¾å¦ï¼å¯ä»¥æ·»å æå é¤åãå建æéæ¯ç´¢å¼ãæ´æ¹ç°æåçç±»åæè éå½ååæ表æ¬èº«ãè¿å¯ä»¥æ´æ¹ç¹å¾ï¼ä¾å¦ç¨äºè¡¨æ表注éçåå¨å¼æã
ALTER TABLE tbl_name
[alter_option [, alter_option] ...]
[partition_options]
alter_option: {
table_options
| ADD [COLUMN] col_name column_definition
[FIRST | AFTER col_name]
| ADD [COLUMN] (col_name column_definition,...)
| ADD {INDEX | KEY} [index_name]
[index_type] (key_part,...) [index_option] ...
| ADD {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name]
(key_part,...) [index_option] ...
| ADD [CONSTRAINT [symbol]] PRIMARY KEY
[index_type] (key_part,...)
[index_option] ...
| ADD [CONSTRAINT [symbol]] UNIQUE [INDEX | KEY]
[index_name] [index_type] (key_part,...)
[index_option] ...
| ADD [CONSTRAINT [symbol]] FOREIGN KEY
[index_name] (col_name,...)
reference_definition
| ADD [CONSTRAINT [symbol]] CHECK (expr) [[NOT] ENFORCED]
| DROP {CHECK | CONSTRAINT} symbol
| ALTER {CHECK | CONSTRAINT} symbol [NOT] ENFORCED
| ALGORITHM [=] {DEFAULT | INSTANT | INPLACE | COPY}
| ALTER [COLUMN] col_name {
SET DEFAULT {literal | (expr)}
| SET {VISIBLE | INVISIBLE}
| DROP DEFAULT
}
| ALTER INDEX index_name {VISIBLE | INVISIBLE}
| CHANGE [COLUMN] old_col_name new_col_name column_definition
[FIRST | AFTER col_name]
| [DEFAULT] CHARACTER SET [=] charset_name [COLLATE [=] collation_name]
| CONVERT TO CHARACTER SET charset_name [COLLATE collation_name]
| {DISABLE | ENABLE} KEYS
| {DISCARD | IMPORT} TABLESPACE
| DROP [COLUMN] col_name
| DROP {INDEX | KEY} index_name
| DROP PRIMARY KEY
| DROP FOREIGN KEY fk_symbol
| FORCE
| LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}
| MODIFY [COLUMN] col_name column_definition
[FIRST | AFTER col_name]
| ORDER BY col_name [, col_name] ...
| RENAME COLUMN old_col_name TO new_col_name
| RENAME {INDEX | KEY} old_index_name TO new_index_name
| RENAME [TO | AS] new_tbl_name
| {WITHOUT | WITH} VALIDATION
}
partition_options:
partition_option [partition_option] ...
partition_option: {
ADD PARTITION (partition_definition)
| DROP PARTITION partition_names
| DISCARD PARTITION {partition_names | ALL} TABLESPACE
| IMPORT PARTITION {partition_names | ALL} TABLESPACE
| TRUNCATE PARTITION {partition_names | ALL}
| COALESCE PARTITION number
| REORGANIZE PARTITION partition_names INTO (partition_definitions)
| EXCHANGE PARTITION partition_name WITH TABLE tbl_name [{WITH | WITHOUT} VALIDATION]
| ANALYZE PARTITION {partition_names | ALL}
| CHECK PARTITION {partition_names | ALL}
| OPTIMIZE PARTITION {partition_names | ALL}
| REBUILD PARTITION {partition_names | ALL}
| REPAIR PARTITION {partition_names | ALL}
| REMOVE PARTITIONING
}
key_part: {col_name [(length)] | (expr)} [ASC | DESC]
index_type:
USING {BTREE | HASH}
index_option: {
KEY_BLOCK_SIZE [=] value
| index_type
| WITH PARSER parser_name
| COMMENT 'string'
| {VISIBLE | INVISIBLE}
}
table_options:
table_option [[,] table_option] ...
table_option: {
AUTOEXTEND_SIZE [=] value
| AUTO_INCREMENT [=] value
| AVG_ROW_LENGTH [=] value
| [DEFAULT] CHARACTER SET [=] charset_name
| CHECKSUM [=] {0 | 1}
| [DEFAULT] COLLATE [=] collation_name
| COMMENT [=] 'string'
| COMPRESSION [=] {'ZLIB' | 'LZ4' | 'NONE'}
| CONNECTION [=] 'connect_string'
| {DATA | INDEX} DIRECTORY [=] 'absolute path to directory'
| DELAY_KEY_WRITE [=] {0 | 1}
| ENCRYPTION [=] {'Y' | 'N'}
| ENGINE [=] engine_name
| ENGINE_ATTRIBUTE [=] 'string'
| INSERT_METHOD [=] { NO | FIRST | LAST }
| KEY_BLOCK_SIZE [=] value
| MAX_ROWS [=] value
| MIN_ROWS [=] value
| PACK_KEYS [=] {0 | 1 | DEFAULT}
| PASSWORD [=] 'string'
| ROW_FORMAT [=] {DEFAULT | DYNAMIC | FIXED | COMPRESSED | REDUNDANT | COMPACT}
| SECONDARY_ENGINE_ATTRIBUTE [=] 'string'
| STATS_AUTO_RECALC [=] {DEFAULT | 0 | 1}
| STATS_PERSISTENT [=] {DEFAULT | 0 | 1}
| STATS_SAMPLE_PAGES [=] value
| TABLESPACE tablespace_name [STORAGE {DISK | MEMORY}]
| UNION [=] (tbl_name[,tbl_name]...)
}
partition_options:
(see CREATE TABLE options)
æ·»å åå é¤å ç¨äºADDå表ä¸æ·»å æ°å以å DROPå é¤ç°æåãæ¯æ å SQL ç MySQL æ©å±ã DROP col_name è¦å¨è¡¨è¡ä¸çç¹å®ä½ç½®æ·»å åï¼è¯·ä½¿ç¨ FIRSTæãé»è®¤æ åµä¸æåæ·»å åã AFTER col_name å¦æè¡¨ä» å å«ä¸åï¼åæ æ³å é¤è¯¥åã å¦æä»è¡¨ä¸å é¤åï¼åè¿äºåä¹ä¼ä»å®ä»¬æå±çä»»ä½ç´¢å¼ä¸å é¤ãå¦æææç´¢å¼çææåé½è¢«å é¤ï¼åç´¢å¼ä¹ä¼è¢«å é¤ãå¦æä½¿ç¨ CHANGEæMODIFYæ¥ç¼©çåä¸åå¨ç´¢å¼çåï¼å¹¶ä¸ç»æåé¿åº¦å°äºç´¢å¼é¿åº¦ï¼MySQL ä¼èªå¨ç¼©çç´¢å¼ã 对äºALTER TABLE ... ADDï¼å¦æåå ·æ使ç¨ä¸ç¡®å®æ§å½æ°ç表达å¼é»è®¤å¼ï¼å该è¯å¥å¯è½ä¼äº§çè¦åæé误ã
- å个è¯å¥ä¸å è®¸ä½¿ç¨ å¤ä¸ªADD, ALTER, DROP, ååå¥ï¼ä»¥éå·åéãè¿æ¯ MySQL 对æ å SQL çæ©å±ï¼æ¯ä¸ªè¯å¥ä» å 许æ¯ä¸ªåå¥ä¹ä¸ ãä¾å¦ï¼è¦å é¤å个è¯å¥ä¸çå¤ä¸ªåï¼è¯·æ§è¡ä»¥ä¸æä½ï¼ CHANGEALTER TABLEALTER TABLE
ALTER TABLE t2 DROP COLUMN c, DROP COLUMN d;
éå½åãéæ°å®ä¹ è¦æ´æ¹å以æ´æ¹å ¶å称åå®ä¹ï¼è¯·ä½¿ç¨ CHANGEï¼æå®æ§å称ãæ°å称以åæ°å®ä¹ãä¾å¦ï¼è¦å°INT NOT NULLåä»aé å½å为b并å°å ¶å®ä¹æ´æ¹ä¸ºä½¿ç¨ BIGINTæ°æ®ç±»åï¼åæ¶ä¿ç NOT NULLå±æ§ï¼è¯·æ§è¡ä»¥ä¸æä½ï¼
ALTER TABLE t1 CHANGE a b BIGINT NOT NULL;
è¦æ´æ¹åå®ä¹ä½ä¸æ´æ¹å ¶å称ï¼è¯·ä½¿ç¨ CHANGEæMODIFYãå¯¹äº CHANGEï¼è¯æ³éè¦ä¸¤ä¸ªåå称ï¼å æ¤æ¨å¿ é¡»æå®ç¸åçå称两次æè½ä¿æå称ä¸åãä¾å¦ï¼è¦æ´æ¹ column çå®ä¹ bï¼è¯·æ§è¡ä»¥ä¸æä½ï¼
ALTER TABLE t1 CHANGE b b INT NOT NULL;
MODIFYæ´æ¹å®ä¹èä¸æ´æ¹å称æ´æ¹ä¾¿ï¼å 为å®åªéè¦ä¸æ¬¡ååï¼
ALTER TABLE t1 MODIFY b INT NOT NULL;
è¦æ´æ¹åå称ä½ä¸æ´æ¹å ¶å®ä¹ï¼è¯·ä½¿ç¨ CHANGEæRENAME COLUMNã对äºCHANGEï¼è¯æ³éè¦åå®ä¹ï¼å æ¤è¦ä¿æå®ä¹ä¸åï¼æ¨å¿ é¡»éæ°æå®åå½åå ·æçå®ä¹ãä¾å¦ï¼è¦å°INT NOT NULLåä» é å½åb为aï¼è¯·æ§è¡ä»¥ä¸æä½ï¼
ALTER TABLE t1 CHANGE b a INT NOT NULL;
RENAME COLUMNæ´æ¹å称èä¸æ´æ¹å®ä¹æ´æ¹ä¾¿ï¼å 为å®åªéè¦æ§å称åæ°å称ï¼
ALTER TABLE t1 RENAME COLUMN b TO a;
é常ï¼æ¨ä¸è½å°åéå½å为表ä¸å·²åå¨çå称ãç¶èï¼ææ¶æ åµå¹¶éå¦æ¤ï¼ä¾å¦å½æ¨äº¤æ¢å称æå¨å¾ªç¯ä¸ç§»å¨å®ä»¬æ¶ãå¦æè¡¨å ·æå为aãbå ç åcï¼åè¿äºæ¯ææçæä½ï¼
-- swap a and b
ALTER TABLE t1 RENAME COLUMN a TO b,
RENAME COLUMN b TO a;
-- "rotate" a, b, c through a cycle
ALTER TABLE t1 RENAME COLUMN a TO b,
RENAME COLUMN b TO c,
RENAME COLUMN c TO a;
CHANGE对äºä½¿ç¨æ è¿è¡çåå®ä¹æ´æ¹MODIFYï¼å®ä¹å¿ é¡»å æ¬æ°æ®ç±»å以ååºåºç¨äºæ°åçææå±æ§ï¼ç´¢å¼å±æ§ï¼ä¾å¦PRIMARY KEYæ ï¼ é¤å¤UNIQUEï¼ãåå§å®ä¹ä¸åå¨ä½æªä¸ºæ°å®ä¹æå®çå±æ§ä¸ä¼è¢«ä¿çãå设åcol1å®ä¹ä¸ºï¼INT UNSIGNED DEFAULT 1 COMMENT 'my column'并ä¸æ¨æå¦ä¸æ¹å¼ä¿®æ¹è¯¥åï¼æç®ä» æ´æ¹INT为 BIGINTï¼
ALTER TABLE t1 MODIFY col1 BIGINT;
该è¯å¥å°æ°æ®ç±»åä» æ´æ¹ä¸ºINT ï¼BIGINTä½ä¹å é¤äº UNSIGNEDãDEFAULTå COMMENTå±æ§ãè¦ä¿çå®ä»¬ï¼è¯å¥å¿ é¡»æç¡®å å«å®ä»¬ï¼
ALTER TABLE t1 MODIFY col1 BIGINT UNSIGNED DEFAULT 1 COMMENT 'my column';
主é®åç´¢å¼ å建主é®ï¼
ALTER TABLE table_name ADD PRIMARY KEY (column_name);
å建æ®éç´¢å¼ï¼
ALTER TABLE table_name ADD INDEX index_name (column1 [ASC|DESC], column2 [ASC|DESC], ...);
å建å¯ä¸ç´¢å¼ï¼
ALTER TABLE table_name ADD UNIQUE INDEX index_name (column1 [ASC|DESC], column2 [ASC|DESC], ...);
å é¤ç´¢å¼ï¼
ALTER TABLE table_name DROP INDEX index_name;
å¤é®åå ¶ä»çº¦æ FOREIGN KEY åå¥ç±ååå¨å¼æREFERENCESæ¯æ ï¼âå¤é®çº¦æâã对äºå ¶ä»åå¨å¼æï¼è¿äºåå¥ä¼è¢«è§£æä½è¢«å¿½ç¥ã InnoDB NDB ADD [CONSTRAINT [symbol]] FOREIGN KEY [index_name] (...) REFERENCES ... (...) ä¸ ä¸åçæ¯ALTER TABLEï¼å¯¹äº CREATE TABLEï¼å¦æç»å®åADD FOREIGN KEY忽ç¥_index_name_并使ç¨èªå¨çæçå¤é®å称ãä½ä¸ºè§£å³æ¹æ³ï¼è¯·å å«CONSTRAINTç¨äºæå®å¤é®å称çåå¥ï¼
ADD CONSTRAINT name FOREIGN KEY (....) ...
æ·»å å¤é®ï¼
ALTER TABLE table_name ADD FOREIGN KEY (column_name) REFERENCES referenced_table(ref_column_name);
æ·»å å¯ä¸çº¦æï¼
ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE (column_name);
MySQL é»é»å°å¿½ç¥å èREFERENCES è§èï¼å ¶ä¸å¼ç¨è¢«å®ä¹ä¸ºåè§èçä¸é¨åãMySQL åªæ¥å REFERENCESä½ä¸ºåç¬FOREIGN KEYè§èçä¸é¨åå®ä¹çåå¥ã ååºInnoDB表ä¸æ¯æå¤é®ãæ¤éå¶ä¸éç¨äº NDB表ï¼å æ¬é£äºç±[LINEAR] KEY. æå ³æ´å¤ä¿¡æ¯ï¼è¯·åé 第 24.6.2 è âä¸åå¨å¼æç¸å ³çååºéå¶âã MySQL Server å NDB Cluster é½æ¯æä½¿ç¨ ALTER TABLEå é¤å¤é®ï¼
ALTER TABLE tbl_name DROP FOREIGN KEY fk_symbol;
ALTER TABLEæ¯æ å¨åä¸è¯å¥ä¸æ·»å åå é¤å¤é®ï¼ ALTER TABLE ... ALGORITHM=INPLACEä½ä¸æ¯æ ALTER TABLE ... ALGORITHM=COPYã æå¡å¨ç¦æ¢å¯¹å¯è½å¯¼è´å¼ç¨å®æ´æ§ä¸¢å¤±çå¤é®åè¿è¡æ´æ¹ãALTER TABLE ... DROP FOREIGN KEY解å³æ¹æ³æ¯å¨æ´æ¹åå®ä¹ä¹ååä¹å使ç¨ALTER TABLE ... ADD FOREIGN KEYãç¦æ¢æ´æ¹ç示ä¾å æ¬ï¼
- 对å¯è½ä¸å®å ¨çå¤é®åçæ°æ®ç±»åçæ´æ¹ãä¾å¦ï¼å 许æ´æ¹ VARCHAR(20)为 VARCHAR(30)ï¼ä½æ´æ¹ä¸ºVARCHAR(1024)并ä¸æ¯å 为è¿ä¼æ¹ååå¨å个å¼æéçé¿åº¦åèæ°ã
- NULLç¦æ¢å°å æ´æ¹NOT NULL为éä¸¥æ ¼æ¨¡å¼ï¼ä»¥é²æ¢å°NULLå¼è½¬æ¢ä¸ºé»è®¤éNULLå¼ï¼å 为å¨å¼ç¨ç表ä¸æ²¡æ对åºçå¼ãå¨ä¸¥æ ¼æ¨¡å¼ä¸å 许该æä½ï¼ä½å¦æéè¦ä»»ä½æ¤ç±»è½¬æ¢ï¼åä¼è¿åé误ã
SQLlite
SQLite æ¯æ ALTER TABLE çæéåéãSQLite ä¸ç ALTER TABLE å½ä»¤å
许对ç°æ表è¿è¡è¿äºæ´æ¹ï¼å®å¯ä»¥éå½åï¼å¯ä»¥éå½ååï¼å¯ä»¥åå
¶ä¸æ·»å ä¸åï¼æè
å¯ä»¥ä»ä¸å é¤ä¸åã
å¨ SQLite ä¸ï¼é¤äºéå½å表åå¨å·²æç表ä¸æ·»å åï¼ALTER TABLE å½ä»¤ä¸æ¯æå
¶ä»æä½ã
éå½å
ç¨æ¥éå½åå·²æç表ç ALTER TABLE çåºæ¬è¯æ³å¦ä¸ï¼
ALTER TABLE database_name.table_name RENAME TO new_table_name;
æ°å¢å ç¨æ¥å¨å·²æç表ä¸æ·»å ä¸ä¸ªæ°çåç ALTER TABLE çåºæ¬è¯æ³å¦ä¸ï¼
ALTER TABLE database_name.table_name ADD COLUMN column_def...;
æµè¯åç° sqlite3 并ä¸æ¯æç´æ¥æ·»å 带æ unique 约æçåï¼
sqlite> alter table company add department text unique; Error: Cannot add a UNIQUE column
å¯ä»¥å ç´æ¥æ·»å ä¸åæ°æ®ï¼ç¶ååæ·»å unique ç´¢å¼ãå ¶å®å¨å»ºè¡¨çæ¶åå¦æåæ unique 约æï¼éè¿æ¥è¯¢ç³»ç»è¡¨ SQLITE_MASTER å¯ä»¥çå°ï¼ä¼èªå¨å建ç¸åºçç´¢å¼ã SQLITE_TEMP_MASTER è· SQLITE_MASTER æ¯ sqlite çç³»ç»è¡¨ï¼SQLITE_TEMP_MASTER 表åå¨ä¸´æ¶è¡¨æå ³çææå 容ã å é¤å DROP COLUMN è¯æ³ç¨äºä»è¡¨ä¸å é¤ç°æåãDROP COLUMN å½ä»¤ä»è¡¨ä¸å é¤å½ååï¼å¹¶éåå ¶å å®¹ä»¥æ¸ é¤ä¸è¯¥åå ³èçæ°æ®ãDROP COLUMN å½ä»¤ä» å¨è¯¥åæªè¢«æ¨¡å¼çä»»ä½å ¶ä»é¨åå¼ç¨å¹¶ä¸ä¸æ¯ PRIMARY KEY ä¸æ²¡æ UNIQUE 约ææ¶æææ
ALTER TABLE table_name DROP COLUMN column_name;
éæ°å®ä¹ãå¤é®åå ¶ä»çº¦æ 请注æï¼ä¸æ¯ææçALTERæä½é½å¨SQLiteä¸é½æ¯æ¯æçï¼ä¾å¦ä¿®æ¹åç约æçæä½ãSQLiteçALTERè¯å¥çåè½ç¸å¯¹æéãå¦æéè¦æ´å¤æç表ç»æä¿®æ¹ï¼å¯è½éè¦ä½¿ç¨å ¶ä»æ¹æ³ï¼ä¾å¦å建æ°è¡¨å¹¶å°æ°æ®è¿ç§»è³æ°è¡¨ã
å ·ä½ä»£ç
type AlterExpress interface {
alterExpr()
}
type Constraint struct {
name string
col Column
References any
}
func (c Column) ForeignKey(references any) Constraint {
return Constraint{
name: "FOREIGN KEY",
col: c,
References: references,
}
}
func (_ Constraint) alterExpr() {}
type IndexExpress struct {
name string
colNames []string
isPrimaryKey bool
isUnique bool
}
func (_ IndexExpress) alterExpr() {}
func Unique(name string, cols ...string) IndexExpress {
return IndexExpress{
isUnique: true,
name: name,
colNames: cols,
}
}
func Primary(col string) IndexExpress {
return IndexExpress{
isPrimaryKey: true,
colNames: []string{col},
}
}
func Index(name string, cols ...string) IndexExpress {
return IndexExpress{
name: name,
colNames: cols,
}
}
type ColumnDefinition struct {
Size int
NotNull bool
Comment string
DefaultValue string
ColType string
}
func (_ ColumnDefinition) alterExpr() {}
func (c Column) Definition(val ColumnDefinition) Column {
return Column{
table: c.table,
definition: val,
name: c.name,
alias: c.alias,
}
}
// Column represents column
// it could have alias
// in general, we use it in two ways
// 1. specify the column in query
// 2. it's the start point of building complex expression
type Column struct {
table TableReference
definition ColumnDefinition
oldName string
name string
alias string
}
func (c Column) Rename(name string) Column {
return Column{
name: name,
oldName: c.name,
table: c.table,
alias: c.alias,
definition: c.definition,
}
}
package eorm
type AlterOption struct { // RENAME,DROP,ADD,MODIFY,CHANGE
Op string
//col Column
expr AlterExpress
}
func Add(expr AlterExpress) AlterOption {
return AlterOption{
Op: "ADD",
expr: expr,
}
}
func Drop(expr AlterExpress) AlterOption {
return AlterOption{
Op: "DROP",
expr: expr,
}
}
func Change(expr AlterExpress) AlterOption {
return AlterOption{
Op: "CHANGE",
expr: expr,
}
}
func Modify(expr AlterExpress) AlterOption {
return AlterOption{
Op: "MODIFY",
expr: expr,
}
}
var _ QueryBuilder = &Alter[any]{}
type Alter[T any] struct {
builder
Session
table interface{}
ops []AlterOption
}
func NewAlter[T any](sess Session) *Alter[T] {
return &Alter[T]{
builder: builder{
core: sess.getCore(),
buffer: bytebufferpool.Get(),
},
Session: sess,
}
}
func (d *Alter[T]) Table(tbl TableReference) *Alter[T] {
d.table = tbl
return d
}
func (d *Alter[T]) AddColumn(name string) *Alter[T] {
d.ops = append(d.ops, Add(Column{name: name}))
return d
}
func (d *Alter[T]) Build() (Query, error) {
return Query{}, nil
}
func (d *Alter[T]) Get(ctx context.Context) (*T, error) {
query, err := d.Build()
if err != nil {
return nil, err
}
return newQuerier[T](d.Session, query, d.meta, ALTER).Get(ctx)
}
对 DDL å°è£
æä¾ä¸ä¸ª Migrator çæ¥å£ï¼ 该æ¥å£ç»è£ äº CreaterãDropãAlter çDDLæ¥å£ï¼é¤æ¤ä¹å¤å¯¹äºè¡¨è¿ç§»çæ¥å£è¿éè¦å°ä¸è¿°æ¥å£ççé¢ç²åº¦ç»å为ï¼ä¿®æ¹è¡¨åãå¾å°è¡¨ç±»åã ä¿®æ¹åç±»åãä¿®æ¹ååãä¿®æ¹ç´¢å¼çæ¥å£ã 为äºæ»¡è¶³ORMè¿ç§»å·¥å ·ç±»çéæ±ï¼ä¸ä¸ªMigratoræ¥å£é常éè¦å ·å¤ä»¥ä¸æ¹æ³ï¼
- CreateTable(table interface{}) errorï¼å建æ°è¡¨çæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象ï¼å¹¶å¨æ°æ®åºä¸å建对åºç表ã
- DropTable(table interface{}) errorï¼å é¤è¡¨çæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象ï¼å¹¶å¨æ°æ®åºä¸å é¤å¯¹åºç表ã
- AlterTable(table interface{}) errorï¼ä¿®æ¹è¡¨ç»æçæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象ï¼å¹¶æ ¹æ®å¯¹è±¡çå®ä¹ï¼å¨æ°æ®åºä¸ä¿®æ¹è¡¨ç»æã
- AddColumn(table interface{}, columnName string, columnType string) errorï¼æ·»å åçæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象ãåå称ååç±»åï¼å¹¶å¨æ°æ®åºä¸ç表ä¸æ·»å 对åºçåã
- DropColumn(table interface{}, columnName string) errorï¼å é¤åçæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象åè¦å é¤çåå称ï¼å¹¶ä»æ°æ®åºç表ä¸å é¤å¯¹åºçåã
- ModifyColumn(table interface{}, columnName string, newColumnType string) errorï¼ä¿®æ¹åç±»åçæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象ãè¦ä¿®æ¹çåå称åæ°çåç±»åï¼å¹¶å¨æ°æ®åºä¸ä¿®æ¹å¯¹åºçåç±»åã
- RenameTable(oldTableName string, newTableName string) errorï¼éå½å表çæ¹æ³ï¼ä¼ å ¥æ§ç表ååæ°ç表åï¼å¹¶å¨æ°æ®åºä¸ä¿®æ¹å¯¹åºç表åã
- AddIndex(table interface{}, indexName string, columnNames ...string) errorï¼æ·»å ç´¢å¼çæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象ãç´¢å¼å称åè¦æ·»å ç´¢å¼çåå称ï¼ä¸ºæ°æ®åºä¸ç表添å 对åºçç´¢å¼ã
- DropIndex(table interface{}, indexName string) errorï¼å é¤ç´¢å¼çæ¹æ³ï¼ä¼ å ¥ä¸ä¸ªä»£è¡¨è¡¨ç»æç对象åè¦å é¤çç´¢å¼å称ï¼å¹¶ä»æ°æ®åºç表ä¸å é¤å¯¹åºçç´¢å¼ã
- HasTable(tableName string) boolï¼æ£æ¥è¡¨æ¯å¦åå¨çæ¹æ³ï¼ä¼ å ¥è¡¨åï¼å¤ææ°æ®åºä¸æ¯å¦åå¨å¯¹åºç表ã
- HasColumn(tableName string, columnName string) boolï¼æ£æ¥åæ¯å¦åå¨çæ¹æ³ï¼ä¼ å ¥è¡¨ååååï¼å¤ææ°æ®åºç表ä¸æ¯å¦åå¨å¯¹åºçåã
ä¸è¬æ åµåæ°tableåºè¯¥æ¯ä¸ä¸ªç»æä½ç±»åï¼èä¸æ¯ä¸ä¸ªæ¥å£ãè¿æ¯å 为æ们å¨å建表æ¶éè¦æç¡®æå®è¡¨çç»æåå段信æ¯ï¼èç»æä½è½å¤æä¾è¿äºä¿¡æ¯çå®ä¹åæè¿°ã éè¿ä¼ éä¸ä¸ªä»£è¡¨è¡¨ç»æçç»æä½ä½ä¸ºåæ°ï¼Migratoræ¥å£å¯ä»¥æ ¹æ®ç»æä½çå®ä¹ï¼å¨æ°æ®åºä¸å建ç¸åºç表ãç»æä½å¯ä»¥çµæ´»å°å®ä¹è¡¨çå段ãç±»åã约æåå ¶ä»å æ°æ®ä¿¡æ¯ï¼è¿æ ·Migratorå®ç°å¯ä»¥æ´åç¡®å°çæç¸åºçSQLè¯å¥æ¥å建表ã èæ¥å£é常ç¨äºå®ä¹ä¸ç»æ¹æ³ï¼èä¸ä¼æä¾å ·ä½çç»æä¿¡æ¯ãå æ¤ï¼å¨å建表æ¶ï¼ä½¿ç¨ç»æä½ä½ä¸ºåæ°æ´è½æ»¡è¶³éæ±ãä¾å¦Migratoræ¥å£çCreateTableæ¹æ³ï¼åºè¯¥å°ä¸ä¸ªä»£è¡¨è¡¨ç»æçç»æä½ä½ä¸ºåæ°ä¼ éè¿å»ã对äºå ·ä½çORMåºåæ°æ®åºç±»åã éè¿ä½¿ç¨æ¥å£ï¼å¯ä»¥å®ç°æ´é«å±æ¬¡çæ½è±¡åçµæ´»æ§ãå¯ä»¥å®ä¹ä¸ä¸ªéç¨ç表ç»ææ¥å£ï¼ç¶åé对ä¸åçæ°æ®åºåORMåºï¼å®ç°å ·ä½ç表ç»æç»æä½ã 综ä¸æè¿°ï¼ä½¿ç¨æ¥å£æ¥å®ä¹è¡¨ç»ææ¯ä¸ç§å¸¸è§ä¸ææçåæ³ï¼å¯ä»¥æä¾æ´å¤§ççµæ´»æ§åå¯æ©å±æ§ã è¿äºæ¹æ³å¯ä»¥æä¾åºæ¬ç表ååæä½ï¼ä»è满足 ORM è¿ç§»å·¥å ·ç±»çéæ±ãå ·ä½çå®ç°æ¹å¼å¯è½å å ·ä½çORM åºåæ°æ®åºç±»åèææå·®å¼ã å¨å®ç° Migrator** **æ¥å£æ¶ï¼éè¦æ ¹æ®å ·ä½çéæ±å使ç¨ç ORM åºè¿è¡ç¸åºçè°æ´åæ©å±ã Migrator å¯ç¨æ¥ä¸º ORM Schemas å®ç°èªå®ä¹çè¿ç§»é»è¾ãMigrator è¿ä¸ºä¸åç±»åçæ°æ®åºæä¾äºç»ä¸ç API æ½è±¡ï¼ä¾å¦ï¼SQLite ä¸æ¯æ ALTER COLUMNãDROP COLUMN ç SQL åå¥ï¼æ以å½æ们è°ç¨ Migrator API è¯å¾ä¿®æ¹è¡¨ç»ææ¶ï¼ä¼èªå®ä¸ºå¨ SQLite å建ä¸å¼ æ°è¡¨ã并å¤å¶æææ°æ®ï¼ç¶åå é¤æ§è¡¨ãéå½åæ°è¡¨ã
type Migrator interface {
// Run
Run(dst ...interface{}) error
// Database
CurrentDatabase() (name string, err error)
FullColumnTypeOf(meta *model.ColumnMeta) string
GetTypeAliases(databaseTypeName string) []string
// Tables
CreateTable(dst ...interface{}) error
DropTable(dst ...interface{}) error
HasTable(model *model.TableMeta) (bool, error)
RenameTable(oldName, newName interface{}) error
GetTables() (tableList []string, err error)
TableType(dst interface{}) (TableType, error)
// Columns
AddColumn(dst interface{}, field string) error
DropColumn(dst interface{}, field string) error
AlterColumn(dst interface{}, field string) error
MigrateColumn(dst interface{}, meta *model.ColumnMeta, columnType ColumnType) error
HasColumn(dst interface{}, field string) bool
RenameColumn(dst interface{}, oldName, field string) error
ColumnTypes(dst interface{}) ([]ColumnType, error)
// Indexes
CreateIndex(dst interface{}, name string) error
DropIndex(dst interface{}, name string) error
HasIndex(dst interface{}, name string) (bool, error)
RenameIndex(dst interface{}, newName string) error
GetIndexes(dst interface{}) ([]ColumnIndex, error)
}
type ColumnIndex interface {
Table() string
Name() string
Columns() []string
PrimaryKey() (isPrimaryKey bool, ok bool)
Unique() (unique bool, ok bool)
Option() string
}
type TableType interface {
Schema() string
Name() string
Type() string
Comment() (comment string, ok bool)
}
é£ä¹ Migrate çå®ç°è¿é就主è¦åègormçå®ç°ã
type AutoMigrator struct {
core
db *DB
}
func NewAutoMigrator(db *DB) migrator.Migrator {
return &AutoMigrator{
db: db,
core: db.getCore(),
}
}
å建ä¸ä¸ª AutoMigrate ç»æä½ï¼è¯¥ç»æä½å®ç°Migratoræ¥å£ï¼æ¥æ¶ä¸ä¸ªæ°æ®åºè¿æ¥åè¦è¿ç§»ç模å对象ä½ä¸ºåæ°ã
Run æ¹æ³
Run æ¹æ³ä¸º Migrator ææ ¸å¿çæ¹æ³ï¼ä¹å°±æ¯è´è´£æ¨¡åç±»è¿ç§»å¯å¨çæ¹æ³
// Run auto migrate values
func (m *AutoMigrator) Run(values ...any) error {
metaRegistry := m.core.metaRegistry
for _, value := range values {
tableMeta, err := metaRegistry.ParseTableMeta(value)
if err != nil {
return err
}
hasTable, err := m.HasTable(tableMeta)
if err != nil {
return err
}
if !hasTable {
if err = m.CreateTable(value); err != nil {
return err
}
} else {
var columnTypes []ColumnType
columnTypes, err = m.ColumnTypes(value)
if err != nil {
return err
}
for columnName, columnMeta := range tableMeta.ColumnMap {
var foundColumn ColumnType
for _, columnType := range columnTypes {
if columnType.Name() == columnName {
foundColumn = columnType
break
}
if foundColumn == nil {
if err = m.AddColumn(value, columnName); err != nil {
return err
}
} else {
if err = m.MigrateColumn(value, columnMeta, foundColumn); err != nil {
return err
}
}
}
for _, idx := range columnMeta.ColumnIndexes {
hasIndex, err := m.HasIndex(value, idx.Name)
if err != nil {
return err
}
if !hasIndex {
if err = m.CreateIndex(value, idx.Name); err != nil {
return err
}
} else {
if err = m.MigrateIndex(value, columnMeta, idx.Name); err != nil {
return err
}
}
}
}
}
}
return nil
}
func (m *AutoMigrator) HasTable(model *model.TableMeta) bool {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) MigrateColumn(dst interface{}, columnMeta *model.ColumnMeta, columnType ColumnType) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) AddColumn(dst any, field string) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) DropColumn(dst any, field string) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) AlterColumn(dst any, field string) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) CreateIndex(dst any, name string) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) DropIndex(dst interface{}, name string) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) CurrentDatabase() (name string, err error) {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) CreateTable(dst ...interface{}) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) ColumnTypes(dst interface{}) ([]ColumnType, error) {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) FullDataTypeOf(field *model.ColumnMeta) string {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) DataTypeOf(field *model.ColumnMeta) string {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) GetTypeAliases(databaseTypeName string) []string {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) DropTable(dst ...interface{}) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) RenameTable(oldName, newName interface{}) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) GetTables() (tableList []string, err error) {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) TableType(dst interface{}) (TableType, error) {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) HasColumn(dst interface{}, field string) bool {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) RenameColumn(dst interface{}, oldName, field string) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) HasIndex(dst interface{}, name string) bool {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) RenameIndex(dst interface{}, oldName, newName string) error {
// TODO implement me
panic("implement me")
}
func (m *AutoMigrator) GetIndexes(dst interface{}) ([]ColumnIndex, error) {
// TODO implement me
panic("implement me")
}
HasTable æ¹æ³
HasTable å¤æ表æ¯å¦åå¨ï¼è¿åå¸å°å¼ãå®ç°çæ ¸å¿æ¯éè¿sqlå»æ¥è¯¢ç®æ 表æ¯å¦åå¨äºå½åè¿æ¥çæ°æ®åºä¸ã è·å¾å½åè¿æ¥çæ°æ®åºå称ï¼
func (m *AutoMigrator) CurrentDatabase() (name string, err error) {
res, err := RawQuery[string](
m.db, "SELECT DATABASE()").Get(context.Background())
return *res, err
}
è¿è¡raw sql æ¼æ¥ï¼æ¥è¯¢ç®æ 表æ¯å¦åå¨ï¼
// HasTable returns table exists or not for value, value could be a struct or string
func (m *AutoMigrator) HasTable(model *model.TableMeta) bool {
query := "SELECT count(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = ? AND table_type = ?"
currentDatabase, err := m.CurrentDatabase()
if err != nil {
panic(err)
}
res, err := RawQuery[int64](
m.db, query, currentDatabase, model.TableName, "BASE TABLE").Get(context.Background())
if err != nil {
panic(err)
}
return *res > 0
}
CreateTable æ¹æ³
åºäº builder å建æ°æ®åºè¡¨ï¼
func (m *AutoMigrator) CreateTable(dst ...interface{}) error {
_, err := NewCreator[int](m.db).Table(TableOf(dst, "")).Get(context.Background())
return err
}
ColumnTypes æ¹æ³
è·å表ç»æçå段信æ¯ï¼ å¨ç®æ 表åå 就已ç»å»ºç«çæ åµä¸ï¼æ¥æ¾ç®åå æ°æ®æ¨¡åçåï¼ å°è¿ç§»å¨ä½å为äºä¸¤ä¸ªåæ¯ï¼ ï¼
- åç°æ°å段ï¼æ·»å æ°å段ï¼
- æªåç°æ°å段ï¼æ£æ¥æ§å段ä¸æ¯å¦æ被修æ¹çå±æ§ï¼å¦ææ就对表é该å段å±æ§è¿è¡ä¿®æ¹ï¼
以ä¸ä¸¤ä¸ªåæ¯éè¦å½å表ç表ç»æå段信æ¯ï¼ä¸ºäºåææ°è§£æçå æ°æ®æ¨¡åçä¿¡æ¯å对æ¯ã æ°å¢ ColumnType ï¼ä¸ºå段信æ¯çæ½è±¡ï¼
type ColumnType interface {
Name() string
DatabaseTypeName() string // varchar
ColumnType() (columnType string, ok bool) // varchar(64)
PrimaryKey() (isPrimaryKey bool, ok bool)
AutoIncrement() (isAutoIncrement bool, ok bool)
Length() (length int64, ok bool)
DecimalSize() (precision int64, scale int64, ok bool)
Nullable() (nullable bool, ok bool)
Unique() (unique bool, ok bool)
ScanType() reflect.Type
Comment() (value string, ok bool)
DefaultValue() (value string, ok bool)
}
å®ç° ColumnTypeï¼ç¨äºæ¥æ¶ï¼ sql/database ç表ç»æå段çä¿¡æ¯ã
type ColumnType struct {
SQLColumnType *sql.ColumnType
NameValue sql.NullString
DataTypeValue sql.NullString
ColumnTypeValue sql.NullString
PrimaryKeyValue sql.NullBool
UniqueValue sql.NullBool
AutoIncrementValue sql.NullBool
LengthValue sql.NullInt64
DecimalSizeValue sql.NullInt64
ScaleValue sql.NullInt64
NullableValue sql.NullBool
ScanTypeValue reflect.Type
CommentValue sql.NullString
DefaultValueValue sql.NullString
}
func (ct ColumnType) Name() string {
if ct.NameValue.Valid {
return ct.NameValue.String
}
return ct.SQLColumnType.Name()
}
func (ct ColumnType) DatabaseTypeName() string {
if ct.DataTypeValue.Valid {
return ct.DataTypeValue.String
}
return ct.SQLColumnType.DatabaseTypeName()
}
func (ct ColumnType) ColumnType() (columnType string, ok bool) {
return ct.ColumnTypeValue.String, ct.ColumnTypeValue.Valid
}
func (ct ColumnType) PrimaryKey() (isPrimaryKey bool, ok bool) {
return ct.PrimaryKeyValue.Bool, ct.PrimaryKeyValue.Valid
}
func (ct ColumnType) AutoIncrement() (isAutoIncrement bool, ok bool) {
return ct.AutoIncrementValue.Bool, ct.AutoIncrementValue.Valid
}
func (ct ColumnType) Length() (length int64, ok bool) {
if ct.LengthValue.Valid {
return ct.LengthValue.Int64, true
}
return ct.SQLColumnType.Length()
}
func (ct ColumnType) DecimalSize() (precision int64, scale int64, ok bool) {
if ct.DecimalSizeValue.Valid {
return ct.DecimalSizeValue.Int64, ct.ScaleValue.Int64, true
}
return ct.SQLColumnType.DecimalSize()
}
func (ct ColumnType) Nullable() (nullable bool, ok bool) {
if ct.NullableValue.Valid {
return ct.NullableValue.Bool, true
}
return ct.SQLColumnType.Nullable()
}
func (ct ColumnType) Unique() (unique bool, ok bool) {
return ct.UniqueValue.Bool, ct.UniqueValue.Valid
}
func (ct ColumnType) ScanType() reflect.Type {
if ct.ScanTypeValue != nil {
return ct.ScanTypeValue
}
return ct.SQLColumnType.ScanType()
}
func (ct ColumnType) Comment() (value string, ok bool) {
return ct.CommentValue.String, ct.CommentValue.Valid
}
func (ct ColumnType) DefaultValue() (value string, ok bool) {
return ct.DefaultValueValue.String, ct.DefaultValueValue.Valid
}
å®ç° ColumnTypes æ¹æ³ï¼
func (m *AutoMigrator) ColumnTypes(dst interface{}) ([]ColumnType, error) {
columnTypes := make([]ColumnType, 0)
query, err := NewSelector[dst](m.db).Build()
if err != nil {
return nil, err
}
rows, err := m.db.queryContext(context.Background(), query)
if err != nil {
return nil, err
}
defer func() {
err = rows.Close()
}()
var rawColumnTypes []*sql.ColumnType
rawColumnTypes, err = rows.ColumnTypes()
if err != nil {
return nil, err
}
for _, c := range rawColumnTypes {
columnTypes = append(columnTypes, migrator.ColumnType{SQLColumnType: c})
}
return columnTypes, nil
}
AddColumn æ¹æ³
为å½å表添å å
func (m *AutoMigrator) AddColumn(dst any, colName string) error {
_, err := NewAlter[int](m.db).Table(TableOf(dst, "")).AddColumn(colName).Get(context.Background())
return err
}
MigrateColumn æ¹æ³
å°ç®æ åè¿ç§»å°æ°æ®åºä¸ï¼ FullColumnTypeOf ï¼è·å模åå æ°æ®ä¸çå ¨é¨å段类åï¼ DatabaseTypeName ï¼è·åå½å表该å段å¨æ°æ®åºä¸çç±»åï¼ ç¶åç»è¿éä¸å¯¹æ¯ï¼å段çå±æ§ï¼æ¥å¤æå段æ¯å¦éè¦è¢«ä¿®æ¹ã
func (m *AutoMigrator) MigrateColumn(dst interface{}, columnMeta *model.ColumnMeta, columnType ColumnType) error {
fullDataType := strings.TrimSpace(strings.ToLower(m.FullColumnTypeOf(columnMeta)))
realDataType := strings.ToLower(columnType.DatabaseTypeName())
var (
alterColumn bool
isSameType = fullDataType == realDataType
)
if !columnMeta.IsPrimaryKey {
// check type
if !strings.HasPrefix(fullDataType, realDataType) {
// check type aliases
aliases := m.dialect.GetTypeAliases(realDataType)
for _, alias := range aliases {
if strings.HasPrefix(fullDataType, alias) {
isSameType = true
break
}
}
if !isSameType {
alterColumn = true
}
}
}
if !isSameType {
// check size
if length, ok := columnType.Length(); length != int64(columnMeta.Size) {
if length > 0 && columnMeta.Size > 0 {
alterColumn = true
} else {
matches2 := regFullDataType.FindAllStringSubmatch(fullDataType, -1)
if !columnMeta.IsPrimaryKey && (len(matches2) == 1 && matches2[0][1] != fmt.Sprint(length) && ok) {
alterColumn = true
}
}
}
if precision, _, ok := columnType.DecimalSize(); ok && int64(columnMeta.Precision) != precision {
if regexp.MustCompile(fmt.Sprintf("[^0-9]%d[^0-9]", columnMeta.Precision)).MatchString(
m.ColumnTypeOf(columnMeta)) {
alterColumn = true
}
}
if nullable, ok := columnType.Nullable(); ok && nullable != columnMeta.IsNull {
if !columnMeta.IsPrimaryKey && nullable {
alterColumn = true
}
}
// check unique
if unique, ok := columnType.Unique(); ok && unique != columnMeta.Unique {
// not primary key
if !columnMeta.IsPrimaryKey {
alterColumn = true
}
}
if !columnMeta.IsPrimaryKey {
currentDefaultNotNull := columnMeta.HasDefaultValue && (columnMeta.DefaultValueInterface != nil || !strings.EqualFold(columnMeta.DefaultValue, "NULL"))
dv, dvNotNull := columnType.DefaultValue()
if dvNotNull && !currentDefaultNotNull {
// default value -> null
alterColumn = true
} else if !dvNotNull && currentDefaultNotNull {
// null -> default value
alterColumn = true
} else if (columnMeta.DataType != model.Time && dv != columnMeta.DefaultValue) ||
(columnMeta.DataType != model.Time && !strings.EqualFold(strings.TrimSuffix(dv, "()"), strings.TrimSuffix(columnMeta.DefaultValue, "()"))) {
if currentDefaultNotNull || dvNotNull {
alterColumn = true
}
}
}
if comment, ok := columnType.Comment(); ok && comment != columnMeta.Comment {
if !columnMeta.IsPrimaryKey {
alterColumn = true
}
}
if alterColumn && !columnMeta.IgnoreMigration {
return m.AlterColumn(dst, columnMeta.ColumnName)
}
}
return nil
}
HasIndex æ¹æ³
å¤æå¨ç´¢å¼æ¯å¦åå¨ã
func (m *AutoMigrator) HasIndex(dst interface{}, name string) (bool, error) {
res := new(int64)
err := m.RunWithValue(dst, func(meta *model.TableMeta) error {
currentDatabase, err := m.CurrentDatabase()
if err != nil {
return err
}
if idx := meta.LoadIndex(name); idx != nil {
name = idx.Name
}
res, err = RawQuery[int64](
m.db, "SELECT count(*) FROM information_schema.statistics WHERE table_schema = ? AND table_name = ? AND index_name = ?", currentDatabase, meta.TableName, name,
).Get(context.Background())
return err
})
return *res > 0, err
}
CreateIndex æ¹æ³
æ·»å ç´¢å¼ã
func (m *AutoMigrator) CreateIndex(dst any, name string) error {
var (
res = new(int64)
err error
)
err = m.RunWithValue(dst, func(meta *model.TableMeta) error {
if idx := meta.LoadIndex(name); idx != nil {
res, err = NewAlter[int64](m.db).Table(TableOf(dst, "")).AddIndex(IndexExpress{
name: idx.Name,
colNames: idx.ColumnList,
IndexClass: idx.IndexClass,
Comment: idx.Comment,
Length: idx.Length,
IndexType: idx.IndexType,
}).Get(context.Background())
return err
}
return fmt.Errorf("failed to create index with name %s", name)
})
if err != nil {
return err
}
if *res <= 0 {
return errors.New("åæ´å¤±è´¥ï¼åæ´è¡æ°ä¸º 0")
}
return nil
}
çæ¬æ§å¶
为äºå å®ç°ç®åçDDLæä½ï¼åè¿ç§»å°è£ ï¼æä¸æ¯æçæ¬æ§å¶ã
åå æµè¯
- å建模åæ å°è¡¨
- å é¤æ¨¡åæ å°è¡¨
- ä¿®æ¹å段
- æ°å¢å段
- å é¤å段
éææµè¯
- å建模åæ å°è¡¨
- å é¤æ¨¡åæ å°è¡¨
- ä¿®æ¹å段
- æ°å¢å段
- å é¤å段
package migrator
import (
"testing"
)
func TestMigrator_IntegrationTest_Run(t *testing.T) {
db, err := single.OpenDB(s.driver, s.dsn)
if err != nil {
t.Fatal(err)
}
if err = db.Wait(); err != nil {
t.Fatal(err)
}
orm, err := eorm.Open(s.driver, db)
if err != nil {
t.Fatal(err)
}
migrator := NewMigrator(orm)
// Define the table schema
tableSchema := []Column{
{Name: "id", Type: "int", Constraints: "PRIMARY KEY"},
{Name: "name", Type: "varchar(255)", Constraints: "NOT NULL"},
}
err := migrator.Run(&User{}, &Profile{}, &Post{}, &Role{})
if err != nil {
t.Errorf("Error creating table: %v", err)
}
// Optionally, perform assertions to verify the table creation
// ...
}
这个功能的实现要分成两个部分:
- 一个是纯粹的 DDL 语句生成,也就是对应到我们的各种 sql Builder。
- 第二个是利用第一个部分,来生成对应的 DDL。
在这两个的基础上,再考虑执行对应的 DDL。
举个例子来说:
- 用户可以完全手动的构造 DDL,然后他自己去调度执行。
- 你可以根据模型来直接生成 creat table 的语句
- 你也可以根据模型和数据库中对应的表结构来生成 DDL 语句
而执行 DDL 本身如果要做成命令行工具,我的建议是拆出去作为一个单独仓库。
此外,还有一个很关键的问题,就是用户怎么指定我 GO 某个类型对应到什么的数据库类型?
再进一步考虑,用户能不能在测试环境的时候使用 sqlite,生成一种 DDL?在生产环境是 mysql,又是另外一种 DDL?
方案选型按gorm 还是 goalng-migrate 呢?如果要做成命令行要单独拆出去或者考虑封装 goalng-migrate ??如果按照 gorm,那就设计一个 migrate 的接口,去封装 DDL 的生成就可以了!!
成年人做啥选择,我都要!意思就是,用户可以自己在代码里面调用一下,也可以用命令行执行一下。暂时都放在 eorm 吧
可以的,非常赞!可以搞起来!