@@ -145,6 +145,19 @@ func (m Migrator) CreateTable(values ...interface{}) error {
145145 }
146146 if constraint := rel .ParseConstraint (); constraint != nil {
147147 if constraint .Schema == stmt .Schema {
148+ // Oracle doesn’t support OnUpdate on foreign keys.
149+ // Use a trigger instead to propagate the update to the child table instead.
150+ if len (constraint .References ) > 0 && constraint .OnUpdate != "" {
151+ defer func (tx * gorm.DB , table string , constraint * schema.Constraint , onUpdate string ) {
152+ if err == nil {
153+ // retore the OnUpdate value
154+ constraint .OnUpdate = onUpdate
155+ err = m .createUpadateCascadeTrigger (tx , constraint )
156+ }
157+ }(tx , stmt .Table , constraint , constraint .OnUpdate )
158+ constraint .OnUpdate = ""
159+ }
160+
148161 // If the same set of foreign keys already references the parent column,
149162 // remove duplicates to avoid ORA-02274: duplicate referential constraint specifications
150163 var foreignKeys []string
@@ -399,6 +412,32 @@ func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
399412 return columnTypes , execErr
400413}
401414
415+ // CreateConstraint creates constraint based on the given 'value' and 'name'
416+ func (m Migrator ) CreateConstraint (value interface {}, name string ) error {
417+ return m .RunWithValue (value , func (stmt * gorm.Statement ) error {
418+ constraint , table := m .GuessConstraintInterfaceAndTable (stmt , name )
419+ if constraint != nil {
420+ if c , ok := constraint .(* schema.Constraint ); ok {
421+ // Oracle doesn’t support OnUpdate on foreign keys.
422+ // Use a trigger instead to propagate the update to the child table instead.
423+ if len (c .References ) > 0 && c .OnUpdate != "" {
424+ m .createUpadateCascadeTrigger (m .DB , c )
425+ c .OnUpdate = ""
426+ constraint = c
427+ }
428+ }
429+
430+ vars := []interface {}{clause.Table {Name : table }}
431+ if stmt .TableExpr != nil {
432+ vars [0 ] = stmt .TableExpr
433+ }
434+ sql , values := constraint .Build ()
435+ return m .DB .Exec ("ALTER TABLE ? ADD " + sql , append (vars , values ... )... ).Error
436+ }
437+ return nil
438+ })
439+ }
440+
402441// HasConstraint checks whether the table for the given `value` contains the specified constraint `name`
403442func (m Migrator ) HasConstraint (value interface {}, name string ) bool {
404443 var count int64
@@ -418,6 +457,33 @@ func (m Migrator) HasConstraint(value interface{}, name string) bool {
418457 return count > 0
419458}
420459
460+ // DropConstraint drops constraint based on the given 'value' and 'name'
461+ func (m Migrator ) DropConstraint (value interface {}, name string ) error {
462+ if err := m .RunWithValue (value , func (stmt * gorm.Statement ) error {
463+
464+ constraint , _ := m .GuessConstraintInterfaceAndTable (stmt , name )
465+
466+ if c , ok := constraint .(* schema.Constraint ); ok && c != nil {
467+ if len (c .References ) > 0 && c .OnUpdate != "" {
468+ for i , fk := range c .ForeignKeys {
469+ triggerName := m .FkTriggerName (
470+ c .ReferenceSchema .Table ,
471+ c .References [i ].DBName ,
472+ c .Schema .Table ,
473+ fk .DBName ,
474+ )
475+ return m .DB .Exec ("DROP TRIGGER ?" , clause.Column {Name : triggerName }).Error
476+ }
477+ }
478+ }
479+ return nil
480+ }); err != nil {
481+ return err
482+ }
483+
484+ return m .Migrator .DropConstraint (value , name )
485+ }
486+
421487// DropIndex drops the index with the specified `name` from the table associated with `value`
422488func (m Migrator ) DropIndex (value interface {}, name string ) error {
423489 return m .RunWithValue (value , func (stmt * gorm.Statement ) error {
@@ -569,3 +635,64 @@ func (m Migrator) isNumeric(s string) bool {
569635 _ , err := strconv .ParseFloat (s , 64 )
570636 return err == nil
571637}
638+
639+ func (m Migrator ) FkTriggerName (refTable string , refField string , table string , field string ) string {
640+ return fmt .Sprintf ("fk_trigger_%s_%s_%s_%s" , refTable , refField , table , field )
641+ }
642+
643+ // Creates a trigger to cascade the update to the child table
644+ func (m Migrator ) createUpadateCascadeTrigger (tx * gorm.DB , constraint * schema.Constraint ) error {
645+ onUpdate := strings .TrimSpace (strings .ToLower (constraint .OnUpdate ))
646+ if onUpdate != "cascade" && onUpdate != "set null" && onUpdate != "set default" {
647+ return nil
648+ }
649+
650+ parentTable := constraint .ReferenceSchema .Table
651+ quotedParentTable := QuoteIdentifier (parentTable )
652+ table := constraint .Schema .Table
653+ quotedTable := QuoteIdentifier (table )
654+
655+ for i , fk := range constraint .ForeignKeys {
656+ parentField := constraint .References [i ].DBName
657+ quotedParentField := QuoteIdentifier (parentField )
658+ field := fk .DBName
659+ quotedField := QuoteIdentifier (field )
660+ triggerName := m .FkTriggerName (parentTable , parentField , table , field )
661+ quotedTriggerName := QuoteIdentifier (triggerName )
662+
663+ var updateValue string
664+ switch onUpdate {
665+ case "cascade" :
666+ updateValue = ":NEW." + quotedParentField
667+ case "set null" :
668+ updateValue = "NULL"
669+ case "set default" :
670+ updateValue = "DEFAULT"
671+ }
672+
673+ plsql := fmt .Sprintf (
674+ `CREATE OR REPLACE TRIGGER %s
675+ AFTER UPDATE OF %s ON %s
676+ FOR EACH ROW
677+ BEGIN
678+ UPDATE %s
679+ SET %s = %s
680+ WHERE %s = :OLD.%s;
681+ END;` ,
682+ quotedTriggerName ,
683+ quotedParentField ,
684+ quotedParentTable ,
685+ quotedTable ,
686+ quotedField ,
687+ updateValue ,
688+ quotedField ,
689+ quotedParentField ,
690+ )
691+
692+ if err := tx .Exec (plsql ).Error ; err != nil {
693+ return err
694+ }
695+ }
696+
697+ return nil
698+ }
0 commit comments