diff --git a/dialect.go b/dialect.go index 897e2570..adaf43af 100644 --- a/dialect.go +++ b/dialect.go @@ -25,6 +25,8 @@ type Dialect interface { // HasIndex check has index or not HasIndex(tableName string, indexName string) bool + // HasForeignKey check has foreign key or not + HasForeignKey(tableName string, foreignKeyName string) bool // RemoveIndex remove index RemoveIndex(tableName string, indexName string) error // HasTable check has table or not diff --git a/dialect_common.go b/dialect_common.go index 7afc5c14..95553c97 100644 --- a/dialect_common.go +++ b/dialect_common.go @@ -95,6 +95,10 @@ func (s commonDialect) RemoveIndex(tableName string, indexName string) error { return err } +func (s commonDialect) HasForeignKey(tableName string, foreignKeyName string) bool { + return false +} + func (s commonDialect) HasTable(tableName string) bool { var count int s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = ? AND table_name = ?", s.currentDatabase(), tableName).Scan(&count) diff --git a/dialect_mssql.go b/dialect_mssql.go deleted file mode 100644 index 7a59bb30..00000000 --- a/dialect_mssql.go +++ /dev/null @@ -1,115 +0,0 @@ -package gorm - -import ( - "fmt" - "reflect" - "strings" - "time" -) - -type mssql struct { - commonDialect -} - -func init() { - RegisterDialect("mssql", &mssql{}) -} - -func (mssql) GetName() string { - return "mssql" -} - -func (mssql) DataTypeOf(field *StructField) string { - var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field) - - if sqlType == "" { - switch dataValue.Kind() { - case reflect.Bool: - sqlType = "bit" - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: - if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { - sqlType = "int IDENTITY(1,1)" - } else { - sqlType = "int" - } - case reflect.Int64, reflect.Uint64: - if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { - sqlType = "bigint IDENTITY(1,1)" - } else { - sqlType = "bigint" - } - case reflect.Float32, reflect.Float64: - sqlType = "float" - case reflect.String: - if size > 0 && size < 65532 { - sqlType = fmt.Sprintf("nvarchar(%d)", size) - } else { - sqlType = "text" - } - case reflect.Struct: - if _, ok := dataValue.Interface().(time.Time); ok { - sqlType = "datetime2" - } - default: - if _, ok := dataValue.Interface().([]byte); ok { - if size > 0 && size < 65532 { - sqlType = fmt.Sprintf("varchar(%d)", size) - } else { - sqlType = "text" - } - } - } - } - - if sqlType == "" { - panic(fmt.Sprintf("invalid sql type %s (%s) for mssql", dataValue.Type().Name(), dataValue.Kind().String())) - } - - if strings.TrimSpace(additionalType) == "" { - return sqlType - } - return fmt.Sprintf("%v %v", sqlType, additionalType) -} - -func (s mssql) HasIndex(tableName string, indexName string) bool { - var count int - s.db.QueryRow("SELECT count(*) FROM sys.indexes WHERE name=? AND object_id=OBJECT_ID(?)", indexName, tableName).Scan(&count) - return count > 0 -} - -func (s mssql) RemoveIndex(tableName string, indexName string) error { - _, err := s.db.Exec(fmt.Sprintf("DROP INDEX %v ON %v", indexName, s.Quote(tableName))) - return err -} - -func (s mssql) HasTable(tableName string) bool { - var count int - s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = ? AND table_catalog = ?", tableName, s.currentDatabase()).Scan(&count) - return count > 0 -} - -func (s mssql) HasColumn(tableName string, columnName string) bool { - var count int - s.db.QueryRow("SELECT count(*) FROM information_schema.columns WHERE table_catalog = ? AND table_name = ? AND column_name = ?", s.currentDatabase(), tableName, columnName).Scan(&count) - return count > 0 -} - -func (s mssql) currentDatabase() (name string) { - s.db.QueryRow("SELECT DB_NAME() AS [Current Database]").Scan(&name) - return -} - -func (mssql) LimitAndOffsetSQL(limit, offset int) (sql string) { - if limit > 0 || offset > 0 { - if offset < 0 { - offset = 0 - } - - sql += fmt.Sprintf(" OFFSET %d ROWS", offset) - - if limit >= 0 { - sql += fmt.Sprintf(" FETCH NEXT %d ROWS ONLY", limit) - } - } - return -} diff --git a/dialect_mysql.go b/dialect_mysql.go index d98c33a3..a6946d5c 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -97,6 +97,12 @@ func (s mysql) RemoveIndex(tableName string, indexName string) error { return err } +func (s mysql) HasForeignKey(tableName string, foreignKeyName string) bool { + var count int + s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=? AND TABLE_NAME=? AND CONSTRAINT_NAME=? AND CONSTRAINT_TYPE='FOREIGN KEY'", s.currentDatabase(), foreignKeyName).Scan(&count) + return count > 0 +} + func (s mysql) currentDatabase() (name string) { s.db.QueryRow("SELECT DATABASE()").Scan(&name) return diff --git a/dialect_postgres.go b/dialect_postgres.go index 96627d92..baf76659 100644 --- a/dialect_postgres.go +++ b/dialect_postgres.go @@ -83,6 +83,12 @@ func (s postgres) HasIndex(tableName string, indexName string) bool { return count > 0 } +func (s postgres) HasForeignKey(tableName string, foreignKeyName string) bool { + var count int + s.db.QueryRow("SELECT count(con.conname) FROM pg_constraint con WHERE $1::regclass::oid = con.conrelid AND con.conname = $2 AND con.contype='f'", s.currentDatabase(), foreignKeyName).Scan(&count) + return count > 0 +} + func (s postgres) HasTable(tableName string) bool { var count int s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = $1 AND table_type = 'BASE TABLE'", tableName).Scan(&count) diff --git a/dialects/mssql/mssql.go b/dialects/mssql/mssql.go index d628ef7c..657bada9 100644 --- a/dialects/mssql/mssql.go +++ b/dialects/mssql/mssql.go @@ -1,7 +1,11 @@ package mssql import ( + "database/sql" "fmt" + "reflect" + "strings" + "time" _ "github.com/denisenkom/go-mssqldb" "github.com/jinzhu/gorm" @@ -15,4 +19,132 @@ func setIdentityInsert(scope *gorm.Scope) { func init() { gorm.DefaultCallback.Create().After("gorm:begin_transaction").Register("mssql:set_identity_insert", setIdentityInsert) + gorm.RegisterDialect("mssql", &mssql{}) +} + +type mssql struct { + db *sql.DB +} + +func (mssql) GetName() string { + return "mssql" +} + +func (s *mssql) SetDB(db *sql.DB) { + s.db = db +} + +func (mssql) BindVar(i int) string { + return "$$" // ? +} + +func (mssql) Quote(key string) string { + return fmt.Sprintf(`"%s"`, key) +} + +func (mssql) DataTypeOf(field *gorm.StructField) string { + var dataValue, sqlType, size, additionalType = gorm.ParseFieldStructForDialect(field) + + if sqlType == "" { + switch dataValue.Kind() { + case reflect.Bool: + sqlType = "bit" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: + if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { + sqlType = "int IDENTITY(1,1)" + } else { + sqlType = "int" + } + case reflect.Int64, reflect.Uint64: + if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { + sqlType = "bigint IDENTITY(1,1)" + } else { + sqlType = "bigint" + } + case reflect.Float32, reflect.Float64: + sqlType = "float" + case reflect.String: + if size > 0 && size < 65532 { + sqlType = fmt.Sprintf("nvarchar(%d)", size) + } else { + sqlType = "text" + } + case reflect.Struct: + if _, ok := dataValue.Interface().(time.Time); ok { + sqlType = "datetime2" + } + default: + if _, ok := dataValue.Interface().([]byte); ok { + if size > 0 && size < 65532 { + sqlType = fmt.Sprintf("varchar(%d)", size) + } else { + sqlType = "text" + } + } + } + } + + if sqlType == "" { + panic(fmt.Sprintf("invalid sql type %s (%s) for mssql", dataValue.Type().Name(), dataValue.Kind().String())) + } + + if strings.TrimSpace(additionalType) == "" { + return sqlType + } + return fmt.Sprintf("%v %v", sqlType, additionalType) +} + +func (s mssql) HasIndex(tableName string, indexName string) bool { + var count int + s.db.QueryRow("SELECT count(*) FROM sys.indexes WHERE name=? AND object_id=OBJECT_ID(?)", indexName, tableName).Scan(&count) + return count > 0 +} + +func (s mssql) RemoveIndex(tableName string, indexName string) error { + _, err := s.db.Exec(fmt.Sprintf("DROP INDEX %v ON %v", indexName, s.Quote(tableName))) + return err +} + +func (s mssql) HasForeignKey(tableName string, foreignKeyName string) bool { + return false +} + +func (s mssql) HasTable(tableName string) bool { + var count int + s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = ? AND table_catalog = ?", tableName, s.currentDatabase()).Scan(&count) + return count > 0 +} + +func (s mssql) HasColumn(tableName string, columnName string) bool { + var count int + s.db.QueryRow("SELECT count(*) FROM information_schema.columns WHERE table_catalog = ? AND table_name = ? AND column_name = ?", s.currentDatabase(), tableName, columnName).Scan(&count) + return count > 0 +} + +func (s mssql) currentDatabase() (name string) { + s.db.QueryRow("SELECT DB_NAME() AS [Current Database]").Scan(&name) + return +} + +func (mssql) LimitAndOffsetSQL(limit, offset int) (sql string) { + if limit > 0 || offset > 0 { + if offset < 0 { + offset = 0 + } + + sql += fmt.Sprintf(" OFFSET %d ROWS", offset) + + if limit >= 0 { + sql += fmt.Sprintf(" FETCH NEXT %d ROWS ONLY", limit) + } + } + return +} + +func (mssql) SelectFromDummyTable() string { + return "" +} + +func (mssql) LastInsertIdReturningSuffix(tableName, columnName string) string { + return "" } diff --git a/scope_private.go b/scope_private.go index d0597c11..b2f504d1 100644 --- a/scope_private.go +++ b/scope_private.go @@ -616,6 +616,10 @@ func (scope *Scope) addIndex(unique bool, indexName string, column ...string) { func (scope *Scope) addForeignKey(field string, dest string, onDelete string, onUpdate string) { var keyName = fmt.Sprintf("%s_%s_%s_foreign", scope.TableName(), field, dest) keyName = regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(keyName, "_") + + if scope.Dialect().HasForeignKey(scope.TableName(), keyName) { + return + } var query = `ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s ON DELETE %s ON UPDATE %s;` scope.Raw(fmt.Sprintf(query, scope.QuotedTableName(), scope.quoteIfPossible(keyName), scope.quoteIfPossible(field), dest, onDelete, onUpdate)).Exec() }