@@ -145,6 +145,17 @@ 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+ constraint .OnUpdate = ""
152+ defer func (tx * gorm.DB , table string , constraint * schema.Constraint ) {
153+ if err == nil {
154+ err = m .createUpadateCascadeTrigger (tx , constraint )
155+ }
156+ }(tx , stmt .Table , constraint )
157+ }
158+
148159 // If the same set of foreign keys already references the parent column,
149160 // remove duplicates to avoid ORA-02274: duplicate referential constraint specifications
150161 var foreignKeys []string
@@ -399,6 +410,32 @@ func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) {
399410 return columnTypes , execErr
400411}
401412
413+ // CreateConstraint creates constraint based on the given 'value' and 'name'
414+ func (m Migrator ) CreateConstraint (value interface {}, name string ) error {
415+ return m .RunWithValue (value , func (stmt * gorm.Statement ) error {
416+ constraint , table := m .GuessConstraintInterfaceAndTable (stmt , name )
417+ if constraint != nil {
418+ if c , ok := constraint .(* schema.Constraint ); ok {
419+ // Oracle doesn’t support OnUpdate on foreign keys.
420+ // Use a trigger instead to propagate the update to the child table instead.
421+ if len (c .References ) > 0 && c .OnUpdate != "" {
422+ c .OnUpdate = ""
423+ constraint = c
424+ m .createUpadateCascadeTrigger (m .DB , c )
425+ }
426+ }
427+
428+ vars := []interface {}{clause.Table {Name : table }}
429+ if stmt .TableExpr != nil {
430+ vars [0 ] = stmt .TableExpr
431+ }
432+ sql , values := constraint .Build ()
433+ return m .DB .Exec ("ALTER TABLE ? ADD " + sql , append (vars , values ... )... ).Error
434+ }
435+ return nil
436+ })
437+ }
438+
402439// HasConstraint checks whether the table for the given `value` contains the specified constraint `name`
403440func (m Migrator ) HasConstraint (value interface {}, name string ) bool {
404441 var count int64
@@ -418,6 +455,33 @@ func (m Migrator) HasConstraint(value interface{}, name string) bool {
418455 return count > 0
419456}
420457
458+ // DropConstraint drops constraint based on the given 'value' and 'name'
459+ func (m Migrator ) DropConstraint (value interface {}, name string ) error {
460+ if err := m .RunWithValue (value , func (stmt * gorm.Statement ) error {
461+
462+ constraint , _ := m .GuessConstraintInterfaceAndTable (stmt , name )
463+
464+ if c , ok := constraint .(* schema.Constraint ); ok && c != nil {
465+ if len (c .References ) > 0 && c .OnUpdate != "" {
466+ for i , fk := range c .ForeignKeys {
467+ triggerName := m .FkTriggerName (
468+ c .ReferenceSchema .Table ,
469+ c .References [i ].DBName ,
470+ c .Schema .Table ,
471+ fk .DBName ,
472+ )
473+ return m .DB .Exec ("DROP TRIGGER ?" , clause.Column {Name : triggerName }).Error
474+ }
475+ }
476+ }
477+ return nil
478+ }); err != nil {
479+ return err
480+ }
481+
482+ return m .Migrator .DropConstraint (value , name )
483+ }
484+
421485// DropIndex drops the index with the specified `name` from the table associated with `value`
422486func (m Migrator ) DropIndex (value interface {}, name string ) error {
423487 return m .RunWithValue (value , func (stmt * gorm.Statement ) error {
@@ -569,3 +633,65 @@ func (m Migrator) isNumeric(s string) bool {
569633 _ , err := strconv .ParseFloat (s , 64 )
570634 return err == nil
571635}
636+
637+ func (m Migrator ) FkTriggerName (refTable string , refField string , table string , field string ) string {
638+ return fmt .Sprintf ("fk_trigger_%s_%s_%s_%s" , refTable , refField , table , field )
639+ }
640+
641+ // Creates a trigger to cascade the update to the child table
642+ func (m Migrator ) createUpadateCascadeTrigger (tx * gorm.DB , constraint * schema.Constraint ) error {
643+ for i , fk := range constraint .ForeignKeys {
644+ var (
645+ tmpBuilder strings.Builder
646+ plsqlBuilder strings.Builder
647+ parentTable string = constraint .ReferenceSchema .Table
648+ parentField string = constraint .References [i ].DBName
649+ table string = constraint .Schema .Table
650+ field string = fk .DBName
651+
652+ triggerName string = m .FkTriggerName (parentTable , parentField , table , field )
653+
654+ quotedParentTable string
655+ quotedParentField string
656+ quotedTable string
657+ quotedField string
658+ quotedTriggerName string
659+ )
660+
661+ // Initialize quoted variables according to the driver’s quoting rules
662+ writeQuotedIdentifier (& tmpBuilder , parentTable )
663+ quotedParentTable = tmpBuilder .String ()
664+ tmpBuilder .Reset ()
665+
666+ writeQuotedIdentifier (& tmpBuilder , parentField )
667+ quotedParentField = tmpBuilder .String ()
668+ tmpBuilder .Reset ()
669+
670+ writeQuotedIdentifier (& tmpBuilder , table )
671+ quotedTable = tmpBuilder .String ()
672+ tmpBuilder .Reset ()
673+
674+ writeQuotedIdentifier (& tmpBuilder , field )
675+ quotedField = tmpBuilder .String ()
676+ tmpBuilder .Reset ()
677+
678+ writeQuotedIdentifier (& tmpBuilder , triggerName )
679+ quotedTriggerName = tmpBuilder .String ()
680+ tmpBuilder .Reset ()
681+
682+ // Start PL/SQL block
683+ plsqlBuilder .WriteString ("CREATE OR REPLACE TRIGGER " + quotedTriggerName + "\n " )
684+ plsqlBuilder .WriteString ("AFTER UPDATE OF " + quotedParentField + " ON " + quotedParentTable + "\n " )
685+ plsqlBuilder .WriteString ("FOR EACH ROW\n " )
686+ plsqlBuilder .WriteString ("BEGIN\n " )
687+ plsqlBuilder .WriteString (" UPDATE " + quotedTable + "\n " )
688+ plsqlBuilder .WriteString (" SET " + quotedField + " = :NEW." + quotedParentField + "\n " )
689+ plsqlBuilder .WriteString (" WHERE " + quotedField + " = :OLD." + quotedParentField )
690+ plsqlBuilder .WriteString (";\n " )
691+ plsqlBuilder .WriteString ("END;" )
692+ if err := tx .Exec (plsqlBuilder .String ()).Error ; err != nil {
693+ return err
694+ }
695+ }
696+ return nil
697+ }
0 commit comments