From e5ca672742e1dd8f99a3c029f8614724ca815a19 Mon Sep 17 00:00:00 2001 From: Stephan Peijnik Date: Wed, 3 Feb 2016 12:19:55 +0100 Subject: [PATCH 1/2] Implement conditional generation of foreign keys. For now, support is restricted to MySQL and PostgreSQL, while all other DBMS default to the old behavior. Signed-off-by: Stephan Peijnik --- common_dialect.go | 7 +++++++ dialect.go | 1 + mysql.go | 7 +++++++ postgres.go | 7 +++++++ 4 files changed, 22 insertions(+) diff --git a/common_dialect.go b/common_dialect.go index 7f08b04f..cc75b677 100644 --- a/common_dialect.go +++ b/common_dialect.go @@ -95,6 +95,13 @@ func (c commonDialect) HasIndex(scope *Scope, tableName string, indexName string return count > 0 } +func (c commonDialect) HasForeignKey(scope *Scope, tableName string, fkName string) bool { + // Checking if a foreign key constraint exists is DBMS specific. In order to preserve + // the previous logic of AddForeignKey, we are always returning false for dialects which do + // not implement this check. + return false +} + func (commonDialect) RemoveIndex(scope *Scope, indexName string) { scope.Err(scope.NewDB().Exec(fmt.Sprintf("DROP INDEX %v ON %v", indexName, scope.QuotedTableName())).Error) } diff --git a/dialect.go b/dialect.go index 926f8a11..551a721a 100644 --- a/dialect.go +++ b/dialect.go @@ -16,6 +16,7 @@ type Dialect interface { HasTable(scope *Scope, tableName string) bool HasColumn(scope *Scope, tableName string, columnName string) bool HasIndex(scope *Scope, tableName string, indexName string) bool + HasForeignKey(scope *Scope, tableName string, fkName string) bool RemoveIndex(scope *Scope, indexName string) CurrentDatabase(scope *Scope) string } diff --git a/mysql.go b/mysql.go index 9e1d56d3..9cdbb5b7 100644 --- a/mysql.go +++ b/mysql.go @@ -56,6 +56,13 @@ func (mysql) SqlTag(value reflect.Value, size int, autoIncrease bool) string { panic(fmt.Sprintf("invalid sql type %s (%s) for mysql", value.Type().Name(), value.Kind().String())) } +func (s mysql) HasForeignKey(scope *Scope, tableName string, fkName string) bool { + var count int + s.RawScanInt(scope, &count, "SELECT count(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=? AND TABLE_NAME=? AND CONSTRAINT_NAME=? AND CONSTRAINT_TYPE='FOREIGN KEY'", + s.CurrentDatabase(scope), tableName, fkName) + return count > 0 +} + func (mysql) Quote(key string) string { return fmt.Sprintf("`%s`", key) } diff --git a/postgres.go b/postgres.go index 3b083dfa..7d381853 100644 --- a/postgres.go +++ b/postgres.go @@ -103,6 +103,13 @@ func (s postgres) HasIndex(scope *Scope, tableName string, indexName string) boo return count > 0 } +func (s postgres) HasForeignKey(scope *Scope, tableName string, fkName string) bool { + var count int + s.RawScanInt(scope, &count, "SELECT count(con.conname) FROM pg_constraint con WHERE con.conname = ? AND con.contype='f' AND ?::regclass::oid = con.conrelid", + fkName, tableName) + return count > 0 +} + func (s postgres) CurrentDatabase(scope *Scope) (name string) { s.RawScanString(scope, &name, "SELECT CURRENT_DATABASE()") return From f4e9671baff4d5874babadef960480e5a548bd15 Mon Sep 17 00:00:00 2001 From: Stephan Peijnik Date: Wed, 3 Feb 2016 12:49:40 +0100 Subject: [PATCH 2/2] Actually check if a foreign key exists, before creating it. Signed-off-by: Stephan Peijnik --- scope_private.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scope_private.go b/scope_private.go index fa5d5f44..1220a92e 100644 --- a/scope_private.go +++ b/scope_private.go @@ -615,6 +615,11 @@ 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, 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() }