github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/ddl_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  	"strings"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/dolthub/vitess/go/mysql"
    25  
    26  	gmstime "github.com/dolthub/go-mysql-server/internal/time"
    27  	"github.com/dolthub/go-mysql-server/sql"
    28  	"github.com/dolthub/go-mysql-server/sql/expression"
    29  	"github.com/dolthub/go-mysql-server/sql/types"
    30  )
    31  
    32  var _ sql.Node = (*CreateEvent)(nil)
    33  var _ sql.Expressioner = (*CreateEvent)(nil)
    34  var _ sql.Databaser = (*CreateEvent)(nil)
    35  var _ sql.EventSchedulerStatement = (*CreateEvent)(nil)
    36  
    37  type CreateEvent struct {
    38  	ddlNode
    39  	EventName        string
    40  	Definer          string
    41  	At               *OnScheduleTimestamp
    42  	Every            *expression.Interval
    43  	Starts           *OnScheduleTimestamp
    44  	Ends             *OnScheduleTimestamp
    45  	OnCompPreserve   bool
    46  	Status           sql.EventStatus
    47  	Comment          string
    48  	DefinitionString string
    49  	DefinitionNode   sql.Node
    50  	IfNotExists      bool
    51  	// eventScheduler is used to notify EventSchedulerStatus of the event creation
    52  	eventScheduler sql.EventScheduler
    53  }
    54  
    55  // NewCreateEvent returns a *CreateEvent node.
    56  func NewCreateEvent(
    57  	db sql.Database,
    58  	name, definer string,
    59  	at, starts, ends *OnScheduleTimestamp,
    60  	every *expression.Interval,
    61  	onCompletionPreserve bool,
    62  	status sql.EventStatus,
    63  	comment, definitionString string,
    64  	definition sql.Node,
    65  	ifNotExists bool,
    66  ) *CreateEvent {
    67  	return &CreateEvent{
    68  		ddlNode:          ddlNode{db},
    69  		EventName:        name,
    70  		Definer:          definer,
    71  		At:               at,
    72  		Every:            every,
    73  		Starts:           starts,
    74  		Ends:             ends,
    75  		OnCompPreserve:   onCompletionPreserve,
    76  		Status:           status,
    77  		Comment:          comment,
    78  		DefinitionString: definitionString,
    79  		DefinitionNode:   prepareCreateEventDefinitionNode(definition),
    80  		IfNotExists:      ifNotExists,
    81  	}
    82  }
    83  
    84  // Resolved implements the sql.Node interface.
    85  func (c *CreateEvent) Resolved() bool {
    86  	r := c.ddlNode.Resolved() && c.DefinitionNode.Resolved()
    87  	if c.At != nil {
    88  		r = r && c.At.Resolved()
    89  	} else {
    90  		r = r && c.Every.Resolved()
    91  		if c.Starts != nil {
    92  			r = r && c.Starts.Resolved()
    93  		}
    94  		if c.Ends != nil {
    95  			r = r && c.Ends.Resolved()
    96  		}
    97  	}
    98  	return r
    99  }
   100  
   101  func (c *CreateEvent) IsReadOnly() bool {
   102  	return false
   103  }
   104  
   105  // Schema implements the sql.Node interface.
   106  func (c *CreateEvent) Schema() sql.Schema {
   107  	return nil
   108  }
   109  
   110  // Children implements the sql.Node interface.
   111  func (c *CreateEvent) Children() []sql.Node {
   112  	return []sql.Node{c.DefinitionNode}
   113  }
   114  
   115  // WithChildren implements the sql.Node interface.
   116  func (c *CreateEvent) WithChildren(children ...sql.Node) (sql.Node, error) {
   117  	if len(children) != 1 {
   118  		return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1)
   119  	}
   120  
   121  	nc := *c
   122  	nc.DefinitionNode = prepareCreateEventDefinitionNode(children[0])
   123  
   124  	return &nc, nil
   125  }
   126  
   127  // CheckPrivileges implements the interface sql.Node.
   128  func (c *CreateEvent) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool {
   129  	subject := sql.PrivilegeCheckSubject{
   130  		Database: c.Db.Name(),
   131  	}
   132  	return opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Event))
   133  }
   134  
   135  // Database implements the sql.Databaser interface.
   136  func (c *CreateEvent) Database() sql.Database {
   137  	return c.Db
   138  }
   139  
   140  // WithDatabase implements the sql.Databaser interface.
   141  func (c *CreateEvent) WithDatabase(database sql.Database) (sql.Node, error) {
   142  	ce := *c
   143  	ce.Db = database
   144  	return &ce, nil
   145  }
   146  
   147  // String implements the sql.Node interface.
   148  func (c *CreateEvent) String() string {
   149  	definer := ""
   150  	if c.Definer != "" {
   151  		definer = fmt.Sprintf(" DEFINER = %s", c.Definer)
   152  	}
   153  
   154  	onSchedule := ""
   155  	if c.At != nil {
   156  		onSchedule = fmt.Sprintf(" ON SCHEDULE %s", c.At.String())
   157  	} else {
   158  		onSchedule = onScheduleEveryString(c.Every, c.Starts, c.Ends)
   159  	}
   160  
   161  	onCompletion := ""
   162  	if !c.OnCompPreserve {
   163  		onCompletion = " ON COMPLETION NOT PRESERVE"
   164  	}
   165  
   166  	comment := ""
   167  	if c.Comment != "" {
   168  		comment = fmt.Sprintf(" COMMENT '%s'", c.Comment)
   169  	}
   170  
   171  	return fmt.Sprintf("CREATE%s EVENT %s %s%s%s%s DO %s",
   172  		definer, c.EventName, onSchedule, onCompletion, c.Status.String(), comment, sql.DebugString(c.DefinitionNode))
   173  }
   174  
   175  // Expressions implements the sql.Expressioner interface.
   176  func (c *CreateEvent) Expressions() []sql.Expression {
   177  	if c.At != nil {
   178  		return []sql.Expression{c.At}
   179  	} else {
   180  		if c.Starts == nil && c.Ends == nil {
   181  			return []sql.Expression{c.Every}
   182  		} else if c.Starts == nil {
   183  			return []sql.Expression{c.Every, c.Ends}
   184  		} else if c.Ends == nil {
   185  			return []sql.Expression{c.Every, c.Starts}
   186  		} else {
   187  			return []sql.Expression{c.Every, c.Starts, c.Ends}
   188  		}
   189  	}
   190  }
   191  
   192  // WithExpressions implements the sql.Expressioner interface.
   193  func (c *CreateEvent) WithExpressions(e ...sql.Expression) (sql.Node, error) {
   194  	if len(e) < 1 {
   195  		return nil, sql.ErrInvalidChildrenNumber.New(c, len(e), "at least 1")
   196  	}
   197  
   198  	nc := *c
   199  	if c.At != nil {
   200  		ts, ok := e[0].(*OnScheduleTimestamp)
   201  		if !ok {
   202  			return nil, fmt.Errorf("expected `*OnScheduleTimestamp` but got `%T`", e[0])
   203  		}
   204  		nc.At = ts
   205  	} else {
   206  		every, ok := e[0].(*expression.Interval)
   207  		if !ok {
   208  			return nil, fmt.Errorf("expected `*expression.Interval` but got `%T`", e[0])
   209  		}
   210  		nc.Every = every
   211  
   212  		var ts *OnScheduleTimestamp
   213  		if len(e) > 1 {
   214  			ts, ok = e[1].(*OnScheduleTimestamp)
   215  			if !ok {
   216  				return nil, fmt.Errorf("expected `*OnScheduleTimestamp` but got `%T`", e[1])
   217  			}
   218  			if c.Starts != nil {
   219  				nc.Starts = ts
   220  			} else if c.Ends != nil {
   221  				nc.Ends = ts
   222  			}
   223  		}
   224  
   225  		if len(e) == 3 {
   226  			ts, ok = e[2].(*OnScheduleTimestamp)
   227  			if !ok {
   228  				return nil, fmt.Errorf("expected `*OnScheduleTimestamp` but got `%T`", e[2])
   229  			}
   230  			nc.Ends = ts
   231  		}
   232  	}
   233  
   234  	return &nc, nil
   235  }
   236  
   237  // RowIter implements the sql.Node interface.
   238  func (c *CreateEvent) RowIter(ctx *sql.Context, _ sql.Row) (sql.RowIter, error) {
   239  	eventCreationTime := ctx.QueryTime()
   240  	// TODO: event time values are evaluated in 'SYSTEM' TZ for now (should be session TZ)
   241  	eventDefinition, err := c.GetEventDefinition(ctx, eventCreationTime, eventCreationTime, time.Time{}, gmstime.SystemTimezoneOffset())
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  
   246  	eventDb, ok := c.Db.(sql.EventDatabase)
   247  	if !ok {
   248  		return nil, sql.ErrEventsNotSupported.New(c.Db.Name())
   249  	}
   250  
   251  	return &createEventIter{
   252  		event:          eventDefinition,
   253  		eventDb:        eventDb,
   254  		ifNotExists:    c.IfNotExists,
   255  		eventScheduler: c.eventScheduler,
   256  	}, nil
   257  }
   258  
   259  // WithEventScheduler is used to notify EventSchedulerStatus to update the events list for CREATE EVENT.
   260  func (c *CreateEvent) WithEventScheduler(scheduler sql.EventScheduler) sql.Node {
   261  	nc := *c
   262  	nc.eventScheduler = scheduler
   263  	return &nc
   264  }
   265  
   266  // GetEventDefinition returns an EventDefinition object with all of its fields populated from the details
   267  // of this CREATE EVENT statement.
   268  func (c *CreateEvent) GetEventDefinition(ctx *sql.Context, eventCreationTime, lastAltered, lastExecuted time.Time, tz string) (sql.EventDefinition, error) {
   269  	// TODO: support DISABLE ON SLAVE event status
   270  	if c.Status == sql.EventStatus_DisableOnSlave && ctx != nil && ctx.Session != nil {
   271  		ctx.Session.Warn(&sql.Warning{
   272  			Level:   "Warning",
   273  			Code:    mysql.ERNotSupportedYet,
   274  			Message: fmt.Sprintf("DISABLE ON SLAVE status is not supported yet, used DISABLE status instead."),
   275  		})
   276  		c.Status = sql.EventStatus_Disable
   277  	}
   278  
   279  	eventDefinition := sql.EventDefinition{
   280  		Name:                 c.EventName,
   281  		Definer:              c.Definer,
   282  		OnCompletionPreserve: c.OnCompPreserve,
   283  		Status:               c.Status.String(),
   284  		Comment:              c.Comment,
   285  		EventBody:            c.DefinitionString,
   286  		TimezoneOffset:       tz,
   287  	}
   288  
   289  	if c.At != nil {
   290  		var err error
   291  		eventDefinition.HasExecuteAt = true
   292  		eventDefinition.ExecuteAt, err = c.At.EvalTime(ctx, tz)
   293  		if err != nil {
   294  			return sql.EventDefinition{}, err
   295  		}
   296  	} else {
   297  		delta, err := c.Every.EvalDelta(ctx, nil)
   298  		if err != nil {
   299  			return sql.EventDefinition{}, err
   300  		}
   301  		interval := sql.NewEveryInterval(delta.Years, delta.Months, delta.Days, delta.Hours, delta.Minutes, delta.Seconds)
   302  		iVal, iField := interval.GetIntervalValAndField()
   303  		eventDefinition.ExecuteEvery = fmt.Sprintf("%s %s", iVal, iField)
   304  
   305  		if c.Starts != nil {
   306  			eventDefinition.Starts, err = c.Starts.EvalTime(ctx, tz)
   307  			if err != nil {
   308  				return sql.EventDefinition{}, err
   309  			}
   310  		} else {
   311  			// If STARTS is not defined, it defaults to CURRENT_TIMESTAMP
   312  			eventDefinition.Starts = eventCreationTime
   313  		}
   314  		if c.Ends != nil {
   315  			eventDefinition.HasEnds = true
   316  			eventDefinition.Ends, err = c.Ends.EvalTime(ctx, tz)
   317  			if err != nil {
   318  				return sql.EventDefinition{}, err
   319  			}
   320  		}
   321  	}
   322  
   323  	eventDefinition.CreatedAt = eventCreationTime
   324  	eventDefinition.LastAltered = lastAltered
   325  	eventDefinition.LastExecuted = lastExecuted
   326  	return eventDefinition, nil
   327  }
   328  
   329  // prepareCreateEventDefinitionNode fills in any missing ProcedureReference structures for
   330  // BeginEndBlocks in the event's definition.
   331  func prepareCreateEventDefinitionNode(definition sql.Node) sql.Node {
   332  	beginEndBlock, ok := definition.(*BeginEndBlock)
   333  	if !ok {
   334  		return definition
   335  	}
   336  
   337  	// NOTE: To execute a multi-statement event body in a BeginEndBlock, a ProcedureReference
   338  	//       must be set in the BeginEndBlock, but this currently only gets initialized in the
   339  	//       analyzer for ProcedureCalls, so we initialize it here.
   340  	// TODO: How does this work for triggers, which would have the same issue; seems like there
   341  	//       should be a cleaner way to handle this
   342  	beginEndBlock.Pref = expression.NewProcedureReference()
   343  
   344  	newChildren := make([]sql.Node, len(beginEndBlock.Children()))
   345  	for i, child := range beginEndBlock.Children() {
   346  		newChildren[i] = prepareCreateEventDefinitionNode(child)
   347  	}
   348  	newNode, _ := beginEndBlock.WithChildren(newChildren...)
   349  	return newNode
   350  }
   351  
   352  // createEventIter is the row iterator for *CreateEvent.
   353  type createEventIter struct {
   354  	once           sync.Once
   355  	event          sql.EventDefinition
   356  	eventDb        sql.EventDatabase
   357  	ifNotExists    bool
   358  	eventScheduler sql.EventScheduler
   359  }
   360  
   361  // Next implements the sql.RowIter interface.
   362  func (c *createEventIter) Next(ctx *sql.Context) (sql.Row, error) {
   363  	run := false
   364  	c.once.Do(func() {
   365  		run = true
   366  	})
   367  	if !run {
   368  		return nil, io.EOF
   369  	}
   370  
   371  	mode := sql.LoadSqlMode(ctx)
   372  	c.event.SqlMode = mode.String()
   373  
   374  	// checks if the defined ENDS time is before STARTS time
   375  	if c.event.HasEnds {
   376  		if c.event.Ends.Sub(c.event.Starts).Seconds() < 0 {
   377  			return nil, fmt.Errorf("ENDS is either invalid or before STARTS")
   378  		}
   379  	}
   380  
   381  	enabled, err := c.eventDb.SaveEvent(ctx, c.event)
   382  	if err != nil {
   383  		if sql.ErrEventAlreadyExists.Is(err) && c.ifNotExists {
   384  			ctx.Session.Warn(&sql.Warning{
   385  				Level:   "Note",
   386  				Code:    1537,
   387  				Message: fmt.Sprintf(err.Error()),
   388  			})
   389  			return sql.Row{types.NewOkResult(0)}, nil
   390  		}
   391  		return nil, err
   392  	}
   393  
   394  	if c.event.HasExecuteAt {
   395  		// If the event execution time is in the past and is set.
   396  		if c.event.ExecuteAt.Sub(c.event.CreatedAt).Seconds() <= -1 {
   397  			if c.event.OnCompletionPreserve && ctx != nil && ctx.Session != nil {
   398  				// If ON COMPLETION PRESERVE is defined, the event is disabled.
   399  				c.event.Status = sql.EventStatus_Disable.String()
   400  				_, err = c.eventDb.UpdateEvent(ctx, c.event.Name, c.event)
   401  				if err != nil {
   402  					return nil, err
   403  				}
   404  				ctx.Session.Warn(&sql.Warning{
   405  					Level:   "Note",
   406  					Code:    1544,
   407  					Message: "Event execution time is in the past. Event has been disabled",
   408  				})
   409  			} else {
   410  				// If ON COMPLETION NOT PRESERVE is defined, the event is dropped immediately after creation.
   411  				err = c.eventDb.DropEvent(ctx, c.event.Name)
   412  				if err != nil {
   413  					return nil, err
   414  				}
   415  				ctx.Session.Warn(&sql.Warning{
   416  					Level:   "Note",
   417  					Code:    1588,
   418  					Message: "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation.",
   419  				})
   420  			}
   421  			return sql.Row{types.NewOkResult(0)}, nil
   422  		}
   423  	}
   424  
   425  	// make sure to notify the EventSchedulerStatus AFTER adding the event in the database
   426  	if c.eventScheduler != nil && enabled {
   427  		c.eventScheduler.AddEvent(ctx, c.eventDb, c.event)
   428  	}
   429  
   430  	return sql.Row{types.NewOkResult(0)}, nil
   431  }
   432  
   433  // Close implements the sql.RowIter interface.
   434  func (c *createEventIter) Close(ctx *sql.Context) error {
   435  	return nil
   436  }
   437  
   438  // onScheduleEveryString returns ON SCHEDULE EVERY clause part of CREATE EVENT statement.
   439  func onScheduleEveryString(every sql.Expression, starts, ends *OnScheduleTimestamp) string {
   440  	everyInterval := strings.TrimPrefix(every.String(), "INTERVAL ")
   441  	startsStr := ""
   442  	if starts != nil {
   443  		startsStr = fmt.Sprintf(" %s", starts.String())
   444  	}
   445  	endsStr := ""
   446  	if ends != nil {
   447  		endsStr = fmt.Sprintf(" %s", ends.String())
   448  	}
   449  
   450  	return fmt.Sprintf("ON SCHEDULE EVERY %s%s%s", everyInterval, startsStr, endsStr)
   451  }
   452  
   453  // OnScheduleTimestamp is object used for EVENT ON SCHEDULE { AT / STARTS / ENDS } optional fields only.
   454  type OnScheduleTimestamp struct {
   455  	field     string
   456  	timestamp sql.Expression
   457  	intervals []sql.Expression
   458  }
   459  
   460  var _ sql.Expression = (*OnScheduleTimestamp)(nil)
   461  
   462  // NewOnScheduleTimestamp creates OnScheduleTimestamp object used for EVENT ON SCHEDULE { AT / STARTS / ENDS } optional fields only.
   463  func NewOnScheduleTimestamp(f string, ts sql.Expression, i []sql.Expression) *OnScheduleTimestamp {
   464  	return &OnScheduleTimestamp{
   465  		field:     f,
   466  		timestamp: ts,
   467  		intervals: i,
   468  	}
   469  }
   470  
   471  func (ost *OnScheduleTimestamp) IsReadOnly() bool {
   472  	return true
   473  }
   474  
   475  func (ost *OnScheduleTimestamp) Type() sql.Type {
   476  	return ost.timestamp.Type()
   477  }
   478  
   479  func (ost *OnScheduleTimestamp) IsNullable() bool {
   480  	if ost.timestamp.IsNullable() {
   481  		return true
   482  	}
   483  	for _, i := range ost.intervals {
   484  		if i.IsNullable() {
   485  			return true
   486  		}
   487  	}
   488  	return false
   489  }
   490  
   491  func (ost *OnScheduleTimestamp) Children() []sql.Expression {
   492  	var exprs = []sql.Expression{ost.timestamp}
   493  	return append(exprs, ost.intervals...)
   494  }
   495  
   496  func (ost *OnScheduleTimestamp) WithChildren(children ...sql.Expression) (sql.Expression, error) {
   497  	if len(children) == 0 {
   498  		return nil, sql.ErrInvalidChildrenNumber.New(ost, len(children), "at least 1")
   499  	}
   500  
   501  	var intervals = make([]sql.Expression, 0)
   502  	if len(children) > 1 {
   503  		intervals = append(intervals, children[1:]...)
   504  	}
   505  
   506  	return NewOnScheduleTimestamp(ost.field, children[0], intervals), nil
   507  }
   508  
   509  // Resolved implements the sql.Node interface.
   510  func (ost *OnScheduleTimestamp) Resolved() bool {
   511  	var children = []sql.Expression{ost.timestamp}
   512  	children = append(children, ost.intervals...)
   513  	for _, child := range children {
   514  		if !child.Resolved() {
   515  			return false
   516  		}
   517  	}
   518  	return true
   519  }
   520  
   521  // String implements the sql.Node interface.
   522  func (ost *OnScheduleTimestamp) String() string {
   523  	intervals := ""
   524  	for _, interval := range ost.intervals {
   525  		intervals = fmt.Sprintf("%s + %s", intervals, interval.String())
   526  	}
   527  	return fmt.Sprintf("%s %s%s", ost.field, ost.timestamp.String(), intervals)
   528  }
   529  
   530  func (ost *OnScheduleTimestamp) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
   531  	panic("OnScheduleTimestamp.Eval is just a placeholder method and should not be called directly")
   532  }
   533  
   534  // EvalTime returns time.Time value converted to UTC evaluating given expressions as expected to be time value
   535  // and optional interval values. The value returned is time.Time value from timestamp value plus all intervals given.
   536  func (ost *OnScheduleTimestamp) EvalTime(ctx *sql.Context, tz string) (time.Time, error) {
   537  	value, err := ost.timestamp.Eval(ctx, nil)
   538  	if err != nil {
   539  		return time.Time{}, err
   540  	}
   541  
   542  	if bs, ok := value.([]byte); ok {
   543  		value = string(bs)
   544  	}
   545  
   546  	var t time.Time
   547  	switch v := value.(type) {
   548  	case time.Time:
   549  		// TODO: check if this value is in session timezone
   550  		t = v
   551  	case string:
   552  		t, err = sql.GetTimeValueFromStringInput(ost.field, v)
   553  		if err != nil {
   554  			return time.Time{}, err
   555  		}
   556  	default:
   557  		return time.Time{}, fmt.Errorf("unexpected type: %s", v)
   558  	}
   559  
   560  	for _, interval := range ost.intervals {
   561  		i, ok := interval.(*expression.Interval)
   562  		if !ok {
   563  			return time.Time{}, fmt.Errorf("expected interval but got: %s", interval)
   564  		}
   565  
   566  		timeDelta, err := i.EvalDelta(ctx, nil)
   567  		if err != nil {
   568  			return time.Time{}, err
   569  		}
   570  		t = timeDelta.Add(t)
   571  	}
   572  
   573  	// truncates the timezone part from the time value and returns the time value in given TZ
   574  	truncatedVal, err := time.Parse(sql.EventDateSpaceTimeFormat, t.Format(sql.EventDateSpaceTimeFormat))
   575  	if err != nil {
   576  		return time.Time{}, err
   577  	}
   578  	return gmstime.ConvertTimeToLocation(truncatedVal, tz)
   579  }
   580  
   581  var _ sql.Node = (*DropEvent)(nil)
   582  var _ sql.Databaser = (*DropEvent)(nil)
   583  var _ sql.EventSchedulerStatement = (*DropEvent)(nil)
   584  
   585  type DropEvent struct {
   586  	ddlNode
   587  	EventName string
   588  	IfExists  bool
   589  	// eventScheduler is used to notify EventSchedulerStatus of the event deletion
   590  	eventScheduler sql.EventScheduler
   591  }
   592  
   593  // NewDropEvent creates a new *DropEvent node.
   594  func NewDropEvent(db sql.Database, eventName string, ifExists bool) *DropEvent {
   595  	return &DropEvent{
   596  		ddlNode:   ddlNode{db},
   597  		EventName: strings.ToLower(eventName),
   598  		IfExists:  ifExists,
   599  	}
   600  }
   601  
   602  // String implements the sql.Node interface.
   603  func (d *DropEvent) String() string {
   604  	ifExists := ""
   605  	if d.IfExists {
   606  		ifExists = "IF EXISTS "
   607  	}
   608  	return fmt.Sprintf("DROP PROCEDURE %s%s", ifExists, d.EventName)
   609  }
   610  
   611  // Schema implements the sql.Node interface.
   612  func (d *DropEvent) Schema() sql.Schema {
   613  	return nil
   614  }
   615  
   616  func (d *DropEvent) IsReadOnly() bool {
   617  	return false
   618  }
   619  
   620  // RowIter implements the sql.Node interface.
   621  func (d *DropEvent) RowIter(ctx *sql.Context, row sql.Row) (sql.RowIter, error) {
   622  	eventDb, ok := d.Db.(sql.EventDatabase)
   623  	if !ok {
   624  		if d.IfExists {
   625  			return sql.RowsToRowIter(), nil
   626  		} else {
   627  			return nil, sql.ErrEventsNotSupported.New(d.EventName)
   628  		}
   629  	}
   630  
   631  	// make sure to notify the EventSchedulerStatus before dropping the event in the database
   632  	if d.eventScheduler != nil {
   633  		d.eventScheduler.RemoveEvent(eventDb.Name(), d.EventName)
   634  	}
   635  
   636  	err := eventDb.DropEvent(ctx, d.EventName)
   637  	if d.IfExists && sql.ErrEventDoesNotExist.Is(err) && ctx != nil && ctx.Session != nil {
   638  		ctx.Session.Warn(&sql.Warning{
   639  			Level:   "Note",
   640  			Code:    1305,
   641  			Message: fmt.Sprintf("Event %s does not exist", d.EventName),
   642  		})
   643  	} else if err != nil {
   644  		return nil, err
   645  	}
   646  
   647  	return sql.RowsToRowIter(sql.Row{types.NewOkResult(0)}), nil
   648  }
   649  
   650  // WithChildren implements the sql.Node interface.
   651  func (d *DropEvent) WithChildren(children ...sql.Node) (sql.Node, error) {
   652  	return NillaryWithChildren(d, children...)
   653  }
   654  
   655  // CheckPrivileges implements the interface sql.Node.
   656  func (d *DropEvent) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool {
   657  	subject := sql.PrivilegeCheckSubject{
   658  		Database: d.Db.Name(),
   659  	}
   660  	return opChecker.UserHasPrivileges(ctx, sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Event))
   661  }
   662  
   663  // WithDatabase implements the sql.Databaser interface.
   664  func (d *DropEvent) WithDatabase(database sql.Database) (sql.Node, error) {
   665  	nde := *d
   666  	nde.Db = database
   667  	return &nde, nil
   668  }
   669  
   670  // WithEventScheduler is used to notify EventSchedulerStatus to update the events list for DROP EVENT.
   671  func (d *DropEvent) WithEventScheduler(scheduler sql.EventScheduler) sql.Node {
   672  	nd := *d
   673  	nd.eventScheduler = scheduler
   674  	return &nd
   675  }