fix: failed to migrate
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
.idea
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
var errCodes = map[string]error{
|
||||
"23505": gorm.ErrDuplicatedKey,
|
||||
"23503": gorm.ErrForeignKeyViolated,
|
||||
"42703": gorm.ErrInvalidField,
|
||||
}
|
||||
|
||||
type ErrMessage struct {
|
||||
Code string
|
||||
Severity string
|
||||
Message string
|
||||
}
|
||||
|
||||
// Translate it will translate the error to native gorm errors.
|
||||
// Since currently gorm supporting both pgx and pg drivers, only checking for pgx PgError types is not enough for translating errors, so we have additional error json marshal fallback.
|
||||
func (dialector Dialector) Translate(err error) error {
|
||||
if pgErr, ok := err.(*pgconn.PgError); ok {
|
||||
if translatedErr, found := errCodes[pgErr.Code]; found {
|
||||
return translatedErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
parsedErr, marshalErr := json.Marshal(err)
|
||||
if marshalErr != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errMsg ErrMessage
|
||||
unmarshalErr := json.Unmarshal(parsedErr, &errMsg)
|
||||
if unmarshalErr != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if translatedErr, found := errCodes[errMsg.Code]; found {
|
||||
return translatedErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
+210
-165
@@ -13,44 +13,61 @@ import (
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// See https://stackoverflow.com/questions/2204058/list-columns-with-indexes-in-postgresql
|
||||
// Here are some changes:
|
||||
// - use `LEFT JOIN` instead of `CROSS JOIN`
|
||||
// - exclude indexes used to support constraints (they are auto-generated)
|
||||
const indexSql = `
|
||||
select
|
||||
t.relname as table_name,
|
||||
i.relname as index_name,
|
||||
a.attname as column_name,
|
||||
ix.indisunique as non_unique,
|
||||
ix.indisprimary as primary
|
||||
from
|
||||
pg_class t,
|
||||
pg_class i,
|
||||
pg_index ix,
|
||||
pg_attribute a
|
||||
where
|
||||
t.oid = ix.indrelid
|
||||
and i.oid = ix.indexrelid
|
||||
and a.attrelid = t.oid
|
||||
and a.attnum = ANY(ix.indkey)
|
||||
and t.relkind = 'r'
|
||||
and t.relname = ?
|
||||
SELECT
|
||||
ct.relname AS table_name,
|
||||
ci.relname AS index_name,
|
||||
i.indisunique AS non_unique,
|
||||
i.indisprimary AS primary,
|
||||
a.attname AS column_name
|
||||
FROM
|
||||
pg_index i
|
||||
LEFT JOIN pg_class ct ON ct.oid = i.indrelid
|
||||
LEFT JOIN pg_class ci ON ci.oid = i.indexrelid
|
||||
LEFT JOIN pg_attribute a ON a.attrelid = ct.oid
|
||||
LEFT JOIN pg_constraint con ON con.conindid = i.indexrelid
|
||||
WHERE
|
||||
a.attnum = ANY(i.indkey)
|
||||
AND con.oid IS NULL
|
||||
AND ct.relkind = 'r'
|
||||
AND ct.relname = ?
|
||||
`
|
||||
|
||||
var typeAliasMap = map[string][]string{
|
||||
"int2": {"smallint"},
|
||||
"int4": {"integer"},
|
||||
"int8": {"bigint"},
|
||||
"smallint": {"int2"},
|
||||
"integer": {"int4"},
|
||||
"bigint": {"int8"},
|
||||
"decimal": {"numeric"},
|
||||
"numeric": {"decimal"},
|
||||
"int2": {"smallint"},
|
||||
"int4": {"integer"},
|
||||
"int8": {"bigint"},
|
||||
"smallint": {"int2"},
|
||||
"integer": {"int4"},
|
||||
"bigint": {"int8"},
|
||||
"decimal": {"numeric"},
|
||||
"numeric": {"decimal"},
|
||||
"timestamptz": {"timestamp with time zone"},
|
||||
"timestamp with time zone": {"timestamptz"},
|
||||
"bool": {"boolean"},
|
||||
"boolean": {"bool"},
|
||||
}
|
||||
|
||||
type Migrator struct {
|
||||
migrator.Migrator
|
||||
}
|
||||
|
||||
// select querys ignore dryrun
|
||||
func (m Migrator) queryRaw(sql string, values ...interface{}) (tx *gorm.DB) {
|
||||
queryTx := m.DB
|
||||
if m.DB.DryRun {
|
||||
queryTx = m.DB.Session(&gorm.Session{})
|
||||
queryTx.DryRun = false
|
||||
}
|
||||
return queryTx.Raw(sql, values...)
|
||||
}
|
||||
|
||||
func (m Migrator) CurrentDatabase() (name string) {
|
||||
m.DB.Raw("SELECT CURRENT_DATABASE()").Scan(&name)
|
||||
m.queryRaw("SELECT CURRENT_DATABASE()").Scan(&name)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -76,11 +93,13 @@ func (m Migrator) BuildIndexOptions(opts []schema.IndexOption, stmt *gorm.Statem
|
||||
func (m Migrator) HasIndex(value interface{}, name string) bool {
|
||||
var count int64
|
||||
m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
||||
name = idx.Name
|
||||
if stmt.Schema != nil {
|
||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
||||
name = idx.Name
|
||||
}
|
||||
}
|
||||
currentSchema, curTable := m.CurrentSchema(stmt, stmt.Table)
|
||||
return m.DB.Raw(
|
||||
return m.queryRaw(
|
||||
"SELECT count(*) FROM pg_indexes WHERE tablename = ? AND indexname = ? AND schemaname = ?", curTable, name, currentSchema,
|
||||
).Scan(&count).Error
|
||||
})
|
||||
@@ -90,33 +109,35 @@ func (m Migrator) HasIndex(value interface{}, name string) bool {
|
||||
|
||||
func (m Migrator) CreateIndex(value interface{}, name string) error {
|
||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
||||
opts := m.BuildIndexOptions(idx.Fields, stmt)
|
||||
values := []interface{}{clause.Column{Name: idx.Name}, m.CurrentTable(stmt), opts}
|
||||
if stmt.Schema != nil {
|
||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
||||
opts := m.BuildIndexOptions(idx.Fields, stmt)
|
||||
values := []interface{}{clause.Column{Name: idx.Name}, m.CurrentTable(stmt), opts}
|
||||
|
||||
createIndexSQL := "CREATE "
|
||||
if idx.Class != "" {
|
||||
createIndexSQL += idx.Class + " "
|
||||
createIndexSQL := "CREATE "
|
||||
if idx.Class != "" {
|
||||
createIndexSQL += idx.Class + " "
|
||||
}
|
||||
createIndexSQL += "INDEX "
|
||||
|
||||
if strings.TrimSpace(strings.ToUpper(idx.Option)) == "CONCURRENTLY" {
|
||||
createIndexSQL += "CONCURRENTLY "
|
||||
}
|
||||
|
||||
createIndexSQL += "IF NOT EXISTS ? ON ?"
|
||||
|
||||
if idx.Type != "" {
|
||||
createIndexSQL += " USING " + idx.Type + "(?)"
|
||||
} else {
|
||||
createIndexSQL += " ?"
|
||||
}
|
||||
|
||||
if idx.Where != "" {
|
||||
createIndexSQL += " WHERE " + idx.Where
|
||||
}
|
||||
|
||||
return m.DB.Exec(createIndexSQL, values...).Error
|
||||
}
|
||||
createIndexSQL += "INDEX "
|
||||
|
||||
if strings.TrimSpace(strings.ToUpper(idx.Option)) == "CONCURRENTLY" {
|
||||
createIndexSQL += "CONCURRENTLY "
|
||||
}
|
||||
|
||||
createIndexSQL += "IF NOT EXISTS ? ON ?"
|
||||
|
||||
if idx.Type != "" {
|
||||
createIndexSQL += " USING " + idx.Type + "(?)"
|
||||
} else {
|
||||
createIndexSQL += " ?"
|
||||
}
|
||||
|
||||
if idx.Where != "" {
|
||||
createIndexSQL += " WHERE " + idx.Where
|
||||
}
|
||||
|
||||
return m.DB.Exec(createIndexSQL, values...).Error
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to create index with name %v", name)
|
||||
@@ -134,8 +155,10 @@ func (m Migrator) RenameIndex(value interface{}, oldName, newName string) error
|
||||
|
||||
func (m Migrator) DropIndex(value interface{}, name string) error {
|
||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
||||
name = idx.Name
|
||||
if stmt.Schema != nil {
|
||||
if idx := stmt.Schema.LookIndex(name); idx != nil {
|
||||
name = idx.Name
|
||||
}
|
||||
}
|
||||
|
||||
return m.DB.Exec("DROP INDEX ?", clause.Column{Name: name}).Error
|
||||
@@ -144,7 +167,7 @@ func (m Migrator) DropIndex(value interface{}, name string) error {
|
||||
|
||||
func (m Migrator) GetTables() (tableList []string, err error) {
|
||||
currentSchema, _ := m.CurrentSchema(m.DB.Statement, "")
|
||||
return tableList, m.DB.Raw("SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_type = ?", currentSchema, "BASE TABLE").Scan(&tableList).Error
|
||||
return tableList, m.queryRaw("SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_type = ?", currentSchema, "BASE TABLE").Scan(&tableList).Error
|
||||
}
|
||||
|
||||
func (m Migrator) CreateTable(values ...interface{}) (err error) {
|
||||
@@ -153,13 +176,16 @@ func (m Migrator) CreateTable(values ...interface{}) (err error) {
|
||||
}
|
||||
for _, value := range m.ReorderModels(values, false) {
|
||||
if err = m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
for _, field := range stmt.Schema.FieldsByDBName {
|
||||
if field.Comment != "" {
|
||||
if err := m.DB.Exec(
|
||||
"COMMENT ON COLUMN ?.? IS ?",
|
||||
m.CurrentTable(stmt), clause.Column{Name: field.DBName}, gorm.Expr(m.Migrator.Dialector.Explain("$1", field.Comment)),
|
||||
).Error; err != nil {
|
||||
return err
|
||||
if stmt.Schema != nil {
|
||||
for _, fieldName := range stmt.Schema.DBNames {
|
||||
field := stmt.Schema.FieldsByDBName[fieldName]
|
||||
if field.Comment != "" {
|
||||
if err := m.DB.Exec(
|
||||
"COMMENT ON COLUMN ?.? IS ?",
|
||||
m.CurrentTable(stmt), clause.Column{Name: field.DBName}, gorm.Expr(m.Migrator.Dialector.Explain("$1", field.Comment)),
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +201,7 @@ func (m Migrator) HasTable(value interface{}) bool {
|
||||
var count int64
|
||||
m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
currentSchema, curTable := m.CurrentSchema(stmt, stmt.Table)
|
||||
return m.DB.Raw("SELECT count(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = ? AND table_type = ?", currentSchema, curTable, "BASE TABLE").Scan(&count).Error
|
||||
return m.queryRaw("SELECT count(*) FROM information_schema.tables WHERE table_schema = ? AND table_name = ? AND table_type = ?", currentSchema, curTable, "BASE TABLE").Scan(&count).Error
|
||||
})
|
||||
return count > 0
|
||||
}
|
||||
@@ -200,13 +226,15 @@ func (m Migrator) AddColumn(value interface{}, field string) error {
|
||||
m.resetPreparedStmts()
|
||||
|
||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
if field := stmt.Schema.LookUpField(field); field != nil {
|
||||
if field.Comment != "" {
|
||||
if err := m.DB.Exec(
|
||||
"COMMENT ON COLUMN ?.? IS ?",
|
||||
m.CurrentTable(stmt), clause.Column{Name: field.DBName}, gorm.Expr(m.Migrator.Dialector.Explain("$1", field.Comment)),
|
||||
).Error; err != nil {
|
||||
return err
|
||||
if stmt.Schema != nil {
|
||||
if field := stmt.Schema.LookUpField(field); field != nil {
|
||||
if field.Comment != "" {
|
||||
if err := m.DB.Exec(
|
||||
"COMMENT ON COLUMN ?.? IS ?",
|
||||
m.CurrentTable(stmt), clause.Column{Name: field.DBName}, gorm.Expr(m.Migrator.Dialector.Explain("$1", field.Comment)),
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,7 +253,7 @@ func (m Migrator) HasColumn(value interface{}, field string) bool {
|
||||
}
|
||||
|
||||
currentSchema, curTable := m.CurrentSchema(stmt, stmt.Table)
|
||||
return m.DB.Raw(
|
||||
return m.queryRaw(
|
||||
"SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?",
|
||||
currentSchema, curTable, name,
|
||||
).Scan(&count).Error
|
||||
@@ -250,7 +278,7 @@ func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnTy
|
||||
checkSQL += "WHERE objsubid = (SELECT ordinal_position FROM information_schema.columns WHERE table_schema = ? AND table_name = ? AND column_name = ?) "
|
||||
checkSQL += "AND objoid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = ? AND relnamespace = "
|
||||
checkSQL += "(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = ?))"
|
||||
m.DB.Raw(checkSQL, values...).Scan(&description)
|
||||
m.queryRaw(checkSQL, values...).Scan(&description)
|
||||
|
||||
comment := strings.Trim(field.Comment, "'")
|
||||
comment = strings.Trim(comment, `"`)
|
||||
@@ -269,101 +297,92 @@ func (m Migrator) MigrateColumn(value interface{}, field *schema.Field, columnTy
|
||||
// AlterColumn alter value's `field` column' type based on schema definition
|
||||
func (m Migrator) AlterColumn(value interface{}, field string) error {
|
||||
err := m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
if field := stmt.Schema.LookUpField(field); field != nil {
|
||||
var (
|
||||
columnTypes, _ = m.DB.Migrator().ColumnTypes(value)
|
||||
fieldColumnType *migrator.ColumnType
|
||||
)
|
||||
for _, columnType := range columnTypes {
|
||||
if columnType.Name() == field.DBName {
|
||||
fieldColumnType, _ = columnType.(*migrator.ColumnType)
|
||||
}
|
||||
}
|
||||
|
||||
fileType := clause.Expr{SQL: m.DataTypeOf(field)}
|
||||
// check for typeName and SQL name
|
||||
isSameType := true
|
||||
if fieldColumnType.DatabaseTypeName() != fileType.SQL {
|
||||
isSameType = false
|
||||
// if different, also check for aliases
|
||||
aliases := m.GetTypeAliases(fieldColumnType.DatabaseTypeName())
|
||||
for _, alias := range aliases {
|
||||
if strings.HasPrefix(fileType.SQL, alias) {
|
||||
isSameType = true
|
||||
break
|
||||
if stmt.Schema != nil {
|
||||
if field := stmt.Schema.LookUpField(field); field != nil {
|
||||
var (
|
||||
columnTypes, _ = m.DB.Migrator().ColumnTypes(value)
|
||||
fieldColumnType *migrator.ColumnType
|
||||
)
|
||||
for _, columnType := range columnTypes {
|
||||
if columnType.Name() == field.DBName {
|
||||
fieldColumnType, _ = columnType.(*migrator.ColumnType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not same, migrate
|
||||
if !isSameType {
|
||||
filedColumnAutoIncrement, _ := fieldColumnType.AutoIncrement()
|
||||
if field.AutoIncrement && filedColumnAutoIncrement { // update
|
||||
serialDatabaseType, _ := getSerialDatabaseType(fileType.SQL)
|
||||
if t, _ := fieldColumnType.ColumnType(); t != serialDatabaseType {
|
||||
if err := m.UpdateSequence(m.DB, stmt, field, serialDatabaseType); err != nil {
|
||||
return err
|
||||
fileType := clause.Expr{SQL: m.DataTypeOf(field)}
|
||||
// check for typeName and SQL name
|
||||
isSameType := true
|
||||
if fieldColumnType.DatabaseTypeName() != fileType.SQL {
|
||||
isSameType = false
|
||||
// if different, also check for aliases
|
||||
aliases := m.GetTypeAliases(fieldColumnType.DatabaseTypeName())
|
||||
for _, alias := range aliases {
|
||||
if strings.HasPrefix(fileType.SQL, alias) {
|
||||
isSameType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if field.AutoIncrement && !filedColumnAutoIncrement { // create
|
||||
serialDatabaseType, _ := getSerialDatabaseType(fileType.SQL)
|
||||
if err := m.CreateSequence(m.DB, stmt, field, serialDatabaseType); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !field.AutoIncrement && filedColumnAutoIncrement { // delete
|
||||
if err := m.DeleteSequence(m.DB, stmt, field, fileType); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? TYPE ? USING ?::?",
|
||||
m.CurrentTable(stmt), clause.Column{Name: field.DBName}, fileType, clause.Column{Name: field.DBName}, fileType).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if null, _ := fieldColumnType.Nullable(); null == field.NotNull {
|
||||
if field.NotNull {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? SET NOT NULL", m.CurrentTable(stmt), clause.Column{Name: field.DBName}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? DROP NOT NULL", m.CurrentTable(stmt), clause.Column{Name: field.DBName}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if uniq, _ := fieldColumnType.Unique(); !uniq && field.Unique {
|
||||
idxName := clause.Column{Name: m.DB.Config.NamingStrategy.IndexName(stmt.Table, field.DBName)}
|
||||
// Not a unique constraint but a unique index
|
||||
if !m.HasIndex(stmt.Table, idxName.Name) {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ADD CONSTRAINT ? UNIQUE(?)", m.CurrentTable(stmt), idxName, clause.Column{Name: field.DBName}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := fieldColumnType.DefaultValue(); (field.DefaultValueInterface == nil && ok) || v != field.DefaultValue {
|
||||
if field.HasDefaultValue && (field.DefaultValueInterface != nil || field.DefaultValue != "") {
|
||||
if field.DefaultValueInterface != nil {
|
||||
defaultStmt := &gorm.Statement{Vars: []interface{}{field.DefaultValueInterface}}
|
||||
m.Dialector.BindVarTo(defaultStmt, defaultStmt, field.DefaultValueInterface)
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? SET DEFAULT ?", m.CurrentTable(stmt), clause.Column{Name: field.DBName}, clause.Expr{SQL: m.Dialector.Explain(defaultStmt.SQL.String(), field.DefaultValueInterface)}).Error; err != nil {
|
||||
// not same, migrate
|
||||
if !isSameType {
|
||||
filedColumnAutoIncrement, _ := fieldColumnType.AutoIncrement()
|
||||
if field.AutoIncrement && filedColumnAutoIncrement { // update
|
||||
serialDatabaseType, _ := getSerialDatabaseType(fileType.SQL)
|
||||
if t, _ := fieldColumnType.ColumnType(); t != serialDatabaseType {
|
||||
if err := m.UpdateSequence(m.DB, stmt, field, serialDatabaseType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if field.AutoIncrement && !filedColumnAutoIncrement { // create
|
||||
serialDatabaseType, _ := getSerialDatabaseType(fileType.SQL)
|
||||
if err := m.CreateSequence(m.DB, stmt, field, serialDatabaseType); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if field.DefaultValue != "(-)" {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? SET DEFAULT ?", m.CurrentTable(stmt), clause.Column{Name: field.DBName}, clause.Expr{SQL: field.DefaultValue}).Error; err != nil {
|
||||
} else if !field.AutoIncrement && filedColumnAutoIncrement { // delete
|
||||
if err := m.DeleteSequence(m.DB, stmt, field, fileType); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? DROP DEFAULT", m.CurrentTable(stmt), clause.Column{Name: field.DBName}, clause.Expr{SQL: field.DefaultValue}).Error; err != nil {
|
||||
if err := m.modifyColumn(stmt, field, fileType, fieldColumnType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if null, _ := fieldColumnType.Nullable(); null == field.NotNull {
|
||||
if field.NotNull {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? SET NOT NULL", m.CurrentTable(stmt), clause.Column{Name: field.DBName}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? DROP NOT NULL", m.CurrentTable(stmt), clause.Column{Name: field.DBName}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := fieldColumnType.DefaultValue(); (field.DefaultValueInterface == nil && ok) || v != field.DefaultValue {
|
||||
if field.HasDefaultValue && (field.DefaultValueInterface != nil || field.DefaultValue != "") {
|
||||
if field.DefaultValueInterface != nil {
|
||||
defaultStmt := &gorm.Statement{Vars: []interface{}{field.DefaultValueInterface}}
|
||||
m.Dialector.BindVarTo(defaultStmt, defaultStmt, field.DefaultValueInterface)
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? SET DEFAULT ?", m.CurrentTable(stmt), clause.Column{Name: field.DBName}, clause.Expr{SQL: m.Dialector.Explain(defaultStmt.SQL.String(), field.DefaultValueInterface)}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else if field.DefaultValue != "(-)" {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? SET DEFAULT ?", m.CurrentTable(stmt), clause.Column{Name: field.DBName}, clause.Expr{SQL: field.DefaultValue}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? DROP DEFAULT", m.CurrentTable(stmt), clause.Column{Name: field.DBName}, clause.Expr{SQL: field.DefaultValue}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to look up field with name: %s", field)
|
||||
})
|
||||
@@ -375,18 +394,39 @@ func (m Migrator) AlterColumn(value interface{}, field string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Migrator) modifyColumn(stmt *gorm.Statement, field *schema.Field, targetType clause.Expr, existingColumn *migrator.ColumnType) error {
|
||||
alterSQL := "ALTER TABLE ? ALTER COLUMN ? TYPE ? USING ?::?"
|
||||
isUncastableDefaultValue := false
|
||||
|
||||
if targetType.SQL == "boolean" {
|
||||
switch existingColumn.DatabaseTypeName() {
|
||||
case "int2", "int8", "numeric":
|
||||
alterSQL = "ALTER TABLE ? ALTER COLUMN ? TYPE ? USING ?::int::?"
|
||||
}
|
||||
isUncastableDefaultValue = true
|
||||
}
|
||||
|
||||
if dv, _ := existingColumn.DefaultValue(); dv != "" && isUncastableDefaultValue {
|
||||
if err := m.DB.Exec("ALTER TABLE ? ALTER COLUMN ? DROP DEFAULT", m.CurrentTable(stmt), clause.Column{Name: field.DBName}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := m.DB.Exec(alterSQL, m.CurrentTable(stmt), clause.Column{Name: field.DBName}, targetType, clause.Column{Name: field.DBName}, targetType).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Migrator) HasConstraint(value interface{}, name string) bool {
|
||||
var count int64
|
||||
m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
||||
currentSchema, curTable := m.CurrentSchema(stmt, table)
|
||||
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
|
||||
if constraint != nil {
|
||||
name = constraint.Name
|
||||
} else if chk != nil {
|
||||
name = chk.Name
|
||||
name = constraint.GetName()
|
||||
}
|
||||
currentSchema, curTable := m.CurrentSchema(stmt, table)
|
||||
|
||||
return m.DB.Raw(
|
||||
return m.queryRaw(
|
||||
"SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE table_schema = ? AND table_name = ? AND constraint_name = ?",
|
||||
currentSchema, curTable, name,
|
||||
).Scan(&count).Error
|
||||
@@ -401,7 +441,7 @@ func (m Migrator) ColumnTypes(value interface{}) (columnTypes []gorm.ColumnType,
|
||||
var (
|
||||
currentDatabase = m.DB.Migrator().CurrentDatabase()
|
||||
currentSchema, table = m.CurrentSchema(stmt, stmt.Table)
|
||||
columns, err = m.DB.Raw(
|
||||
columns, err = m.queryRaw(
|
||||
"SELECT c.column_name, c.is_nullable = 'YES', c.udt_name, c.character_maximum_length, c.numeric_precision, c.numeric_precision_radix, c.numeric_scale, c.datetime_precision, 8 * typlen, c.column_default, pd.description, c.identity_increment FROM information_schema.columns AS c JOIN pg_type AS pgt ON c.udt_name = pgt.typname LEFT JOIN pg_catalog.pg_description as pd ON pd.objsubid = c.ordinal_position AND pd.objoid = (SELECT oid FROM pg_catalog.pg_class WHERE relname = c.table_name AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = c.table_schema)) where table_catalog = ? AND table_schema = ? AND table_name = ?",
|
||||
currentDatabase, currentSchema, table).Rows()
|
||||
)
|
||||
@@ -441,7 +481,7 @@ func (m Migrator) ColumnTypes(value interface{}) (columnTypes []gorm.ColumnType,
|
||||
}
|
||||
|
||||
if column.DefaultValueValue.Valid {
|
||||
column.DefaultValueValue.String = regexp.MustCompile(`'?(.*)\b'?:+[\w\s]+$`).ReplaceAllString(column.DefaultValueValue.String, "$1")
|
||||
column.DefaultValueValue.String = parseDefaultValueValue(column.DefaultValueValue.String)
|
||||
}
|
||||
|
||||
if datetimePrecision.Valid {
|
||||
@@ -475,7 +515,7 @@ func (m Migrator) ColumnTypes(value interface{}) (columnTypes []gorm.ColumnType,
|
||||
|
||||
// check primary, unique field
|
||||
{
|
||||
columnTypeRows, err := m.DB.Raw("SELECT constraint_name FROM information_schema.table_constraints tc JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name) JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name WHERE constraint_type IN ('PRIMARY KEY', 'UNIQUE') AND c.table_catalog = ? AND c.table_schema = ? AND c.table_name = ? AND constraint_type = ?", currentDatabase, currentSchema, table, "UNIQUE").Rows()
|
||||
columnTypeRows, err := m.queryRaw("SELECT constraint_name FROM information_schema.table_constraints tc JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_catalog, table_name, constraint_name) JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name WHERE constraint_type IN ('PRIMARY KEY', 'UNIQUE') AND c.table_catalog = ? AND c.table_schema = ? AND c.table_name = ? AND constraint_type = ?", currentDatabase, currentSchema, table, "UNIQUE").Rows()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -487,7 +527,7 @@ func (m Migrator) ColumnTypes(value interface{}) (columnTypes []gorm.ColumnType,
|
||||
}
|
||||
columnTypeRows.Close()
|
||||
|
||||
columnTypeRows, err = m.DB.Raw("SELECT c.column_name, constraint_name, constraint_type FROM information_schema.table_constraints tc JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_name) JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name WHERE constraint_type IN ('PRIMARY KEY', 'UNIQUE') AND c.table_catalog = ? AND c.table_schema = ? AND c.table_name = ?", currentDatabase, currentSchema, table).Rows()
|
||||
columnTypeRows, err = m.queryRaw("SELECT c.column_name, constraint_name, constraint_type FROM information_schema.table_constraints tc JOIN information_schema.constraint_column_usage AS ccu USING (constraint_schema, constraint_catalog, table_name, constraint_name) JOIN information_schema.columns AS c ON c.table_schema = tc.constraint_schema AND tc.table_name = c.table_name AND ccu.column_name = c.column_name WHERE constraint_type IN ('PRIMARY KEY', 'UNIQUE') AND c.table_catalog = ? AND c.table_schema = ? AND c.table_name = ?", currentDatabase, currentSchema, table).Rows()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -514,7 +554,7 @@ func (m Migrator) ColumnTypes(value interface{}) (columnTypes []gorm.ColumnType,
|
||||
|
||||
// check column type
|
||||
{
|
||||
dataTypeRows, err := m.DB.Raw(`SELECT a.attname as column_name, format_type(a.atttypid, a.atttypmod) AS data_type
|
||||
dataTypeRows, err := m.queryRaw(`SELECT a.attname as column_name, format_type(a.atttypid, a.atttypmod) AS data_type
|
||||
FROM pg_attribute a JOIN pg_class b ON a.attrelid = b.oid AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = ?)
|
||||
WHERE a.attnum > 0 -- hide internal columns
|
||||
AND NOT a.attisdropped -- hide deleted columns
|
||||
@@ -672,7 +712,7 @@ func (m Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
|
||||
|
||||
err := m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
result := make([]*Index, 0)
|
||||
scanErr := m.DB.Raw(indexSql, stmt.Table).Scan(&result).Error
|
||||
scanErr := m.queryRaw(indexSql, stmt.Table).Scan(&result).Error
|
||||
if scanErr != nil {
|
||||
return scanErr
|
||||
}
|
||||
@@ -747,3 +787,8 @@ func (m Migrator) RenameColumn(dst interface{}, oldName, field string) error {
|
||||
m.resetPreparedStmts()
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDefaultValueValue(defaultValue string) string {
|
||||
value := regexp.MustCompile(`^(.*?)(?:::.*)?$`).ReplaceAllString(defaultValue, "$1")
|
||||
return strings.Trim(value, "'")
|
||||
}
|
||||
|
||||
+51
-7
@@ -24,11 +24,17 @@ type Dialector struct {
|
||||
type Config struct {
|
||||
DriverName string
|
||||
DSN string
|
||||
WithoutQuotingCheck bool
|
||||
PreferSimpleProtocol bool
|
||||
WithoutReturning bool
|
||||
Conn gorm.ConnPool
|
||||
}
|
||||
|
||||
var (
|
||||
timeZoneMatcher = regexp.MustCompile("(time_zone|TimeZone)=(.*?)($|&| )")
|
||||
defaultIdentifierLength = 63 //maximum identifier length for postgres
|
||||
)
|
||||
|
||||
func Open(dsn string) gorm.Dialector {
|
||||
return &Dialector{&Config{DSN: dsn}}
|
||||
}
|
||||
@@ -41,17 +47,42 @@ func (dialector Dialector) Name() string {
|
||||
return "postgres"
|
||||
}
|
||||
|
||||
var timeZoneMatcher = regexp.MustCompile("(time_zone|TimeZone)=(.*?)($|&| )")
|
||||
func (dialector Dialector) Apply(config *gorm.Config) error {
|
||||
if config.NamingStrategy == nil {
|
||||
config.NamingStrategy = schema.NamingStrategy{
|
||||
IdentifierMaxLength: defaultIdentifierLength,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := config.NamingStrategy.(type) {
|
||||
case *schema.NamingStrategy:
|
||||
if v.IdentifierMaxLength <= 0 {
|
||||
v.IdentifierMaxLength = defaultIdentifierLength
|
||||
}
|
||||
case schema.NamingStrategy:
|
||||
if v.IdentifierMaxLength <= 0 {
|
||||
v.IdentifierMaxLength = defaultIdentifierLength
|
||||
config.NamingStrategy = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dialector Dialector) Initialize(db *gorm.DB) (err error) {
|
||||
callbackConfig := &callbacks.Config{
|
||||
CreateClauses: []string{"INSERT", "VALUES", "ON CONFLICT"},
|
||||
UpdateClauses: []string{"UPDATE", "SET", "FROM", "WHERE"},
|
||||
DeleteClauses: []string{"DELETE", "FROM", "WHERE"},
|
||||
}
|
||||
// register callbacks
|
||||
if !dialector.WithoutReturning {
|
||||
callbacks.RegisterDefaultCallbacks(db, &callbacks.Config{
|
||||
CreateClauses: []string{"INSERT", "VALUES", "ON CONFLICT", "RETURNING"},
|
||||
UpdateClauses: []string{"UPDATE", "SET", "WHERE", "RETURNING"},
|
||||
DeleteClauses: []string{"DELETE", "FROM", "WHERE", "RETURNING"},
|
||||
})
|
||||
callbackConfig.CreateClauses = append(callbackConfig.CreateClauses, "RETURNING")
|
||||
callbackConfig.UpdateClauses = append(callbackConfig.UpdateClauses, "RETURNING")
|
||||
callbackConfig.DeleteClauses = append(callbackConfig.DeleteClauses, "RETURNING")
|
||||
}
|
||||
callbacks.RegisterDefaultCallbacks(db, callbackConfig)
|
||||
|
||||
if dialector.Conn != nil {
|
||||
db.ConnPool = dialector.Conn
|
||||
@@ -90,10 +121,23 @@ func (dialector Dialector) DefaultValueOf(field *schema.Field) clause.Expression
|
||||
|
||||
func (dialector Dialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v interface{}) {
|
||||
writer.WriteByte('$')
|
||||
writer.WriteString(strconv.Itoa(len(stmt.Vars)))
|
||||
index := 0
|
||||
varLen := len(stmt.Vars)
|
||||
if varLen > 0 {
|
||||
switch stmt.Vars[0].(type) {
|
||||
case pgx.QueryExecMode:
|
||||
index++
|
||||
}
|
||||
}
|
||||
writer.WriteString(strconv.Itoa(varLen - index))
|
||||
}
|
||||
|
||||
func (dialector Dialector) QuoteTo(writer clause.Writer, str string) {
|
||||
if dialector.WithoutQuotingCheck {
|
||||
writer.WriteString(str)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
underQuoted, selfQuoted bool
|
||||
continuousBacktick int8
|
||||
|
||||
Reference in New Issue
Block a user