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  }