github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/alter_event.go (about) 1 // Copyright 2023 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 "io" 20 "sync" 21 "time" 22 23 "github.com/dolthub/vitess/go/mysql" 24 25 gmstime "github.com/dolthub/go-mysql-server/internal/time" 26 "github.com/dolthub/go-mysql-server/sql" 27 "github.com/dolthub/go-mysql-server/sql/expression" 28 "github.com/dolthub/go-mysql-server/sql/types" 29 ) 30 31 var _ sql.Node = (*AlterEvent)(nil) 32 var _ sql.Expressioner = (*AlterEvent)(nil) 33 var _ sql.Databaser = (*AlterEvent)(nil) 34 var _ sql.EventSchedulerStatement = (*AlterEvent)(nil) 35 36 type AlterEvent struct { 37 ddlNode 38 EventName string 39 Definer string 40 41 AlterOnSchedule bool 42 At *OnScheduleTimestamp 43 Every *expression.Interval 44 Starts *OnScheduleTimestamp 45 Ends *OnScheduleTimestamp 46 47 AlterOnComp bool 48 OnCompPreserve bool 49 50 AlterName bool 51 RenameToDb string 52 RenameToName string 53 54 AlterStatus bool 55 Status sql.EventStatus 56 57 AlterComment bool 58 Comment string 59 60 AlterDefinition bool 61 DefinitionString string 62 DefinitionNode sql.Node 63 64 // Event will be set during analysis 65 Event sql.EventDefinition 66 67 // scheduler is used to notify EventSchedulerStatus of the event update 68 scheduler sql.EventScheduler 69 } 70 71 // NewAlterEvent returns a *AlterEvent node. 72 func NewAlterEvent( 73 db sql.Database, 74 name, definer string, 75 alterSchedule bool, 76 at, starts, ends *OnScheduleTimestamp, 77 every *expression.Interval, 78 alterOnComp bool, 79 onCompletionPreserve bool, 80 alterName bool, 81 newName string, 82 alterStatus bool, 83 status sql.EventStatus, 84 alterComment bool, 85 comment string, 86 alterDefinition bool, 87 definitionString string, 88 definition sql.Node, 89 ) *AlterEvent { 90 return &AlterEvent{ 91 ddlNode: ddlNode{db}, 92 EventName: name, 93 Definer: definer, 94 AlterOnSchedule: alterSchedule, 95 At: at, 96 Every: every, 97 Starts: starts, 98 Ends: ends, 99 AlterOnComp: alterOnComp, 100 OnCompPreserve: onCompletionPreserve, 101 AlterName: alterName, 102 RenameToDb: "", // TODO: moving events across dbs is not supported yet 103 RenameToName: newName, 104 AlterStatus: alterStatus, 105 Status: status, 106 AlterComment: alterComment, 107 Comment: comment, 108 AlterDefinition: alterDefinition, 109 DefinitionString: definitionString, 110 DefinitionNode: definition, 111 } 112 } 113 114 // String implements the sql.Node interface. 115 func (a *AlterEvent) String() string { 116 stmt := "ALTER" 117 118 if a.Definer != "" { 119 stmt = fmt.Sprintf("%s DEFINER = %s", stmt, a.Definer) 120 } 121 122 stmt = fmt.Sprintf("%s EVENT", stmt) 123 124 if a.AlterOnSchedule { 125 if a.At != nil { 126 stmt = fmt.Sprintf("%s ON SCHEDULE AT %s", stmt, a.At.String()) 127 } else { 128 stmt = fmt.Sprintf("%s %s", stmt, onScheduleEveryString(a.Every, a.Starts, a.Ends)) 129 } 130 } 131 132 if a.AlterOnComp { 133 onComp := "NOT PRESERVE" 134 if a.OnCompPreserve { 135 onComp = "PRESERVE" 136 } 137 stmt = fmt.Sprintf("%s ON COMPLETION %s", stmt, onComp) 138 } 139 140 if a.AlterName { 141 // rename event database (moving event) is not supported yet 142 stmt = fmt.Sprintf("%s RENAMTE TO %s", stmt, a.RenameToName) 143 } 144 145 if a.AlterStatus { 146 stmt = fmt.Sprintf("%s %s", stmt, a.Status.String()) 147 } 148 149 if a.AlterComment { 150 if a.Comment != "" { 151 stmt = fmt.Sprintf("%s COMMENT %s", stmt, a.Comment) 152 } 153 } 154 155 if a.AlterDefinition { 156 stmt = fmt.Sprintf("%s DO %s", stmt, sql.DebugString(a.DefinitionNode)) 157 } 158 159 return stmt 160 } 161 162 // Resolved implements the sql.Node interface. 163 func (a *AlterEvent) Resolved() bool { 164 r := a.ddlNode.Resolved() 165 166 if a.AlterDefinition { 167 r = r && a.DefinitionNode.Resolved() 168 } 169 if a.AlterOnSchedule { 170 if a.At != nil { 171 r = r && a.At.Resolved() 172 } else { 173 r = r && a.Every.Resolved() 174 if a.Starts != nil { 175 r = r && a.Starts.Resolved() 176 } 177 if a.Ends != nil { 178 r = r && a.Ends.Resolved() 179 } 180 } 181 } 182 return r 183 } 184 185 // Schema implements the sql.Node interface. 186 func (a *AlterEvent) Schema() sql.Schema { 187 return nil 188 } 189 190 func (a *AlterEvent) IsReadOnly() bool { 191 return false 192 } 193 194 // Children implements the sql.Node interface. 195 func (a *AlterEvent) Children() []sql.Node { 196 if a.AlterDefinition { 197 return []sql.Node{a.DefinitionNode} 198 } 199 return nil 200 } 201 202 // WithChildren implements the sql.Node interface. 203 func (a *AlterEvent) WithChildren(children ...sql.Node) (sql.Node, error) { 204 if len(children) > 1 { 205 return nil, sql.ErrInvalidChildrenNumber.New(a, len(children), "0 or 1") 206 } 207 208 if !a.AlterDefinition { 209 return a, nil 210 } 211 212 na := *a 213 na.DefinitionNode = children[0] 214 return &na, nil 215 } 216 217 // CheckPrivileges implements the sql.Node interface. 218 func (a *AlterEvent) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool { 219 subject := sql.PrivilegeCheckSubject{Database: a.Db.Name()} 220 hasPriv := opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Event)) 221 222 if a.AlterName && a.RenameToDb != "" { 223 subject = sql.PrivilegeCheckSubject{Database: a.RenameToDb} 224 hasPriv = hasPriv && opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Event)) 225 } 226 return hasPriv 227 } 228 229 // Database implements the sql.Databaser interface. 230 func (a *AlterEvent) Database() sql.Database { 231 return a.Db 232 } 233 234 // WithDatabase implements the sql.Databaser interface. 235 func (a *AlterEvent) WithDatabase(database sql.Database) (sql.Node, error) { 236 ae := *a 237 ae.Db = database 238 return &ae, nil 239 } 240 241 // RowIter implements the sql.Node interface. 242 func (a *AlterEvent) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) { 243 eventDb, ok := a.Db.(sql.EventDatabase) 244 if !ok { 245 return nil, sql.ErrEventsNotSupported.New(a.Db.Name()) 246 } 247 248 // sanity check that Event was successfully loaded in analyzer 249 if a.Event.Name == "" { 250 return nil, fmt.Errorf("error loading existing event to alter from the database") 251 } 252 var err error 253 ed := a.Event 254 eventAlteredTime := ctx.QueryTime() 255 sysTz := gmstime.SystemTimezoneOffset() 256 ed.LastAltered = eventAlteredTime 257 ed.Definer = a.Definer 258 259 if a.AlterOnSchedule { 260 if a.At != nil { 261 ed.HasExecuteAt = true 262 ed.ExecuteAt, err = a.At.EvalTime(ctx, sysTz) 263 if err != nil { 264 return nil, err 265 } 266 // if Schedule was defined using EVERY previously, clear its fields 267 ed.ExecuteEvery = "" 268 ed.Starts = time.Time{} 269 ed.Ends = time.Time{} 270 ed.HasEnds = false 271 } else { 272 delta, err := a.Every.EvalDelta(ctx, nil) 273 if err != nil { 274 return nil, err 275 } 276 interval := sql.NewEveryInterval(delta.Years, delta.Months, delta.Days, delta.Hours, delta.Minutes, delta.Seconds) 277 iVal, iField := interval.GetIntervalValAndField() 278 ed.ExecuteEvery = fmt.Sprintf("%s %s", iVal, iField) 279 280 if a.Starts != nil { 281 ed.Starts, err = a.Starts.EvalTime(ctx, sysTz) 282 if err != nil { 283 return nil, err 284 } 285 } else { 286 // If STARTS is not defined, it defaults to CURRENT_TIMESTAMP 287 ed.Starts = eventAlteredTime 288 } 289 if a.Ends != nil { 290 ed.HasEnds = true 291 ed.Ends, err = a.Ends.EvalTime(ctx, sysTz) 292 if err != nil { 293 return nil, err 294 } 295 } 296 // if Schedule was defined using AT previously, clear its fields 297 ed.HasExecuteAt = false 298 ed.ExecuteAt = time.Time{} 299 } 300 } 301 if a.AlterOnComp { 302 ed.OnCompletionPreserve = a.OnCompPreserve 303 } 304 if a.AlterName { 305 ed.Name = a.RenameToName 306 } 307 if a.AlterStatus { 308 // TODO: support DISABLE ON SLAVE event status 309 if a.Status == sql.EventStatus_DisableOnSlave && ctx != nil && ctx.Session != nil { 310 ctx.Session.Warn(&sql.Warning{ 311 Level: "Warning", 312 Code: mysql.ERNotSupportedYet, 313 Message: fmt.Sprintf("DISABLE ON SLAVE status is not supported yet, used DISABLE status instead."), 314 }) 315 ed.Status = sql.EventStatus_Disable.String() 316 } else { 317 ed.Status = a.Status.String() 318 } 319 } 320 if a.AlterComment { 321 ed.Comment = a.Comment 322 } 323 if a.AlterDefinition { 324 ed.EventBody = a.DefinitionString 325 } 326 327 return &alterEventIter{ 328 originalName: a.EventName, 329 alterSchedule: a.AlterOnSchedule, 330 alterStatus: a.AlterStatus, 331 event: ed, 332 eventDb: eventDb, 333 scheduler: a.scheduler, 334 }, nil 335 } 336 337 // Expressions implements the sql.Expressioner interface. 338 func (a *AlterEvent) Expressions() []sql.Expression { 339 if a.AlterOnSchedule { 340 if a.At != nil { 341 return []sql.Expression{a.At} 342 } else { 343 if a.Starts == nil && a.Ends == nil { 344 return []sql.Expression{a.Every} 345 } else if a.Starts == nil { 346 return []sql.Expression{a.Every, a.Ends} 347 } else if a.Ends == nil { 348 return []sql.Expression{a.Every, a.Starts} 349 } else { 350 return []sql.Expression{a.Every, a.Starts, a.Ends} 351 } 352 } 353 } 354 return nil 355 } 356 357 // WithExpressions implements the sql.Expressioner interface. 358 func (a *AlterEvent) WithExpressions(e ...sql.Expression) (sql.Node, error) { 359 if len(e) > 3 { 360 return nil, sql.ErrInvalidChildrenNumber.New(a, len(e), "up to 3") 361 } 362 363 if !a.AlterOnSchedule { 364 return a, nil 365 } 366 367 na := *a 368 if a.At != nil { 369 ts, ok := e[0].(*OnScheduleTimestamp) 370 if !ok { 371 return nil, fmt.Errorf("expected `*OnScheduleTimestamp` but got `%T`", e[0]) 372 } 373 na.At = ts 374 } else { 375 every, ok := e[0].(*expression.Interval) 376 if !ok { 377 return nil, fmt.Errorf("expected `*expression.Interval` but got `%T`", e[0]) 378 } 379 na.Every = every 380 381 var ts *OnScheduleTimestamp 382 if len(e) > 1 { 383 ts, ok = e[1].(*OnScheduleTimestamp) 384 if !ok { 385 return nil, fmt.Errorf("expected `*OnScheduleTimestamp` but got `%T`", e[1]) 386 } 387 if a.Starts != nil { 388 na.Starts = ts 389 } else if a.Ends != nil { 390 na.Ends = ts 391 } 392 } 393 394 if len(e) == 3 { 395 ts, ok = e[2].(*OnScheduleTimestamp) 396 if !ok { 397 return nil, fmt.Errorf("expected `*OnScheduleTimestamp` but got `%T`", e[2]) 398 } 399 na.Ends = ts 400 } 401 } 402 403 return &na, nil 404 } 405 406 // WithEventScheduler is used to notify EventSchedulerStatus to update the events list for ALTER EVENT. 407 func (a *AlterEvent) WithEventScheduler(scheduler sql.EventScheduler) sql.Node { 408 na := *a 409 na.scheduler = scheduler 410 return &na 411 } 412 413 // alterEventIter is the row iterator for *CreateEvent. 414 type alterEventIter struct { 415 once sync.Once 416 originalName string 417 alterSchedule bool 418 alterStatus bool 419 event sql.EventDefinition 420 eventDb sql.EventDatabase 421 scheduler sql.EventScheduler 422 } 423 424 // Next implements the sql.RowIter interface. 425 func (a *alterEventIter) Next(ctx *sql.Context) (sql.Row, error) { 426 run := false 427 a.once.Do(func() { 428 run = true 429 }) 430 if !run { 431 return nil, io.EOF 432 } 433 434 var eventEndingTime time.Time 435 if a.event.HasExecuteAt { 436 eventEndingTime = a.event.ExecuteAt 437 } else if a.event.HasEnds { 438 eventEndingTime = a.event.Ends 439 } 440 441 if (a.event.HasExecuteAt || a.event.HasEnds) && eventEndingTime.Sub(a.event.LastAltered).Seconds() < 0 { 442 // If the event execution/end time is altered and in the past. 443 if a.alterSchedule { 444 if a.event.OnCompletionPreserve && ctx != nil && ctx.Session != nil { 445 // If ON COMPLETION PRESERVE is defined, the event is disabled. 446 a.event.Status = sql.EventStatus_Disable.String() 447 ctx.Session.Warn(&sql.Warning{ 448 Level: "Note", 449 Code: 1544, 450 Message: "Event execution time is in the past. Event has been disabled", 451 }) 452 } else { 453 return nil, fmt.Errorf("Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was not changed. Specify a time in the future.") 454 } 455 } 456 457 if a.alterStatus { 458 if a.event.OnCompletionPreserve { 459 // If the event execution/end time is in the past and is ON COMPLETION PRESERVE, status must stay as DISABLE. 460 a.event.Status = sql.EventStatus_Disable.String() 461 } else { 462 // If event status was set to ENABLE and ON COMPLETION NOT PRESERVE, it gets dropped. 463 // make sure to notify the EventSchedulerStatus before dropping the event in the database 464 if a.scheduler != nil { 465 a.scheduler.RemoveEvent(a.eventDb.Name(), a.originalName) 466 } 467 err := a.eventDb.DropEvent(ctx, a.originalName) 468 if err != nil { 469 return nil, err 470 } 471 return sql.Row{types.NewOkResult(0)}, nil 472 } 473 } 474 } 475 476 enabled, err := a.eventDb.UpdateEvent(ctx, a.originalName, a.event) 477 if err != nil { 478 return nil, err 479 } 480 481 // make sure to notify the EventSchedulerStatus after updating the event in the database 482 if a.scheduler != nil && enabled { 483 a.scheduler.UpdateEvent(ctx, a.eventDb, a.originalName, a.event) 484 } 485 486 return sql.Row{types.NewOkResult(0)}, nil 487 } 488 489 // Close implements the sql.RowIter interface. 490 func (a *alterEventIter) Close(_ *sql.Context) error { 491 return nil 492 }