@@ -241,6 +241,7 @@ func applyTriggers(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scope,
241
241
return nil , transform .SameTree , err
242
242
}
243
243
244
+ // triggerTable = getTableName(ct)
244
245
var triggerTable string
245
246
switch t := ct .Table .(type ) {
246
247
case * plan.ResolvedTable :
@@ -450,6 +451,39 @@ func getUpdateJoinSource(n sql.Node) *plan.UpdateSource {
450
451
return nil
451
452
}
452
453
454
+ // Determines if a GetField expression references the triggered table in an UpdateJoin
455
+ func isUpdateJoinTriggerField (getField * expression.GetField , updateJoin * plan.UpdateJoin , trigger * plan.CreateTrigger ) bool {
456
+ updateTargets := updateJoin .UpdateTargets
457
+ if updateTarget , isUpdateTarget := updateTargets [getField .Table ()]; isUpdateTarget {
458
+ if getTableName (updateTarget ) == getTableName (trigger .Table ) {
459
+ return true
460
+ }
461
+ }
462
+ return false
463
+ }
464
+
465
+ // Returns the projection from an UpdateJoin with the non-triggered tables masked. This is to prevent conflicts if two
466
+ // joined tables have columns with the same name
467
+ func getMaskedUpdateJoinProject (updateJoin * plan.UpdateJoin , trigger * plan.CreateTrigger ) * plan.Project {
468
+ if updateSrc , isUpdateSrc := updateJoin .Child .(* plan.UpdateSource ); isUpdateSrc {
469
+ // get project parent
470
+ if project , isProject := updateSrc .Child .(* plan.Project ); isProject {
471
+ projections := project .Projections
472
+ maskedProjections := make ([]sql.Expression , len (projections ))
473
+ for i , projection := range projections {
474
+ maskedProjections [i ] = projection
475
+ if gf , isGf := projection .(* expression.GetField ); isGf {
476
+ if ! isUpdateJoinTriggerField (gf , updateJoin , trigger ) {
477
+ maskedProjections [i ] = gf .WithName ("" )
478
+ }
479
+ }
480
+ }
481
+ return plan .NewProject (maskedProjections , project .Child )
482
+ }
483
+ }
484
+ panic ("UpdateJoin node is not correctly structured" )
485
+ }
486
+
453
487
// getTriggerLogic analyzes and returns the Node representing the trigger body for the trigger given, applied to the
454
488
// plan node given, which must be an insert, update, or delete.
455
489
func getTriggerLogic (ctx * sql.Context , a * Analyzer , n sql.Node , scope * plan.Scope , trigger * plan.CreateTrigger , qFlags * sql.QueryFlags ) (sql.Node , error ) {
@@ -458,41 +492,43 @@ func getTriggerLogic(ctx *sql.Context, a *Analyzer, n sql.Node, scope *plan.Scop
458
492
// fabricate one with the right properties (its child schema matches the table schema, with the right aliased name)
459
493
var triggerLogic sql.Node
460
494
var err error
495
+ var scopeNode * plan.Project
461
496
qFlags = nil
462
497
463
498
switch trigger .TriggerEvent {
464
499
case sqlparser .InsertStr :
465
- scopeNode : = plan .NewProject (
500
+ scopeNode = plan .NewProject (
466
501
[]sql.Expression {expression .NewStar ()},
467
502
plan .NewTableAlias ("new" , trigger .Table ),
468
503
)
469
504
s := (* plan .Scope )(nil ).NewScope (scopeNode ).WithMemos (scope .Memo (n ).MemoNodes ()).WithProcedureCache (scope .ProcedureCache ())
470
505
triggerLogic , _ , err = a .analyzeWithSelector (ctx , trigger .Body , s , SelectAllBatches , DefaultRuleSelector , qFlags )
471
506
case sqlparser .UpdateStr :
472
- var scopeNode * plan.Project
473
- if updateSrc := getUpdateJoinSource (n ); updateSrc == nil {
507
+ if updateJoin , isUpdateJoin := n .(* plan.Update ).Child .(* plan.UpdateJoin ); isUpdateJoin {
508
+ masked := getMaskedUpdateJoinProject (updateJoin , trigger )
509
+ // The scopeNode for an UpdateJoin should contain every node in the updateSource as new and old but should
510
+ // have placeholder expressions for non-triggered tables.
474
511
scopeNode = plan .NewProject (
475
512
[]sql.Expression {expression .NewStar ()},
476
513
plan .NewCrossJoin (
477
- plan .NewTableAlias ("old" , trigger . Table ),
478
- plan .NewTableAlias ("new" , trigger . Table ),
514
+ plan .NewSubqueryAlias ("old" , "" , masked ),
515
+ plan .NewSubqueryAlias ("new" , "" , masked ),
479
516
),
480
517
)
481
518
} else {
482
- // The scopeNode for an UpdateJoin should contain every node in the updateSource as new and old.
483
519
scopeNode = plan .NewProject (
484
520
[]sql.Expression {expression .NewStar ()},
485
521
plan .NewCrossJoin (
486
- plan .NewSubqueryAlias ("old" , "" , updateSrc . Child ),
487
- plan .NewSubqueryAlias ("new" , "" , updateSrc . Child ),
522
+ plan .NewTableAlias ("old" , trigger . Table ),
523
+ plan .NewTableAlias ("new" , trigger . Table ),
488
524
),
489
525
)
490
526
}
491
527
// Triggers are wrapped in prepend nodes, which means that the parent scope is included
492
528
s := (* plan .Scope )(nil ).NewScope (scopeNode ).WithMemos (scope .Memo (n ).MemoNodes ()).WithProcedureCache (scope .ProcedureCache ())
493
529
triggerLogic , _ , err = a .analyzeWithSelector (ctx , trigger .Body , s , SelectAllBatches , DefaultRuleSelector , qFlags )
494
530
case sqlparser .DeleteStr :
495
- scopeNode : = plan .NewProject (
531
+ scopeNode = plan .NewProject (
496
532
[]sql.Expression {expression .NewStar ()},
497
533
plan .NewTableAlias ("old" , trigger .Table ),
498
534
)
0 commit comments