github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/ddl_trigger.go (about) 1 // Copyright 2020-2021 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package plan 16 17 import ( 18 "fmt" 19 "time" 20 21 "github.com/dolthub/vitess/go/vt/sqlparser" 22 23 "github.com/dolthub/go-mysql-server/sql" 24 ) 25 26 type TriggerOrder struct { 27 PrecedesOrFollows string // PrecedesStr, FollowsStr 28 OtherTriggerName string 29 } 30 31 type CreateTrigger struct { 32 ddlNode 33 TriggerName string 34 TriggerTime string 35 TriggerEvent string 36 TriggerOrder *TriggerOrder 37 Table sql.Node 38 Body sql.Node 39 CreateTriggerString string 40 BodyString string 41 CreatedAt time.Time 42 Definer string 43 SqlMode string 44 } 45 46 var _ sql.Node = (*CreateTrigger)(nil) 47 var _ sql.CollationCoercible = (*CreateTrigger)(nil) 48 49 func NewCreateTrigger(triggerDb sql.Database, 50 triggerName, 51 triggerTime, 52 triggerEvent string, 53 triggerOrder *TriggerOrder, 54 table sql.Node, 55 body sql.Node, 56 createTriggerString, 57 bodyString string, 58 createdAt time.Time, 59 definer string) *CreateTrigger { 60 return &CreateTrigger{ 61 ddlNode: ddlNode{Db: triggerDb}, 62 TriggerName: triggerName, 63 TriggerTime: triggerTime, 64 TriggerEvent: triggerEvent, 65 TriggerOrder: triggerOrder, 66 Table: table, 67 Body: body, 68 BodyString: bodyString, 69 CreateTriggerString: createTriggerString, 70 CreatedAt: createdAt, 71 Definer: definer, 72 } 73 } 74 75 func (c *CreateTrigger) Database() sql.Database { 76 return c.Db 77 } 78 79 func (c *CreateTrigger) WithDatabase(database sql.Database) (sql.Node, error) { 80 ct := *c 81 ct.Db = database 82 return &ct, nil 83 } 84 85 func (c *CreateTrigger) Resolved() bool { 86 // c.Body can be unresolved since it can have unresolved table reference to non-existent table 87 return c.ddlNode.Resolved() && c.Table.Resolved() 88 } 89 90 func (c *CreateTrigger) IsReadOnly() bool { 91 return false 92 } 93 94 func (c *CreateTrigger) Schema() sql.Schema { 95 return nil 96 } 97 98 func (c *CreateTrigger) Children() []sql.Node { 99 return []sql.Node{c.Table} 100 } 101 102 func (c *CreateTrigger) WithChildren(children ...sql.Node) (sql.Node, error) { 103 if len(children) != 1 { 104 return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1) 105 } 106 107 nc := *c 108 nc.Table = children[0] 109 return &nc, nil 110 } 111 112 // CheckPrivileges implements the interface sql.Node. 113 func (c *CreateTrigger) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool { 114 subject := sql.PrivilegeCheckSubject{ 115 Database: GetDatabaseName(c.Table), 116 Table: getTableName(c.Table), 117 } 118 119 return opChecker.UserHasPrivileges(ctx, 120 sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Trigger)) 121 } 122 123 // CollationCoercibility implements the interface sql.CollationCoercible. 124 func (*CreateTrigger) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) { 125 return sql.Collation_binary, 7 126 } 127 128 func (c *CreateTrigger) String() string { 129 order := "" 130 if c.TriggerOrder != nil { 131 order = fmt.Sprintf("%s %s ", c.TriggerOrder.PrecedesOrFollows, c.TriggerOrder.OtherTriggerName) 132 } 133 return fmt.Sprintf("CREATE TRIGGER %s %s %s ON %s FOR EACH ROW %s%s", c.TriggerName, c.TriggerTime, c.TriggerEvent, c.Table, order, c.Body) 134 } 135 136 func (c *CreateTrigger) DebugString() string { 137 order := "" 138 if c.TriggerOrder != nil { 139 order = fmt.Sprintf("%s %s ", c.TriggerOrder.PrecedesOrFollows, c.TriggerOrder.OtherTriggerName) 140 } 141 return fmt.Sprintf("CREATE TRIGGER %s %s %s ON %s FOR EACH ROW %s%s", c.TriggerName, c.TriggerTime, c.TriggerEvent, sql.DebugString(c.Table), order, sql.DebugString(c.Body)) 142 } 143 144 // OrderTriggers is a utility method that first sorts triggers into their precedence. It then splits the triggers into 145 // before and after pairs. 146 func OrderTriggers(triggers []*CreateTrigger) (beforeTriggers []*CreateTrigger, afterTriggers []*CreateTrigger) { 147 orderedTriggers := make([]*CreateTrigger, len(triggers)) 148 copy(orderedTriggers, triggers) 149 150 Top: 151 for i, trigger := range triggers { 152 if trigger.TriggerOrder != nil { 153 ref := trigger.TriggerOrder.OtherTriggerName 154 // remove the trigger from the slice 155 orderedTriggers = append(orderedTriggers[:i], orderedTriggers[i+1:]...) 156 // then find where to reinsert it 157 for j, t := range orderedTriggers { 158 if t.TriggerName == ref { 159 if trigger.TriggerOrder.PrecedesOrFollows == sqlparser.PrecedesStr { 160 orderedTriggers = append(orderedTriggers[:j], append(triggers[i:i+1], orderedTriggers[j:]...)...) 161 } else if trigger.TriggerOrder.PrecedesOrFollows == sqlparser.FollowsStr { 162 if len(orderedTriggers) == j-1 { 163 orderedTriggers = append(orderedTriggers, triggers[i]) 164 } else { 165 orderedTriggers = append(orderedTriggers[:j+1], append(triggers[i:i+1], orderedTriggers[j+1:]...)...) 166 } 167 } else { 168 panic("unexpected value for trigger order") 169 } 170 continue Top 171 } 172 } 173 panic(fmt.Sprintf("Referenced trigger %s not found", ref)) 174 } 175 } 176 177 // Now that we have ordered the triggers according to precedence, split them into BEFORE / AFTER triggers 178 for _, trigger := range orderedTriggers { 179 if trigger.TriggerTime == sqlparser.BeforeStr { 180 beforeTriggers = append(beforeTriggers, trigger) 181 } else { 182 afterTriggers = append(afterTriggers, trigger) 183 } 184 } 185 186 return beforeTriggers, afterTriggers 187 }