diff --git a/ast/ddl.go b/ast/ddl.go index aa87de3..ab75ccb 100644 --- a/ast/ddl.go +++ b/ast/ddl.go @@ -1853,6 +1853,9 @@ type CreateMaskingPolicyStmt struct { Expr ExprNode RestrictOps MaskingPolicyRestrictOps MaskingPolicyState MaskingPolicyState + // Using selects the USING (expr) spelling of the masking expression + // clause over AS expr. + Using bool } // Restore implements Node interface. @@ -1875,9 +1878,18 @@ func (n *CreateMaskingPolicyStmt) Restore(ctx *format.RestoreCtx) error { return annotate(err, "An error occurred while restore CreateMaskingPolicyStmt.Column") } ctx.WritePlain(") ") - ctx.WriteKeyWord("AS ") - if err := n.Expr.Restore(ctx); err != nil { - return annotate(err, "An error occurred while restore CreateMaskingPolicyStmt.Expr") + if n.Using { + ctx.WriteKeyWord("USING ") + ctx.WritePlain("(") + if err := n.Expr.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateMaskingPolicyStmt.Expr") + } + ctx.WritePlain(")") + } else { + ctx.WriteKeyWord("AS ") + if err := n.Expr.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateMaskingPolicyStmt.Expr") + } } if n.RestrictOps != MaskingPolicyRestrictOpNone { ctx.WritePlain(" ") @@ -1925,6 +1937,35 @@ func (n *CreateMaskingPolicyStmt) Accept(v Visitor) (Node, bool) { return v.Leave(n) } +// DropMaskingPolicyStmt is a statement to drop a masking policy: +// DROP MASKING POLICY [IF EXISTS] policy_name. +type DropMaskingPolicyStmt struct { + ddlNode + + IfExists bool + PolicyName CIStr +} + +// Restore implements Node interface. +func (n *DropMaskingPolicyStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP MASKING POLICY ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + ctx.WriteName(n.PolicyName.O) + return nil +} + +// Accept implements Node Accept interface. +func (n *DropMaskingPolicyStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropMaskingPolicyStmt) + return v.Leave(n) +} + // CreateResourceGroupStmt is a statement to create a policy. type CreateResourceGroupStmt struct { ddlNode diff --git a/ast/dml.go b/ast/dml.go index d46a3ca..1daf481 100644 --- a/ast/dml.go +++ b/ast/dml.go @@ -3896,12 +3896,32 @@ type SelectIntoOption struct { FileName string FieldsInfo *FieldsClause LinesInfo *LinesClause + // Vars is the variable list of the SelectIntoVars form: user + // variables and, in stored programs, program variables (restored as + // plain names). + Vars []ExprNode } // Restore implements Node interface. func (n *SelectIntoOption) Restore(ctx *format.RestoreCtx) error { - if n.Tp != SelectIntoOutfile { - // only support SELECT/TABLE/VALUES ... INTO OUTFILE statement now + switch n.Tp { + case SelectIntoOutfile: + case SelectIntoDumpfile: + ctx.WriteKeyWord("INTO DUMPFILE ") + ctx.WriteString(n.FileName) + return nil + case SelectIntoVars: + ctx.WriteKeyWord("INTO ") + for i, v := range n.Vars { + if i != 0 { + ctx.WritePlain(", ") + } + if err := v.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore SelectInto.Vars[%d]", i) + } + } + return nil + default: return errors.New("Unsupported SelectionInto type") } @@ -3926,6 +3946,14 @@ func (n *SelectIntoOption) Accept(v Visitor) (Node, bool) { if skipChildren { return v.Leave(newNode) } + n = newNode.(*SelectIntoOption) + for i, v2 := range n.Vars { + node, ok := v2.Accept(v) + if !ok { + return n, false + } + n.Vars[i] = node.(ExprNode) + } return v.Leave(n) } diff --git a/ast/misc.go b/ast/misc.go index 243d5d8..8514af0 100644 --- a/ast/misc.go +++ b/ast/misc.go @@ -868,6 +868,10 @@ type ReplicationSourceOption struct { // Restore implements Node interface. func (n *ReplicationSourceOption) Restore(ctx *format.RestoreCtx) error { ctx.WriteKeyWord(n.Name) + if n.Value == nil { + // A bare option name (START REPLICA UNTIL SQL_AFTER_MTS_GAPS). + return nil + } ctx.WritePlain(" = ") if err := n.Value.Restore(ctx); err != nil { return fmt.Errorf("an error occurred while restore ReplicationSourceOption.Value: %w", err) @@ -2063,18 +2067,51 @@ func (n *AlterUserStmt) Accept(v Visitor) (Node, bool) { // AlterInstanceStmt modifies instance. // See https://dev.mysql.com/doc/refman/8.0/en/alter-instance.html +// Exactly one of the action fields is set: ReloadTLS (with optional +// Channel and NoRollbackOnError), RotateInnoDBMasterKey, +// RotateBinlogMasterKey, ReloadKeyring, EnableInnoDBRedoLog, or +// DisableInnoDBRedoLog. type AlterInstanceStmt struct { stmtNode ReloadTLS bool NoRollbackOnError bool + // Channel is the FOR CHANNEL name of RELOAD TLS (mysql_main or + // mysql_admin); empty when absent. + Channel string + RotateInnoDBMasterKey bool + RotateBinlogMasterKey bool + ReloadKeyring bool + EnableInnoDBRedoLog bool + DisableInnoDBRedoLog bool } // Restore implements Node interface. func (n *AlterInstanceStmt) Restore(ctx *format.RestoreCtx) error { ctx.WriteKeyWord("ALTER INSTANCE") + switch { + case n.RotateInnoDBMasterKey: + ctx.WriteKeyWord(" ROTATE INNODB MASTER KEY") + return nil + case n.RotateBinlogMasterKey: + ctx.WriteKeyWord(" ROTATE BINLOG MASTER KEY") + return nil + case n.ReloadKeyring: + ctx.WriteKeyWord(" RELOAD KEYRING") + return nil + case n.EnableInnoDBRedoLog: + ctx.WriteKeyWord(" ENABLE INNODB REDO_LOG") + return nil + case n.DisableInnoDBRedoLog: + ctx.WriteKeyWord(" DISABLE INNODB REDO_LOG") + return nil + } if n.ReloadTLS { ctx.WriteKeyWord(" RELOAD TLS") + if n.Channel != "" { + ctx.WriteKeyWord(" FOR CHANNEL ") + ctx.WriteName(n.Channel) + } } if n.NoRollbackOnError { ctx.WriteKeyWord(" NO ROLLBACK ON ERROR") diff --git a/ast/mysql_ddl.go b/ast/mysql_ddl.go new file mode 100644 index 0000000..666c229 --- /dev/null +++ b/ast/mysql_ddl.go @@ -0,0 +1,1833 @@ +// Copyright 2026 The sqlc Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package ast + +import ( + "io" + + "github.com/sqlc-dev/marino/auth" + "github.com/sqlc-dev/marino/format" + "github.com/sqlc-dev/marino/types" +) + +// The MySQL data definition statements that postdate the goyacc grammar +// (MySQL 26.7 §15.1): events, triggers, stored functions (and the ALTER +// forms of stored routines), ALTER VIEW, servers, tablespaces, logfile +// groups, spatial reference systems, libraries, and JSON duality views. +// (The masking policy statements live in ddl.go with the other masking +// nodes, and AlterInstanceStmt in misc.go.) + +var ( + _ ExprNode = &JSONDualityObjectExpr{} + + _ DDLNode = &CreateEventStmt{} + _ DDLNode = &AlterEventStmt{} + _ DDLNode = &DropEventStmt{} + _ DDLNode = &CreateTriggerStmt{} + _ DDLNode = &DropTriggerStmt{} + _ DDLNode = &CreateFunctionStmt{} + _ DDLNode = &AlterFunctionStmt{} + _ DDLNode = &DropFunctionStmt{} + _ DDLNode = &AlterProcedureStmt{} + _ DDLNode = &AlterViewStmt{} + _ DDLNode = &CreateServerStmt{} + _ DDLNode = &AlterServerStmt{} + _ DDLNode = &DropServerStmt{} + _ DDLNode = &CreateTablespaceStmt{} + _ DDLNode = &AlterTablespaceStmt{} + _ DDLNode = &DropTablespaceStmt{} + _ DDLNode = &CreateLogfileGroupStmt{} + _ DDLNode = &AlterLogfileGroupStmt{} + _ DDLNode = &DropLogfileGroupStmt{} + _ DDLNode = &CreateSpatialReferenceSystemStmt{} + _ DDLNode = &DropSpatialReferenceSystemStmt{} + _ DDLNode = &CreateLibraryStmt{} + _ DDLNode = &AlterLibraryStmt{} + _ DDLNode = &DropLibraryStmt{} + _ DDLNode = &CreateJSONDualityViewStmt{} + _ DDLNode = &AlterJSONDualityViewStmt{} + + _ StmtNode = &ReturnStmt{} +) + +// restoreDefiner writes a DEFINER = user clause followed by one space. +func restoreDefiner(ctx *format.RestoreCtx, definer *auth.UserIdentity, parent string) error { + ctx.WriteKeyWord("DEFINER ") + ctx.WritePlain("= ") + if err := definer.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore %s.Definer", parent) + } + ctx.WritePlain(" ") + return nil +} + +/* + * Events + */ + +// EventSchedule is the ON SCHEDULE clause of CREATE/ALTER EVENT: +// +// AT timestamp +// | EVERY interval [STARTS timestamp] [ENDS timestamp] +// +// At is set for the AT form; otherwise Every holds the interval quantity +// and Unit its time unit, with optional Starts and Ends timestamps. +type EventSchedule struct { + At ExprNode + Every ExprNode + Unit TimeUnitType + Starts ExprNode + Ends ExprNode +} + +// Restore implements Node interface. +func (n *EventSchedule) Restore(ctx *format.RestoreCtx) error { + if n.At != nil { + ctx.WriteKeyWord("AT ") + if err := n.At.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore EventSchedule.At") + } + return nil + } + ctx.WriteKeyWord("EVERY ") + if err := n.Every.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore EventSchedule.Every") + } + ctx.WritePlain(" ") + ctx.WriteKeyWord(n.Unit.String()) + if n.Starts != nil { + ctx.WriteKeyWord(" STARTS ") + if err := n.Starts.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore EventSchedule.Starts") + } + } + if n.Ends != nil { + ctx.WriteKeyWord(" ENDS ") + if err := n.Ends.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore EventSchedule.Ends") + } + } + return nil +} + +func (n *EventSchedule) accept(v Visitor) bool { + for _, e := range []*ExprNode{&n.At, &n.Every, &n.Starts, &n.Ends} { + if *e == nil { + continue + } + node, ok := (*e).Accept(v) + if !ok { + return false + } + *e = node.(ExprNode) + } + return true +} + +// EventCompletion is the ON COMPLETION clause of CREATE/ALTER EVENT. +type EventCompletion int + +const ( + // EventCompletionDefault omits the clause. + EventCompletionDefault EventCompletion = iota + // EventCompletionPreserve is ON COMPLETION PRESERVE. + EventCompletionPreserve + // EventCompletionNotPreserve is ON COMPLETION NOT PRESERVE. + EventCompletionNotPreserve +) + +// EventState is the ENABLE/DISABLE clause of CREATE/ALTER EVENT. +type EventState int + +const ( + // EventStateDefault omits the clause. + EventStateDefault EventState = iota + // EventStateEnable is ENABLE. + EventStateEnable + // EventStateDisable is DISABLE. + EventStateDisable + // EventStateDisableOnReplica is DISABLE ON REPLICA. + EventStateDisableOnReplica +) + +func restoreEventState(ctx *format.RestoreCtx, state EventState) { + switch state { + case EventStateEnable: + ctx.WriteKeyWord(" ENABLE") + case EventStateDisable: + ctx.WriteKeyWord(" DISABLE") + case EventStateDisableOnReplica: + ctx.WriteKeyWord(" DISABLE ON REPLICA") + } +} + +func restoreEventCompletion(ctx *format.RestoreCtx, completion EventCompletion) { + switch completion { + case EventCompletionPreserve: + ctx.WriteKeyWord(" ON COMPLETION PRESERVE") + case EventCompletionNotPreserve: + ctx.WriteKeyWord(" ON COMPLETION NOT PRESERVE") + } +} + +// CreateEventStmt is a CREATE EVENT statement: +// +// CREATE [DEFINER = user] EVENT [IF NOT EXISTS] event_name +// ON SCHEDULE schedule [ON COMPLETION [NOT] PRESERVE] +// [ENABLE | DISABLE | DISABLE ON REPLICA] [COMMENT 'string'] +// DO event_body +type CreateEventStmt struct { + ddlNode + + IfNotExists bool + Definer *auth.UserIdentity // nil when absent + EventName *TableName + Schedule *EventSchedule + Completion EventCompletion + State EventState + Comment *string + Body StmtNode +} + +// Restore implements Node interface. +func (n *CreateEventStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.Definer != nil { + if err := restoreDefiner(ctx, n.Definer, "CreateEventStmt"); err != nil { + return err + } + } + ctx.WriteKeyWord("EVENT ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + if err := n.EventName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateEventStmt.EventName") + } + ctx.WriteKeyWord(" ON SCHEDULE ") + if err := n.Schedule.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateEventStmt.Schedule") + } + restoreEventCompletion(ctx, n.Completion) + restoreEventState(ctx, n.State) + if n.Comment != nil { + ctx.WriteKeyWord(" COMMENT ") + ctx.WriteString(*n.Comment) + } + ctx.WriteKeyWord(" DO ") + if err := n.Body.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateEventStmt.Body") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateEventStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateEventStmt) + node, ok := n.EventName.Accept(v) + if !ok { + return n, false + } + n.EventName = node.(*TableName) + if !n.Schedule.accept(v) { + return n, false + } + if n.Body != nil { + node, ok := n.Body.Accept(v) + if !ok { + return n, false + } + n.Body = node.(StmtNode) + } + return v.Leave(n) +} + +// AlterEventStmt is an ALTER EVENT statement: +// +// ALTER [DEFINER = user] EVENT event_name +// [ON SCHEDULE schedule] [ON COMPLETION [NOT] PRESERVE] +// [RENAME TO new_event_name] +// [ENABLE | DISABLE | DISABLE ON REPLICA] [COMMENT 'string'] +// [DO event_body] +type AlterEventStmt struct { + ddlNode + + Definer *auth.UserIdentity // nil when absent + EventName *TableName + Schedule *EventSchedule // nil when absent + Completion EventCompletion + RenameTo *TableName + State EventState + Comment *string + Body StmtNode // nil when absent +} + +// Restore implements Node interface. +func (n *AlterEventStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER ") + if n.Definer != nil { + if err := restoreDefiner(ctx, n.Definer, "AlterEventStmt"); err != nil { + return err + } + } + ctx.WriteKeyWord("EVENT ") + if err := n.EventName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterEventStmt.EventName") + } + if n.Schedule != nil { + ctx.WriteKeyWord(" ON SCHEDULE ") + if err := n.Schedule.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterEventStmt.Schedule") + } + } + restoreEventCompletion(ctx, n.Completion) + if n.RenameTo != nil { + ctx.WriteKeyWord(" RENAME TO ") + if err := n.RenameTo.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterEventStmt.RenameTo") + } + } + restoreEventState(ctx, n.State) + if n.Comment != nil { + ctx.WriteKeyWord(" COMMENT ") + ctx.WriteString(*n.Comment) + } + if n.Body != nil { + ctx.WriteKeyWord(" DO ") + if err := n.Body.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterEventStmt.Body") + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *AlterEventStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterEventStmt) + node, ok := n.EventName.Accept(v) + if !ok { + return n, false + } + n.EventName = node.(*TableName) + if n.Schedule != nil && !n.Schedule.accept(v) { + return n, false + } + if n.RenameTo != nil { + node, ok := n.RenameTo.Accept(v) + if !ok { + return n, false + } + n.RenameTo = node.(*TableName) + } + if n.Body != nil { + node, ok := n.Body.Accept(v) + if !ok { + return n, false + } + n.Body = node.(StmtNode) + } + return v.Leave(n) +} + +// DropEventStmt is a DROP EVENT statement: +// DROP EVENT [IF EXISTS] event_name. +type DropEventStmt struct { + ddlNode + + IfExists bool + EventName *TableName +} + +// Restore implements Node interface. +func (n *DropEventStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP EVENT ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + if err := n.EventName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore DropEventStmt.EventName") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *DropEventStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropEventStmt) + node, ok := n.EventName.Accept(v) + if !ok { + return n, false + } + n.EventName = node.(*TableName) + return v.Leave(n) +} + +/* + * Triggers + */ + +// TriggerTime is the BEFORE/AFTER modifier of CREATE TRIGGER. +type TriggerTime int + +const ( + // TriggerTimeBefore is BEFORE. + TriggerTimeBefore TriggerTime = iota + // TriggerTimeAfter is AFTER. + TriggerTimeAfter +) + +// String implements fmt.Stringer interface. +func (n TriggerTime) String() string { + switch n { + case TriggerTimeBefore: + return "BEFORE" + case TriggerTimeAfter: + return "AFTER" + } + return "" +} + +// TriggerEvent is the INSERT/UPDATE/DELETE event of CREATE TRIGGER. +type TriggerEvent int + +const ( + // TriggerEventInsert is INSERT. + TriggerEventInsert TriggerEvent = iota + // TriggerEventUpdate is UPDATE. + TriggerEventUpdate + // TriggerEventDelete is DELETE. + TriggerEventDelete +) + +// String implements fmt.Stringer interface. +func (n TriggerEvent) String() string { + switch n { + case TriggerEventInsert: + return "INSERT" + case TriggerEventUpdate: + return "UPDATE" + case TriggerEventDelete: + return "DELETE" + } + return "" +} + +// TriggerOrder is the FOLLOWS/PRECEDES clause of CREATE TRIGGER. +type TriggerOrder struct { + Precedes bool // FOLLOWS when false + OtherTrigger CIStr +} + +// CreateTriggerStmt is a CREATE TRIGGER statement: +// +// CREATE [DEFINER = user] TRIGGER [IF NOT EXISTS] trigger_name +// {BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON tbl_name +// FOR EACH ROW [{FOLLOWS | PRECEDES} other_trigger_name] +// trigger_body +type CreateTriggerStmt struct { + ddlNode + + IfNotExists bool + Definer *auth.UserIdentity // nil when absent + TriggerName *TableName + TriggerTime TriggerTime + TriggerEvent TriggerEvent + Table *TableName + Order *TriggerOrder // nil when absent + Body StmtNode +} + +// Restore implements Node interface. +func (n *CreateTriggerStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.Definer != nil { + if err := restoreDefiner(ctx, n.Definer, "CreateTriggerStmt"); err != nil { + return err + } + } + ctx.WriteKeyWord("TRIGGER ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + if err := n.TriggerName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateTriggerStmt.TriggerName") + } + ctx.WritePlain(" ") + ctx.WriteKeyWord(n.TriggerTime.String()) + ctx.WritePlain(" ") + ctx.WriteKeyWord(n.TriggerEvent.String()) + ctx.WriteKeyWord(" ON ") + if err := n.Table.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateTriggerStmt.Table") + } + ctx.WriteKeyWord(" FOR EACH ROW") + if n.Order != nil { + if n.Order.Precedes { + ctx.WriteKeyWord(" PRECEDES ") + } else { + ctx.WriteKeyWord(" FOLLOWS ") + } + ctx.WriteName(n.Order.OtherTrigger.O) + } + ctx.WritePlain(" ") + if err := n.Body.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateTriggerStmt.Body") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateTriggerStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateTriggerStmt) + node, ok := n.TriggerName.Accept(v) + if !ok { + return n, false + } + n.TriggerName = node.(*TableName) + node, ok = n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + if n.Body != nil { + node, ok := n.Body.Accept(v) + if !ok { + return n, false + } + n.Body = node.(StmtNode) + } + return v.Leave(n) +} + +// DropTriggerStmt is a DROP TRIGGER statement: +// DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name. +type DropTriggerStmt struct { + ddlNode + + IfExists bool + TriggerName *TableName +} + +// Restore implements Node interface. +func (n *DropTriggerStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP TRIGGER ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + if err := n.TriggerName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore DropTriggerStmt.TriggerName") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *DropTriggerStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropTriggerStmt) + node, ok := n.TriggerName.Accept(v) + if !ok { + return n, false + } + n.TriggerName = node.(*TableName) + return v.Leave(n) +} + +/* + * Stored routines + */ + +// RoutineDeterminism is the [NOT] DETERMINISTIC characteristic of a +// stored routine. +type RoutineDeterminism int + +const ( + // RoutineDeterminismDefault omits the characteristic. + RoutineDeterminismDefault RoutineDeterminism = iota + // RoutineDeterministic is DETERMINISTIC. + RoutineDeterministic + // RoutineNotDeterministic is NOT DETERMINISTIC. + RoutineNotDeterministic +) + +// RoutineDataAccess is the SQL data access characteristic of a stored +// routine. +type RoutineDataAccess int + +const ( + // RoutineDataAccessDefault omits the characteristic. + RoutineDataAccessDefault RoutineDataAccess = iota + // RoutineContainsSQL is CONTAINS SQL. + RoutineContainsSQL + // RoutineNoSQL is NO SQL. + RoutineNoSQL + // RoutineReadsSQLData is READS SQL DATA. + RoutineReadsSQLData + // RoutineModifiesSQLData is MODIFIES SQL DATA. + RoutineModifiesSQLData +) + +// RoutineCharacteristics are the characteristics of a stored routine. +// They restore in the canonical order COMMENT, LANGUAGE, [NOT] +// DETERMINISTIC, data access, SQL SECURITY regardless of source order. +type RoutineCharacteristics struct { + Comment *string + Language CIStr // empty when absent (SQL, JAVASCRIPT, ...) + Determinism RoutineDeterminism + DataAccess RoutineDataAccess + Security *ViewSecurity // nil when absent +} + +// Restore implements Node interface. Every present characteristic is +// written with one leading space. +func (n *RoutineCharacteristics) Restore(ctx *format.RestoreCtx) error { + if n.Comment != nil { + ctx.WriteKeyWord(" COMMENT ") + ctx.WriteString(*n.Comment) + } + if n.Language.O != "" { + ctx.WriteKeyWord(" LANGUAGE ") + ctx.WriteKeyWord(n.Language.O) + } + switch n.Determinism { + case RoutineDeterministic: + ctx.WriteKeyWord(" DETERMINISTIC") + case RoutineNotDeterministic: + ctx.WriteKeyWord(" NOT DETERMINISTIC") + } + switch n.DataAccess { + case RoutineContainsSQL: + ctx.WriteKeyWord(" CONTAINS SQL") + case RoutineNoSQL: + ctx.WriteKeyWord(" NO SQL") + case RoutineReadsSQLData: + ctx.WriteKeyWord(" READS SQL DATA") + case RoutineModifiesSQLData: + ctx.WriteKeyWord(" MODIFIES SQL DATA") + } + if n.Security != nil { + ctx.WriteKeyWord(" SQL SECURITY ") + if *n.Security == SecurityInvoker { + ctx.WriteKeyWord("INVOKER") + } else { + ctx.WriteKeyWord("DEFINER") + } + } + return nil +} + +// FunctionParam is one parameter of a stored function; unlike procedure +// parameters (StoreParameter) it has no IN/OUT/INOUT mode. +type FunctionParam struct { + ParamName string + ParamType *types.FieldType +} + +// Restore implements Node interface. +func (n *FunctionParam) Restore(ctx *format.RestoreCtx) error { + ctx.WriteName(n.ParamName) + ctx.WritePlain(" ") + if err := n.ParamType.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore FunctionParam.ParamType") + } + return nil +} + +// CreateFunctionStmt is a CREATE FUNCTION statement for stored +// functions (CreateLoadableFunctionStmt is the loadable function form): +// +// CREATE [DEFINER = user] FUNCTION [IF NOT EXISTS] sp_name +// ([func_parameter[, ...]]) RETURNS type [characteristic ...] +// routine_body +type CreateFunctionStmt struct { + ddlNode + + IfNotExists bool + Definer *auth.UserIdentity // nil when absent + FunctionName *TableName + Params []*FunctionParam + ReturnType *types.FieldType + Characteristics RoutineCharacteristics + Body StmtNode +} + +// Restore implements Node interface. +func (n *CreateFunctionStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.Definer != nil { + if err := restoreDefiner(ctx, n.Definer, "CreateFunctionStmt"); err != nil { + return err + } + } + ctx.WriteKeyWord("FUNCTION ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + if err := n.FunctionName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateFunctionStmt.FunctionName") + } + ctx.WritePlain("(") + for i, param := range n.Params { + if i != 0 { + ctx.WritePlain(", ") + } + if err := param.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore CreateFunctionStmt.Params[%d]", i) + } + } + ctx.WritePlain(")") + ctx.WriteKeyWord(" RETURNS ") + if err := n.ReturnType.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateFunctionStmt.ReturnType") + } + if err := n.Characteristics.Restore(ctx); err != nil { + return err + } + ctx.WritePlain(" ") + if err := n.Body.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateFunctionStmt.Body") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateFunctionStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateFunctionStmt) + node, ok := n.FunctionName.Accept(v) + if !ok { + return n, false + } + n.FunctionName = node.(*TableName) + if n.Body != nil { + node, ok := n.Body.Accept(v) + if !ok { + return n, false + } + n.Body = node.(StmtNode) + } + return v.Leave(n) +} + +// AlterFunctionStmt is an ALTER FUNCTION statement: +// ALTER FUNCTION func_name [characteristic ...]. +type AlterFunctionStmt struct { + ddlNode + + FunctionName *TableName + Characteristics RoutineCharacteristics +} + +// Restore implements Node interface. +func (n *AlterFunctionStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER FUNCTION ") + if err := n.FunctionName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterFunctionStmt.FunctionName") + } + return n.Characteristics.Restore(ctx) +} + +// Accept implements Node Accept interface. +func (n *AlterFunctionStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterFunctionStmt) + node, ok := n.FunctionName.Accept(v) + if !ok { + return n, false + } + n.FunctionName = node.(*TableName) + return v.Leave(n) +} + +// DropFunctionStmt is a DROP FUNCTION statement (for a stored or +// loadable function): DROP FUNCTION [IF EXISTS] func_name. +type DropFunctionStmt struct { + ddlNode + + IfExists bool + FunctionName *TableName +} + +// Restore implements Node interface. +func (n *DropFunctionStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP FUNCTION ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + if err := n.FunctionName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore DropFunctionStmt.FunctionName") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *DropFunctionStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropFunctionStmt) + node, ok := n.FunctionName.Accept(v) + if !ok { + return n, false + } + n.FunctionName = node.(*TableName) + return v.Leave(n) +} + +// AlterProcedureStmt is an ALTER PROCEDURE statement: +// ALTER PROCEDURE proc_name [characteristic ...]. +type AlterProcedureStmt struct { + ddlNode + + ProcedureName *TableName + Characteristics RoutineCharacteristics +} + +// Restore implements Node interface. +func (n *AlterProcedureStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER PROCEDURE ") + if err := n.ProcedureName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterProcedureStmt.ProcedureName") + } + return n.Characteristics.Restore(ctx) +} + +// Accept implements Node Accept interface. +func (n *AlterProcedureStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterProcedureStmt) + node, ok := n.ProcedureName.Accept(v) + if !ok { + return n, false + } + n.ProcedureName = node.(*TableName) + return v.Leave(n) +} + +// ReturnStmt is a RETURN statement in a stored function body: +// RETURN expr. +type ReturnStmt struct { + stmtNode + + Expr ExprNode +} + +// Restore implements Node interface. +func (n *ReturnStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("RETURN ") + if err := n.Expr.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore ReturnStmt.Expr") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ReturnStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ReturnStmt) + node, ok := n.Expr.Accept(v) + if !ok { + return n, false + } + n.Expr = node.(ExprNode) + return v.Leave(n) +} + +/* + * ALTER VIEW + */ + +// AlterViewStmt is an ALTER VIEW statement: +// +// ALTER [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] +// [DEFINER = user] [SQL SECURITY {DEFINER | INVOKER}] +// VIEW view_name [(column_list)] AS select_statement +// [WITH [CASCADED | LOCAL] CHECK OPTION] +// +// The fields mirror CreateViewStmt: Definer defaults to CURRENT_USER +// and CheckOption to CASCADED when their clauses are absent. +type AlterViewStmt struct { + ddlNode + + ViewName *TableName + Cols []CIStr + Select StmtNode + Algorithm ViewAlgorithm + Definer *auth.UserIdentity + Security ViewSecurity + CheckOption ViewCheckOption +} + +// Restore implements Node interface. +func (n *AlterViewStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER ") + ctx.WriteKeyWord("ALGORITHM ") + ctx.WritePlainf("= %s ", n.Algorithm.String()) + if err := restoreDefiner(ctx, n.Definer, "AlterViewStmt"); err != nil { + return err + } + ctx.WriteKeyWord("SQL SECURITY ") + ctx.WriteKeyWord(n.Security.String()) + ctx.WriteKeyWord(" VIEW ") + if err := n.ViewName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterViewStmt.ViewName") + } + for i, col := range n.Cols { + if i == 0 { + ctx.WritePlain(" (") + } else { + ctx.WritePlain(",") + } + ctx.WriteName(col.O) + if i == len(n.Cols)-1 { + ctx.WritePlain(")") + } + } + ctx.WriteKeyWord(" AS ") + if err := n.Select.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterViewStmt.Select") + } + if n.CheckOption != CheckOptionCascaded { + ctx.WriteKeyWord(" WITH ") + ctx.WriteKeyWord(n.CheckOption.String()) + ctx.WriteKeyWord(" CHECK OPTION") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *AlterViewStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterViewStmt) + node, ok := n.ViewName.Accept(v) + if !ok { + return n, false + } + n.ViewName = node.(*TableName) + selnode, ok := n.Select.Accept(v) + if !ok { + return n, false + } + n.Select = selnode.(StmtNode) + return v.Leave(n) +} + +/* + * Servers + */ + +// ServerOption is one option of CREATE/ALTER SERVER: +// {HOST | DATABASE | USER | PASSWORD | SOCKET | OWNER} 'string' +// | PORT port_num. Names are stored uppercase. +type ServerOption struct { + Name string + Value ValueExpr +} + +// Restore implements Node interface. +func (n *ServerOption) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord(n.Name) + ctx.WritePlain(" ") + if err := n.Value.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore ServerOption.Value") + } + return nil +} + +// CreateServerStmt is a CREATE SERVER statement: +// +// CREATE SERVER server_name FOREIGN DATA WRAPPER wrapper_name +// OPTIONS (option [, option] ...) +type CreateServerStmt struct { + ddlNode + + ServerName CIStr + Wrapper CIStr + Options []*ServerOption +} + +// Restore implements Node interface. +func (n *CreateServerStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE SERVER ") + ctx.WriteName(n.ServerName.O) + ctx.WriteKeyWord(" FOREIGN DATA WRAPPER ") + ctx.WriteName(n.Wrapper.O) + ctx.WriteKeyWord(" OPTIONS ") + ctx.WritePlain("(") + for i, opt := range n.Options { + if i != 0 { + ctx.WritePlain(", ") + } + if err := opt.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore CreateServerStmt.Options[%d]", i) + } + } + ctx.WritePlain(")") + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateServerStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateServerStmt) + return v.Leave(n) +} + +// AlterServerStmt is an ALTER SERVER statement: +// ALTER SERVER server_name OPTIONS (option [, option] ...). +type AlterServerStmt struct { + ddlNode + + ServerName CIStr + Options []*ServerOption +} + +// Restore implements Node interface. +func (n *AlterServerStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER SERVER ") + ctx.WriteName(n.ServerName.O) + ctx.WriteKeyWord(" OPTIONS ") + ctx.WritePlain("(") + for i, opt := range n.Options { + if i != 0 { + ctx.WritePlain(", ") + } + if err := opt.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore AlterServerStmt.Options[%d]", i) + } + } + ctx.WritePlain(")") + return nil +} + +// Accept implements Node Accept interface. +func (n *AlterServerStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterServerStmt) + return v.Leave(n) +} + +// DropServerStmt is a DROP SERVER statement: +// DROP SERVER [IF EXISTS] server_name. +type DropServerStmt struct { + ddlNode + + IfExists bool + ServerName CIStr +} + +// Restore implements Node interface. +func (n *DropServerStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP SERVER ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + ctx.WriteName(n.ServerName.O) + return nil +} + +// Accept implements Node Accept interface. +func (n *DropServerStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropServerStmt) + return v.Leave(n) +} + +/* + * Tablespaces and logfile groups + */ + +// TablespaceOptionType names one option of the CREATE/ALTER/DROP +// TABLESPACE and CREATE/ALTER/DROP LOGFILE GROUP statements. +type TablespaceOptionType int + +const ( + // TablespaceOptionInitialSize is INITIAL_SIZE. + TablespaceOptionInitialSize TablespaceOptionType = iota + // TablespaceOptionAutoextendSize is AUTOEXTEND_SIZE. + TablespaceOptionAutoextendSize + // TablespaceOptionMaxSize is MAX_SIZE. + TablespaceOptionMaxSize + // TablespaceOptionExtentSize is EXTENT_SIZE. + TablespaceOptionExtentSize + // TablespaceOptionNodegroup is NODEGROUP. + TablespaceOptionNodegroup + // TablespaceOptionFileBlockSize is FILE_BLOCK_SIZE. + TablespaceOptionFileBlockSize + // TablespaceOptionUndoBufferSize is UNDO_BUFFER_SIZE. + TablespaceOptionUndoBufferSize + // TablespaceOptionRedoBufferSize is REDO_BUFFER_SIZE. + TablespaceOptionRedoBufferSize + // TablespaceOptionWait is WAIT. + TablespaceOptionWait + // TablespaceOptionEncryption is ENCRYPTION. + TablespaceOptionEncryption + // TablespaceOptionComment is COMMENT. + TablespaceOptionComment + // TablespaceOptionEngine is ENGINE. + TablespaceOptionEngine + // TablespaceOptionEngineAttribute is ENGINE_ATTRIBUTE. + TablespaceOptionEngineAttribute +) + +// String implements fmt.Stringer interface. +func (n TablespaceOptionType) String() string { + switch n { + case TablespaceOptionInitialSize: + return "INITIAL_SIZE" + case TablespaceOptionAutoextendSize: + return "AUTOEXTEND_SIZE" + case TablespaceOptionMaxSize: + return "MAX_SIZE" + case TablespaceOptionExtentSize: + return "EXTENT_SIZE" + case TablespaceOptionNodegroup: + return "NODEGROUP" + case TablespaceOptionFileBlockSize: + return "FILE_BLOCK_SIZE" + case TablespaceOptionUndoBufferSize: + return "UNDO_BUFFER_SIZE" + case TablespaceOptionRedoBufferSize: + return "REDO_BUFFER_SIZE" + case TablespaceOptionWait: + return "WAIT" + case TablespaceOptionEncryption: + return "ENCRYPTION" + case TablespaceOptionComment: + return "COMMENT" + case TablespaceOptionEngine: + return "ENGINE" + case TablespaceOptionEngineAttribute: + return "ENGINE_ATTRIBUTE" + } + return "" +} + +// TablespaceOption is one option of a tablespace or logfile group +// statement. The size options, NODEGROUP, and FILE_BLOCK_SIZE use +// UintValue; ENCRYPTION, COMMENT, and ENGINE_ATTRIBUTE use StrValue +// restored as a string literal; ENGINE uses StrValue restored as a +// plain word; WAIT uses neither. +type TablespaceOption struct { + Tp TablespaceOptionType + StrValue string + UintValue uint64 +} + +// Restore implements Node interface. +func (n *TablespaceOption) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord(n.Tp.String()) + switch n.Tp { + case TablespaceOptionWait: + case TablespaceOptionEngine: + ctx.WritePlain(" = ") + ctx.WriteKeyWord(n.StrValue) + case TablespaceOptionEncryption, TablespaceOptionComment, TablespaceOptionEngineAttribute: + ctx.WritePlain(" = ") + ctx.WriteString(n.StrValue) + default: + ctx.WritePlainf(" = %d", n.UintValue) + } + return nil +} + +func restoreTablespaceOptions(ctx *format.RestoreCtx, options []*TablespaceOption, parent string) error { + for i, opt := range options { + ctx.WritePlain(" ") + if err := opt.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore %s.Options[%d]", parent, i) + } + } + return nil +} + +// CreateTablespaceStmt is a CREATE TABLESPACE statement: +// +// CREATE [UNDO] TABLESPACE tablespace_name [ADD DATAFILE 'file_name'] +// [USE LOGFILE GROUP logfile_group] [option ...] +type CreateTablespaceStmt struct { + ddlNode + + Undo bool + Name CIStr + Datafile string // ADD DATAFILE clause; empty when absent + UseLogfileGroup CIStr // USE LOGFILE GROUP clause; empty when absent + Options []*TablespaceOption +} + +// Restore implements Node interface. +func (n *CreateTablespaceStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.Undo { + ctx.WriteKeyWord("UNDO ") + } + ctx.WriteKeyWord("TABLESPACE ") + ctx.WriteName(n.Name.O) + if n.Datafile != "" { + ctx.WriteKeyWord(" ADD DATAFILE ") + ctx.WriteString(n.Datafile) + } + if n.UseLogfileGroup.O != "" { + ctx.WriteKeyWord(" USE LOGFILE GROUP ") + ctx.WriteName(n.UseLogfileGroup.O) + } + return restoreTablespaceOptions(ctx, n.Options, "CreateTablespaceStmt") +} + +// Accept implements Node Accept interface. +func (n *CreateTablespaceStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateTablespaceStmt) + return v.Leave(n) +} + +// AlterTablespaceOp selects the form of an ALTER TABLESPACE statement. +type AlterTablespaceOp int + +const ( + // AlterTablespaceOptionsOnly changes options alone. + AlterTablespaceOptionsOnly AlterTablespaceOp = iota + // AlterTablespaceAddDatafile is ADD DATAFILE. + AlterTablespaceAddDatafile + // AlterTablespaceDropDatafile is DROP DATAFILE. + AlterTablespaceDropDatafile + // AlterTablespaceRenameTo is RENAME TO. + AlterTablespaceRenameTo + // AlterTablespaceSetActive is SET ACTIVE. + AlterTablespaceSetActive + // AlterTablespaceSetInactive is SET INACTIVE. + AlterTablespaceSetInactive +) + +// AlterTablespaceStmt is an ALTER [UNDO] TABLESPACE statement: +// +// ALTER [UNDO] TABLESPACE tablespace_name +// {{ADD | DROP} DATAFILE 'file_name' | RENAME TO new_name +// | SET {ACTIVE | INACTIVE}} [option ...] +type AlterTablespaceStmt struct { + ddlNode + + Undo bool + Name CIStr + Op AlterTablespaceOp + Datafile string + NewName CIStr + Options []*TablespaceOption +} + +// Restore implements Node interface. +func (n *AlterTablespaceStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER ") + if n.Undo { + ctx.WriteKeyWord("UNDO ") + } + ctx.WriteKeyWord("TABLESPACE ") + ctx.WriteName(n.Name.O) + switch n.Op { + case AlterTablespaceAddDatafile: + ctx.WriteKeyWord(" ADD DATAFILE ") + ctx.WriteString(n.Datafile) + case AlterTablespaceDropDatafile: + ctx.WriteKeyWord(" DROP DATAFILE ") + ctx.WriteString(n.Datafile) + case AlterTablespaceRenameTo: + ctx.WriteKeyWord(" RENAME TO ") + ctx.WriteName(n.NewName.O) + case AlterTablespaceSetActive: + ctx.WriteKeyWord(" SET ACTIVE") + case AlterTablespaceSetInactive: + ctx.WriteKeyWord(" SET INACTIVE") + } + return restoreTablespaceOptions(ctx, n.Options, "AlterTablespaceStmt") +} + +// Accept implements Node Accept interface. +func (n *AlterTablespaceStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterTablespaceStmt) + return v.Leave(n) +} + +// DropTablespaceStmt is a DROP [UNDO] TABLESPACE statement: +// DROP [UNDO] TABLESPACE tablespace_name [ENGINE [=] engine_name]. +type DropTablespaceStmt struct { + ddlNode + + Undo bool + Name CIStr + Options []*TablespaceOption +} + +// Restore implements Node interface. +func (n *DropTablespaceStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP ") + if n.Undo { + ctx.WriteKeyWord("UNDO ") + } + ctx.WriteKeyWord("TABLESPACE ") + ctx.WriteName(n.Name.O) + return restoreTablespaceOptions(ctx, n.Options, "DropTablespaceStmt") +} + +// Accept implements Node Accept interface. +func (n *DropTablespaceStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropTablespaceStmt) + return v.Leave(n) +} + +// CreateLogfileGroupStmt is a CREATE LOGFILE GROUP statement: +// +// CREATE LOGFILE GROUP logfile_group ADD UNDOFILE 'undo_file' +// [option ...] +type CreateLogfileGroupStmt struct { + ddlNode + + GroupName CIStr + Undofile string + Options []*TablespaceOption +} + +// Restore implements Node interface. +func (n *CreateLogfileGroupStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE LOGFILE GROUP ") + ctx.WriteName(n.GroupName.O) + ctx.WriteKeyWord(" ADD UNDOFILE ") + ctx.WriteString(n.Undofile) + return restoreTablespaceOptions(ctx, n.Options, "CreateLogfileGroupStmt") +} + +// Accept implements Node Accept interface. +func (n *CreateLogfileGroupStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateLogfileGroupStmt) + return v.Leave(n) +} + +// AlterLogfileGroupStmt is an ALTER LOGFILE GROUP statement: +// +// ALTER LOGFILE GROUP logfile_group ADD UNDOFILE 'file_name' +// [option ...] +type AlterLogfileGroupStmt struct { + ddlNode + + GroupName CIStr + Undofile string + Options []*TablespaceOption +} + +// Restore implements Node interface. +func (n *AlterLogfileGroupStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER LOGFILE GROUP ") + ctx.WriteName(n.GroupName.O) + ctx.WriteKeyWord(" ADD UNDOFILE ") + ctx.WriteString(n.Undofile) + return restoreTablespaceOptions(ctx, n.Options, "AlterLogfileGroupStmt") +} + +// Accept implements Node Accept interface. +func (n *AlterLogfileGroupStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterLogfileGroupStmt) + return v.Leave(n) +} + +// DropLogfileGroupStmt is a DROP LOGFILE GROUP statement: +// DROP LOGFILE GROUP logfile_group ENGINE [=] engine_name. +type DropLogfileGroupStmt struct { + ddlNode + + GroupName CIStr + Options []*TablespaceOption +} + +// Restore implements Node interface. +func (n *DropLogfileGroupStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP LOGFILE GROUP ") + ctx.WriteName(n.GroupName.O) + return restoreTablespaceOptions(ctx, n.Options, "DropLogfileGroupStmt") +} + +// Accept implements Node Accept interface. +func (n *DropLogfileGroupStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropLogfileGroupStmt) + return v.Leave(n) +} + +/* + * Spatial reference systems + */ + +// SRSAttributeType names one attribute of CREATE SPATIAL REFERENCE +// SYSTEM. +type SRSAttributeType int + +const ( + // SRSAttributeName is NAME 'srs_name'. + SRSAttributeName SRSAttributeType = iota + // SRSAttributeDefinition is DEFINITION 'definition'. + SRSAttributeDefinition + // SRSAttributeOrganization is ORGANIZATION 'org_name' IDENTIFIED BY + // srs_id. + SRSAttributeOrganization + // SRSAttributeDescription is DESCRIPTION 'description'. + SRSAttributeDescription +) + +// String implements fmt.Stringer interface. +func (n SRSAttributeType) String() string { + switch n { + case SRSAttributeName: + return "NAME" + case SRSAttributeDefinition: + return "DEFINITION" + case SRSAttributeOrganization: + return "ORGANIZATION" + case SRSAttributeDescription: + return "DESCRIPTION" + } + return "" +} + +// SRSAttribute is one attribute of CREATE SPATIAL REFERENCE SYSTEM, +// kept in source order. OrgID belongs to SRSAttributeOrganization only. +type SRSAttribute struct { + Tp SRSAttributeType + StrValue string + OrgID uint64 +} + +// Restore implements Node interface. +func (n *SRSAttribute) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord(n.Tp.String()) + ctx.WritePlain(" ") + ctx.WriteString(n.StrValue) + if n.Tp == SRSAttributeOrganization { + ctx.WriteKeyWord(" IDENTIFIED BY ") + ctx.WritePlainf("%d", n.OrgID) + } + return nil +} + +// CreateSpatialReferenceSystemStmt is a CREATE SPATIAL REFERENCE SYSTEM +// statement: +// +// CREATE [OR REPLACE] SPATIAL REFERENCE SYSTEM [IF NOT EXISTS] srid +// srs_attribute ... +type CreateSpatialReferenceSystemStmt struct { + ddlNode + + OrReplace bool + IfNotExists bool + Srid uint64 + Attributes []*SRSAttribute +} + +// Restore implements Node interface. +func (n *CreateSpatialReferenceSystemStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.OrReplace { + ctx.WriteKeyWord("OR REPLACE ") + } + ctx.WriteKeyWord("SPATIAL REFERENCE SYSTEM ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + ctx.WritePlainf("%d", n.Srid) + for i, attr := range n.Attributes { + ctx.WritePlain(" ") + if err := attr.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore CreateSpatialReferenceSystemStmt.Attributes[%d]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateSpatialReferenceSystemStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateSpatialReferenceSystemStmt) + return v.Leave(n) +} + +// DropSpatialReferenceSystemStmt is a DROP SPATIAL REFERENCE SYSTEM +// statement: DROP SPATIAL REFERENCE SYSTEM [IF EXISTS] srid. +type DropSpatialReferenceSystemStmt struct { + ddlNode + + IfExists bool + Srid uint64 +} + +// Restore implements Node interface. +func (n *DropSpatialReferenceSystemStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP SPATIAL REFERENCE SYSTEM ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + ctx.WritePlainf("%d", n.Srid) + return nil +} + +// Accept implements Node Accept interface. +func (n *DropSpatialReferenceSystemStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropSpatialReferenceSystemStmt) + return v.Leave(n) +} + +/* + * Libraries + */ + +// CreateLibraryStmt is a CREATE LIBRARY statement: +// +// CREATE [OR REPLACE] LIBRARY [IF NOT EXISTS] [schema.]library_name +// LANGUAGE language_name AS 'code' +type CreateLibraryStmt struct { + ddlNode + + OrReplace bool + IfNotExists bool + LibraryName *TableName + Language CIStr + Code string +} + +// Restore implements Node interface. +func (n *CreateLibraryStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.OrReplace { + ctx.WriteKeyWord("OR REPLACE ") + } + ctx.WriteKeyWord("LIBRARY ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + if err := n.LibraryName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateLibraryStmt.LibraryName") + } + ctx.WriteKeyWord(" LANGUAGE ") + ctx.WriteKeyWord(n.Language.O) + ctx.WriteKeyWord(" AS ") + ctx.WriteString(n.Code) + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateLibraryStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateLibraryStmt) + node, ok := n.LibraryName.Accept(v) + if !ok { + return n, false + } + n.LibraryName = node.(*TableName) + return v.Leave(n) +} + +// AlterLibraryStmt is an ALTER LIBRARY statement: +// ALTER LIBRARY [schema.]library_name COMMENT 'string'. +type AlterLibraryStmt struct { + ddlNode + + LibraryName *TableName + Comment string +} + +// Restore implements Node interface. +func (n *AlterLibraryStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER LIBRARY ") + if err := n.LibraryName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterLibraryStmt.LibraryName") + } + ctx.WriteKeyWord(" COMMENT ") + ctx.WriteString(n.Comment) + return nil +} + +// Accept implements Node Accept interface. +func (n *AlterLibraryStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterLibraryStmt) + node, ok := n.LibraryName.Accept(v) + if !ok { + return n, false + } + n.LibraryName = node.(*TableName) + return v.Leave(n) +} + +// DropLibraryStmt is a DROP LIBRARY statement: +// DROP LIBRARY [IF EXISTS] [schema.]library_name. +type DropLibraryStmt struct { + ddlNode + + IfExists bool + LibraryName *TableName +} + +// Restore implements Node interface. +func (n *DropLibraryStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("DROP LIBRARY ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + if err := n.LibraryName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore DropLibraryStmt.LibraryName") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *DropLibraryStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DropLibraryStmt) + node, ok := n.LibraryName.Accept(v) + if !ok { + return n, false + } + n.LibraryName = node.(*TableName) + return v.Leave(n) +} + +/* + * JSON duality views + */ + +// CreateJSONDualityViewStmt is a CREATE JSON DUALITY VIEW statement: +// +// CREATE [OR REPLACE] JSON DUALITY VIEW [IF NOT EXISTS] view_name +// AS select_statement +type CreateJSONDualityViewStmt struct { + ddlNode + + OrReplace bool + IfNotExists bool + ViewName *TableName + Select StmtNode +} + +// Restore implements Node interface. +func (n *CreateJSONDualityViewStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.OrReplace { + ctx.WriteKeyWord("OR REPLACE ") + } + ctx.WriteKeyWord("JSON DUALITY VIEW ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + if err := n.ViewName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateJSONDualityViewStmt.ViewName") + } + ctx.WriteKeyWord(" AS ") + if err := n.Select.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CreateJSONDualityViewStmt.Select") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateJSONDualityViewStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateJSONDualityViewStmt) + node, ok := n.ViewName.Accept(v) + if !ok { + return n, false + } + n.ViewName = node.(*TableName) + selnode, ok := n.Select.Accept(v) + if !ok { + return n, false + } + n.Select = selnode.(StmtNode) + return v.Leave(n) +} + +// AlterJSONDualityViewStmt is an ALTER JSON DUALITY VIEW statement: +// ALTER JSON DUALITY VIEW view_name AS select_statement. +type AlterJSONDualityViewStmt struct { + ddlNode + + ViewName *TableName + Select StmtNode +} + +// Restore implements Node interface. +func (n *AlterJSONDualityViewStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ALTER JSON DUALITY VIEW ") + if err := n.ViewName.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterJSONDualityViewStmt.ViewName") + } + ctx.WriteKeyWord(" AS ") + if err := n.Select.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore AlterJSONDualityViewStmt.Select") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *AlterJSONDualityViewStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AlterJSONDualityViewStmt) + node, ok := n.ViewName.Accept(v) + if !ok { + return n, false + } + n.ViewName = node.(*TableName) + selnode, ok := n.Select.Accept(v) + if !ok { + return n, false + } + n.Select = selnode.(StmtNode) + return v.Leave(n) +} + +// JSONDualityObjectItem is one 'key' : value pair of a +// JSON_DUALITY_OBJECT expression. +type JSONDualityObjectItem struct { + Key string + Value ExprNode +} + +// JSONDualityObjectExpr is a JSON_DUALITY_OBJECT('key' : value, ...) +// expression, the select-list constructor of JSON duality views. +type JSONDualityObjectExpr struct { + exprNode + + Items []*JSONDualityObjectItem +} + +// Restore implements Node interface. +func (n *JSONDualityObjectExpr) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("JSON_DUALITY_OBJECT") + ctx.WritePlain("(") + for i, item := range n.Items { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteString(item.Key) + ctx.WritePlain(" : ") + if err := item.Value.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore JSONDualityObjectExpr.Items[%d]", i) + } + } + ctx.WritePlain(")") + return nil +} + +// Format the ExprNode into a Writer. +func (n *JSONDualityObjectExpr) Format(w io.Writer) { + panic("Not implemented") +} + +// Accept implements Node Accept interface. +func (n *JSONDualityObjectExpr) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*JSONDualityObjectExpr) + for _, item := range n.Items { + node, ok := item.Value.Accept(v) + if !ok { + return n, false + } + item.Value = node.(ExprNode) + } + return v.Leave(n) +} diff --git a/ast/mysql_dml.go b/ast/mysql_dml.go new file mode 100644 index 0000000..86c887e --- /dev/null +++ b/ast/mysql_dml.go @@ -0,0 +1,417 @@ +// Copyright 2026 The sqlc Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package ast + +import ( + "github.com/sqlc-dev/marino/format" +) + +// The MySQL data manipulation statements that postdate the goyacc +// grammar (MySQL 26.7 §15.2): HANDLER, IMPORT TABLE, and LOAD XML. + +var ( + _ StmtNode = &HandlerOpenStmt{} + _ StmtNode = &HandlerReadStmt{} + _ StmtNode = &HandlerCloseStmt{} + _ StmtNode = &ImportTableStmt{} + _ StmtNode = &LoadXMLStmt{} +) + +// HandlerOpenStmt is a HANDLER ... OPEN statement: +// HANDLER tbl_name OPEN [[AS] alias]. +type HandlerOpenStmt struct { + stmtNode + + Table *TableName + Alias CIStr // empty when absent; restored with AS +} + +// Restore implements Node interface. +func (n *HandlerOpenStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("HANDLER ") + if err := n.Table.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore HandlerOpenStmt.Table") + } + ctx.WriteKeyWord(" OPEN") + if n.Alias.O != "" { + ctx.WriteKeyWord(" AS ") + ctx.WriteName(n.Alias.O) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *HandlerOpenStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*HandlerOpenStmt) + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + return v.Leave(n) +} + +// HandlerCompareOp is the comparison operator of the indexed-read form +// of HANDLER ... READ. HandlerOpNone selects the direction form. +type HandlerCompareOp int + +const ( + // HandlerOpNone selects the FIRST/NEXT/PREV/LAST direction form. + HandlerOpNone HandlerCompareOp = iota + // HandlerOpEQ is =. + HandlerOpEQ + // HandlerOpLE is <=. + HandlerOpLE + // HandlerOpGE is >=. + HandlerOpGE + // HandlerOpLT is <. + HandlerOpLT + // HandlerOpGT is >. + HandlerOpGT +) + +// String implements fmt.Stringer interface. +func (n HandlerCompareOp) String() string { + switch n { + case HandlerOpEQ: + return "=" + case HandlerOpLE: + return "<=" + case HandlerOpGE: + return ">=" + case HandlerOpLT: + return "<" + case HandlerOpGT: + return ">" + } + return "" +} + +// HandlerReadDirection is the cursor direction of HANDLER ... READ. +type HandlerReadDirection int + +const ( + // HandlerReadFirst is FIRST. + HandlerReadFirst HandlerReadDirection = iota + // HandlerReadNext is NEXT. + HandlerReadNext + // HandlerReadPrev is PREV. + HandlerReadPrev + // HandlerReadLast is LAST. + HandlerReadLast +) + +// String implements fmt.Stringer interface. +func (n HandlerReadDirection) String() string { + switch n { + case HandlerReadFirst: + return "FIRST" + case HandlerReadNext: + return "NEXT" + case HandlerReadPrev: + return "PREV" + case HandlerReadLast: + return "LAST" + } + return "" +} + +// HandlerReadStmt is a HANDLER ... READ statement: +// +// HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value, ...) +// [WHERE where_condition] [LIMIT ...] +// HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } +// [WHERE where_condition] [LIMIT ...] +// HANDLER tbl_name READ { FIRST | NEXT } +// [WHERE where_condition] [LIMIT ...] +// +// IndexName is empty for the table-scan form. Op is HandlerOpNone for +// the direction forms; otherwise Values holds the comparison values and +// Direction is unused. +type HandlerReadStmt struct { + stmtNode + + Table *TableName + IndexName CIStr + Op HandlerCompareOp + Values []ExprNode + Direction HandlerReadDirection + Where ExprNode + Limit *Limit +} + +// Restore implements Node interface. +func (n *HandlerReadStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("HANDLER ") + if err := n.Table.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore HandlerReadStmt.Table") + } + ctx.WriteKeyWord(" READ ") + if n.IndexName.O != "" { + ctx.WriteName(n.IndexName.O) + ctx.WritePlain(" ") + } + if n.Op != HandlerOpNone { + ctx.WritePlain(n.Op.String()) + ctx.WritePlain(" (") + for i, value := range n.Values { + if i != 0 { + ctx.WritePlain(", ") + } + if err := value.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore HandlerReadStmt.Values[%d]", i) + } + } + ctx.WritePlain(")") + } else { + ctx.WriteKeyWord(n.Direction.String()) + } + if n.Where != nil { + ctx.WriteKeyWord(" WHERE ") + if err := n.Where.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore HandlerReadStmt.Where") + } + } + if n.Limit != nil { + ctx.WritePlain(" ") + if err := n.Limit.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore HandlerReadStmt.Limit") + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *HandlerReadStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*HandlerReadStmt) + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + for i, value := range n.Values { + node, ok := value.Accept(v) + if !ok { + return n, false + } + n.Values[i] = node.(ExprNode) + } + if n.Where != nil { + node, ok := n.Where.Accept(v) + if !ok { + return n, false + } + n.Where = node.(ExprNode) + } + if n.Limit != nil { + node, ok := n.Limit.Accept(v) + if !ok { + return n, false + } + n.Limit = node.(*Limit) + } + return v.Leave(n) +} + +// HandlerCloseStmt is a HANDLER ... CLOSE statement: +// HANDLER tbl_name CLOSE. +type HandlerCloseStmt struct { + stmtNode + + Table *TableName +} + +// Restore implements Node interface. +func (n *HandlerCloseStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("HANDLER ") + if err := n.Table.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore HandlerCloseStmt.Table") + } + ctx.WriteKeyWord(" CLOSE") + return nil +} + +// Accept implements Node Accept interface. +func (n *HandlerCloseStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*HandlerCloseStmt) + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + return v.Leave(n) +} + +// ImportTableStmt is an IMPORT TABLE statement: +// IMPORT TABLE FROM sdi_file [, sdi_file] ... +// (ImportIntoStmt is the unrelated TiDB IMPORT INTO statement.) +type ImportTableStmt struct { + stmtNode + + SdiFiles []string +} + +// Restore implements Node interface. +func (n *ImportTableStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("IMPORT TABLE FROM ") + for i, file := range n.SdiFiles { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteString(file) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ImportTableStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ImportTableStmt) + return v.Leave(n) +} + +// LoadXMLStmt is a LOAD XML statement: +// +// LOAD XML [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name' +// [REPLACE | IGNORE] INTO TABLE [db_name.]tbl_name +// [CHARACTER SET charset_name] +// [ROWS IDENTIFIED BY ''] [IGNORE number {LINES | ROWS}] +// [(field_name_or_user_var [, field_name_or_user_var] ...)] +// [SET (col_name={expr | DEFAULT}) [, col_name={expr | DEFAULT}] ...] +// +// The IGNORE number LINES spelling parses like ROWS, so Restore() +// canonicalizes it to ROWS. +type LoadXMLStmt struct { + stmtNode + + LowPriority bool + Concurrent bool + FileLocRef FileLocRefTp + Path string + OnDuplicate OnDuplicateKeyHandlingType + Table *TableName + Charset *string + RowsIdentifiedBy string // the '' literal; empty when absent + IgnoreRows *uint64 + ColumnsAndUserVars []*ColumnNameOrUserVar + ColumnAssignments []*Assignment +} + +// Restore implements Node interface. +func (n *LoadXMLStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("LOAD XML ") + if n.LowPriority { + ctx.WriteKeyWord("LOW_PRIORITY ") + } else if n.Concurrent { + ctx.WriteKeyWord("CONCURRENT ") + } + if n.FileLocRef == FileLocClient { + ctx.WriteKeyWord("LOCAL ") + } + ctx.WriteKeyWord("INFILE ") + ctx.WriteString(n.Path) + if n.OnDuplicate == OnDuplicateKeyHandlingReplace { + ctx.WriteKeyWord(" REPLACE") + } else if n.OnDuplicate == OnDuplicateKeyHandlingIgnore { + ctx.WriteKeyWord(" IGNORE") + } + ctx.WriteKeyWord(" INTO TABLE ") + if err := n.Table.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore LoadXMLStmt.Table") + } + if n.Charset != nil { + ctx.WriteKeyWord(" CHARACTER SET ") + ctx.WritePlain(*n.Charset) + } + if n.RowsIdentifiedBy != "" { + ctx.WriteKeyWord(" ROWS IDENTIFIED BY ") + ctx.WriteString(n.RowsIdentifiedBy) + } + if n.IgnoreRows != nil { + ctx.WriteKeyWord(" IGNORE ") + ctx.WritePlainf("%d", *n.IgnoreRows) + ctx.WriteKeyWord(" ROWS") + } + if len(n.ColumnsAndUserVars) != 0 { + ctx.WritePlain(" (") + for i, c := range n.ColumnsAndUserVars { + if i != 0 { + ctx.WritePlain(",") + } + if err := c.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore LoadXMLStmt.ColumnsAndUserVars[%d]", i) + } + } + ctx.WritePlain(")") + } + if len(n.ColumnAssignments) != 0 { + ctx.WriteKeyWord(" SET") + for i, assign := range n.ColumnAssignments { + if i != 0 { + ctx.WritePlain(",") + } + ctx.WritePlain(" ") + if err := assign.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore LoadXMLStmt.ColumnAssignments[%d]", i) + } + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *LoadXMLStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*LoadXMLStmt) + if n.Table != nil { + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + } + for i, cuVars := range n.ColumnsAndUserVars { + node, ok := cuVars.Accept(v) + if !ok { + return n, false + } + n.ColumnsAndUserVars[i] = node.(*ColumnNameOrUserVar) + } + for i, assignment := range n.ColumnAssignments { + node, ok := assignment.Accept(v) + if !ok { + return n, false + } + n.ColumnAssignments[i] = node.(*Assignment) + } + return v.Leave(n) +} diff --git a/ast/procedure.go b/ast/procedure.go index 698e082..6084950 100644 --- a/ast/procedure.go +++ b/ast/procedure.go @@ -17,6 +17,7 @@ import ( "fmt" "strconv" + "github.com/sqlc-dev/marino/auth" "github.com/sqlc-dev/marino/format" "github.com/sqlc-dev/marino/types" ) @@ -238,11 +239,22 @@ type ProcedureInfo struct { ProcedureParam []*StoreParameter //procedure param ProcedureBody StmtNode //procedure body statement ProcedureParamStr string //procedure parameter string + // Definer is the DEFINER = user clause; nil when absent. + Definer *auth.UserIdentity } // Restore implements Node interface. func (n *ProcedureInfo) Restore(ctx *format.RestoreCtx) error { - ctx.WriteKeyWord("CREATE PROCEDURE ") + ctx.WriteKeyWord("CREATE ") + if n.Definer != nil { + ctx.WriteKeyWord("DEFINER ") + ctx.WritePlain("= ") + if err := n.Definer.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore ProcedureInfo.Definer") + } + ctx.WritePlain(" ") + } + ctx.WriteKeyWord("PROCEDURE ") if n.IfNotExists { ctx.WriteKeyWord("IF NOT EXISTS ") } diff --git a/ast/replication.go b/ast/replication.go new file mode 100644 index 0000000..8bfd5bc --- /dev/null +++ b/ast/replication.go @@ -0,0 +1,497 @@ +// Copyright 2026 The sqlc Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package ast + +import ( + "strings" + + "github.com/sqlc-dev/marino/format" +) + +// The MySQL replication statements (MySQL 26.7 §15.4): PURGE BINARY +// LOGS and RESET BINARY LOGS AND GTIDS on the source side, and CHANGE +// REPLICATION FILTER, RESET REPLICA, START/STOP REPLICA, and START/STOP +// GROUP_REPLICATION on the replica side. (CHANGE REPLICATION SOURCE TO +// and its ReplicationSourceOption node live in misc.go.) + +var ( + _ StmtNode = &PurgeBinaryLogsStmt{} + _ StmtNode = &ResetBinaryLogsAndGtidsStmt{} + _ StmtNode = &ChangeReplicationFilterStmt{} + _ StmtNode = &ResetReplicaStmt{} + _ StmtNode = &StartReplicaStmt{} + _ StmtNode = &StopReplicaStmt{} + _ StmtNode = &StartGroupReplicationStmt{} + _ StmtNode = &StopGroupReplicationStmt{} + + _ SensitiveStmtNode = &StartReplicaStmt{} + _ SensitiveStmtNode = &StartGroupReplicationStmt{} +) + +// PurgeBinaryLogsStmt is a PURGE BINARY LOGS statement: +// PURGE BINARY LOGS {TO 'log_name' | BEFORE datetime_expr}. +// Before is nil for the TO form. +type PurgeBinaryLogsStmt struct { + stmtNode + + To string + Before ExprNode +} + +// Restore implements Node interface. +func (n *PurgeBinaryLogsStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("PURGE BINARY LOGS ") + if n.Before != nil { + ctx.WriteKeyWord("BEFORE ") + if err := n.Before.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore PurgeBinaryLogsStmt.Before") + } + return nil + } + ctx.WriteKeyWord("TO ") + ctx.WriteString(n.To) + return nil +} + +// Accept implements Node Accept interface. +func (n *PurgeBinaryLogsStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*PurgeBinaryLogsStmt) + if n.Before != nil { + node, ok := n.Before.Accept(v) + if !ok { + return n, false + } + n.Before = node.(ExprNode) + } + return v.Leave(n) +} + +// ResetBinaryLogsAndGtidsStmt is a RESET BINARY LOGS AND GTIDS +// statement (which replaced RESET MASTER; the removed spelling is not +// parsed). +type ResetBinaryLogsAndGtidsStmt struct { + stmtNode +} + +// Restore implements Node interface. +func (n *ResetBinaryLogsAndGtidsStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("RESET BINARY LOGS AND GTIDS") + return nil +} + +// Accept implements Node Accept interface. +func (n *ResetBinaryLogsAndGtidsStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ResetBinaryLogsAndGtidsStmt) + return v.Leave(n) +} + +// ReplicationFilterType names the filter of one CHANGE REPLICATION +// FILTER specification. +type ReplicationFilterType int + +const ( + // ReplicationFilterDoDB is REPLICATE_DO_DB. + ReplicationFilterDoDB ReplicationFilterType = iota + // ReplicationFilterIgnoreDB is REPLICATE_IGNORE_DB. + ReplicationFilterIgnoreDB + // ReplicationFilterDoTable is REPLICATE_DO_TABLE. + ReplicationFilterDoTable + // ReplicationFilterIgnoreTable is REPLICATE_IGNORE_TABLE. + ReplicationFilterIgnoreTable + // ReplicationFilterWildDoTable is REPLICATE_WILD_DO_TABLE. + ReplicationFilterWildDoTable + // ReplicationFilterWildIgnoreTable is REPLICATE_WILD_IGNORE_TABLE. + ReplicationFilterWildIgnoreTable + // ReplicationFilterRewriteDB is REPLICATE_REWRITE_DB. + ReplicationFilterRewriteDB +) + +// String implements fmt.Stringer interface. +func (n ReplicationFilterType) String() string { + switch n { + case ReplicationFilterDoDB: + return "REPLICATE_DO_DB" + case ReplicationFilterIgnoreDB: + return "REPLICATE_IGNORE_DB" + case ReplicationFilterDoTable: + return "REPLICATE_DO_TABLE" + case ReplicationFilterIgnoreTable: + return "REPLICATE_IGNORE_TABLE" + case ReplicationFilterWildDoTable: + return "REPLICATE_WILD_DO_TABLE" + case ReplicationFilterWildIgnoreTable: + return "REPLICATE_WILD_IGNORE_TABLE" + case ReplicationFilterRewriteDB: + return "REPLICATE_REWRITE_DB" + } + return "" +} + +// ReplicationRewriteDB is one (from_db, to_db) pair of a +// REPLICATE_REWRITE_DB filter. +type ReplicationRewriteDB struct { + FromDB CIStr + ToDB CIStr +} + +// ReplicationFilter is one filter specification of a CHANGE REPLICATION +// FILTER statement. Exactly the value field matching Tp is set — DBNames +// for the _DB filters, Tables for the _TABLE filters, Patterns for the +// _WILD_ filters, and Rewrites for REPLICATE_REWRITE_DB — and an empty +// list (an empty value clears the filter) restores as (). +type ReplicationFilter struct { + Tp ReplicationFilterType + DBNames []CIStr + Tables []*TableName + Patterns []string + Rewrites []*ReplicationRewriteDB +} + +// Restore implements Node interface. +func (n *ReplicationFilter) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord(n.Tp.String()) + ctx.WritePlain(" = (") + switch n.Tp { + case ReplicationFilterDoDB, ReplicationFilterIgnoreDB: + for i, db := range n.DBNames { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(db.O) + } + case ReplicationFilterDoTable, ReplicationFilterIgnoreTable: + for i, table := range n.Tables { + if i != 0 { + ctx.WritePlain(", ") + } + if err := table.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore ReplicationFilter.Tables[%d]", i) + } + } + case ReplicationFilterWildDoTable, ReplicationFilterWildIgnoreTable: + for i, pattern := range n.Patterns { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteString(pattern) + } + case ReplicationFilterRewriteDB: + for i, rewrite := range n.Rewrites { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WritePlain("(") + ctx.WriteName(rewrite.FromDB.O) + ctx.WritePlain(", ") + ctx.WriteName(rewrite.ToDB.O) + ctx.WritePlain(")") + } + } + ctx.WritePlain(")") + return nil +} + +// ChangeReplicationFilterStmt is a CHANGE REPLICATION FILTER statement: +// CHANGE REPLICATION FILTER filter[, filter] ... [FOR CHANNEL channel]. +type ChangeReplicationFilterStmt struct { + stmtNode + + Filters []*ReplicationFilter + Channel string // FOR CHANNEL clause; empty when absent +} + +// Restore implements Node interface. +func (n *ChangeReplicationFilterStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CHANGE REPLICATION FILTER ") + for i, f := range n.Filters { + if i != 0 { + ctx.WritePlain(", ") + } + if err := f.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore ChangeReplicationFilterStmt.Filters[%d]", i) + } + } + if n.Channel != "" { + ctx.WriteKeyWord(" FOR CHANNEL ") + ctx.WriteString(n.Channel) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ChangeReplicationFilterStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ChangeReplicationFilterStmt) + for _, f := range n.Filters { + for i, table := range f.Tables { + node, ok := table.Accept(v) + if !ok { + return n, false + } + f.Tables[i] = node.(*TableName) + } + } + return v.Leave(n) +} + +// ResetReplicaStmt is a RESET REPLICA statement: +// RESET REPLICA [ALL] [FOR CHANNEL channel]. +type ResetReplicaStmt struct { + stmtNode + + All bool + Channel string // FOR CHANNEL clause; empty when absent +} + +// Restore implements Node interface. +func (n *ResetReplicaStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("RESET REPLICA") + if n.All { + ctx.WriteKeyWord(" ALL") + } + if n.Channel != "" { + ctx.WriteKeyWord(" FOR CHANNEL ") + ctx.WriteString(n.Channel) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ResetReplicaStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ResetReplicaStmt) + return v.Leave(n) +} + +// ReplicaThreadType is one thread type of a START/STOP REPLICA +// statement. +type ReplicaThreadType int + +const ( + // ReplicaThreadIO is IO_THREAD. + ReplicaThreadIO ReplicaThreadType = iota + // ReplicaThreadSQL is SQL_THREAD. + ReplicaThreadSQL +) + +// String implements fmt.Stringer interface. +func (n ReplicaThreadType) String() string { + switch n { + case ReplicaThreadIO: + return "IO_THREAD" + case ReplicaThreadSQL: + return "SQL_THREAD" + } + return "" +} + +func restoreReplicaThreadTypes(ctx *format.RestoreCtx, types []ReplicaThreadType) { + for i, t := range types { + if i != 0 { + ctx.WritePlain(",") + } + ctx.WritePlain(" ") + ctx.WriteKeyWord(t.String()) + } +} + +// StartReplicaStmt is a START REPLICA statement: +// +// START REPLICA [thread_types] [UNTIL until_option] +// [connection_options] [FOR CHANNEL channel] +// +// Until and ConnectionOptions reuse the generic name/value option node +// of CHANGE REPLICATION SOURCE TO; the bare UNTIL SQL_AFTER_MTS_GAPS +// option has a nil Value. +type StartReplicaStmt struct { + stmtNode + + ThreadTypes []ReplicaThreadType + Until []*ReplicationSourceOption + ConnectionOptions []*ReplicationSourceOption + Channel string // FOR CHANNEL clause; empty when absent +} + +// Restore implements Node interface. +func (n *StartReplicaStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("START REPLICA") + restoreReplicaThreadTypes(ctx, n.ThreadTypes) + for i, opt := range n.Until { + if i == 0 { + ctx.WriteKeyWord(" UNTIL ") + } else { + ctx.WritePlain(", ") + } + if err := opt.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore StartReplicaStmt.Until[%d]", i) + } + } + for i, opt := range n.ConnectionOptions { + ctx.WritePlain(" ") + if err := opt.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore StartReplicaStmt.ConnectionOptions[%d]", i) + } + } + if n.Channel != "" { + ctx.WriteKeyWord(" FOR CHANNEL ") + ctx.WriteString(n.Channel) + } + return nil +} + +// SecureText implements SensitiveStatement interface. +func (n *StartReplicaStmt) SecureText() string { + opts := make([]*ReplicationSourceOption, 0, len(n.ConnectionOptions)) + for _, opt := range n.ConnectionOptions { + if strings.Contains(opt.Name, "PASSWORD") { + opt = &ReplicationSourceOption{Name: opt.Name, Value: NewValueExpr("xxxxxx", "", "")} + } + opts = append(opts, opt) + } + masked := &StartReplicaStmt{ + ThreadTypes: n.ThreadTypes, + Until: n.Until, + ConnectionOptions: opts, + Channel: n.Channel, + } + var sb strings.Builder + _ = masked.Restore(format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)) + return sb.String() +} + +// Accept implements Node Accept interface. +func (n *StartReplicaStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*StartReplicaStmt) + return v.Leave(n) +} + +// StopReplicaStmt is a STOP REPLICA statement: +// STOP REPLICA [thread_types] [FOR CHANNEL channel]. +type StopReplicaStmt struct { + stmtNode + + ThreadTypes []ReplicaThreadType + Channel string // FOR CHANNEL clause; empty when absent +} + +// Restore implements Node interface. +func (n *StopReplicaStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("STOP REPLICA") + restoreReplicaThreadTypes(ctx, n.ThreadTypes) + if n.Channel != "" { + ctx.WriteKeyWord(" FOR CHANNEL ") + ctx.WriteString(n.Channel) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *StopReplicaStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*StopReplicaStmt) + return v.Leave(n) +} + +// StartGroupReplicationStmt is a START GROUP_REPLICATION statement: +// START GROUP_REPLICATION [USER='u' [, PASSWORD='p'] [, DEFAULT_AUTH='a']], +// with the credential options reusing the generic name/value option +// node of CHANGE REPLICATION SOURCE TO. +type StartGroupReplicationStmt struct { + stmtNode + + Options []*ReplicationSourceOption +} + +// Restore implements Node interface. +func (n *StartGroupReplicationStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("START GROUP_REPLICATION") + for i, opt := range n.Options { + if i == 0 { + ctx.WritePlain(" ") + } else { + ctx.WritePlain(", ") + } + if err := opt.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore StartGroupReplicationStmt.Options[%d]", i) + } + } + return nil +} + +// SecureText implements SensitiveStatement interface. +func (n *StartGroupReplicationStmt) SecureText() string { + opts := make([]*ReplicationSourceOption, 0, len(n.Options)) + for _, opt := range n.Options { + if strings.Contains(opt.Name, "PASSWORD") { + opt = &ReplicationSourceOption{Name: opt.Name, Value: NewValueExpr("xxxxxx", "", "")} + } + opts = append(opts, opt) + } + masked := &StartGroupReplicationStmt{Options: opts} + var sb strings.Builder + _ = masked.Restore(format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)) + return sb.String() +} + +// Accept implements Node Accept interface. +func (n *StartGroupReplicationStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*StartGroupReplicationStmt) + return v.Leave(n) +} + +// StopGroupReplicationStmt is a STOP GROUP_REPLICATION statement. +type StopGroupReplicationStmt struct { + stmtNode +} + +// Restore implements Node interface. +func (n *StopGroupReplicationStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("STOP GROUP_REPLICATION") + return nil +} + +// Accept implements Node Accept interface. +func (n *StopGroupReplicationStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*StopGroupReplicationStmt) + return v.Leave(n) +} diff --git a/ast/sem.go b/ast/sem.go index c7e8103..4152927 100644 --- a/ast/sem.go +++ b/ast/sem.go @@ -526,6 +526,104 @@ const ( LoadIndexCommand = "LOAD INDEX INTO CACHE" // ResetPersistCommand represents RESET PERSIST statement ResetPersistCommand = "RESET PERSIST" + // XAStartCommand represents XA START statement + XAStartCommand = "XA START" + // XAEndCommand represents XA END statement + XAEndCommand = "XA END" + // XAPrepareCommand represents XA PREPARE statement + XAPrepareCommand = "XA PREPARE" + // XACommitCommand represents XA COMMIT statement + XACommitCommand = "XA COMMIT" + // XARollbackCommand represents XA ROLLBACK statement + XARollbackCommand = "XA ROLLBACK" + // XARecoverCommand represents XA RECOVER statement + XARecoverCommand = "XA RECOVER" + // LockInstanceCommand represents LOCK INSTANCE FOR BACKUP statement + LockInstanceCommand = "LOCK INSTANCE" + // UnlockInstanceCommand represents UNLOCK INSTANCE statement + UnlockInstanceCommand = "UNLOCK INSTANCE" + // PurgeBinaryLogsCommand represents PURGE BINARY LOGS statement + PurgeBinaryLogsCommand = "PURGE BINARY LOGS" + // ResetBinaryLogsAndGtidsCommand represents RESET BINARY LOGS AND GTIDS statement + ResetBinaryLogsAndGtidsCommand = "RESET BINARY LOGS AND GTIDS" + // ChangeReplicationFilterCommand represents CHANGE REPLICATION FILTER statement + ChangeReplicationFilterCommand = "CHANGE REPLICATION FILTER" + // ResetReplicaCommand represents RESET REPLICA statement + ResetReplicaCommand = "RESET REPLICA" + // StartReplicaCommand represents START REPLICA statement + StartReplicaCommand = "START REPLICA" + // StopReplicaCommand represents STOP REPLICA statement + StopReplicaCommand = "STOP REPLICA" + // StartGroupReplicationCommand represents START GROUP_REPLICATION statement + StartGroupReplicationCommand = "START GROUP_REPLICATION" + // StopGroupReplicationCommand represents STOP GROUP_REPLICATION statement + StopGroupReplicationCommand = "STOP GROUP_REPLICATION" + // HandlerOpenCommand represents HANDLER ... OPEN statement + HandlerOpenCommand = "HANDLER OPEN" + // HandlerReadCommand represents HANDLER ... READ statement + HandlerReadCommand = "HANDLER READ" + // HandlerCloseCommand represents HANDLER ... CLOSE statement + HandlerCloseCommand = "HANDLER CLOSE" + // ImportTableCommand represents IMPORT TABLE statement + ImportTableCommand = "IMPORT TABLE" + // LoadXMLCommand represents LOAD XML statement + LoadXMLCommand = "LOAD XML" + // CreateEventCommand represents CREATE EVENT statement + CreateEventCommand = "CREATE EVENT" + // AlterEventCommand represents ALTER EVENT statement + AlterEventCommand = "ALTER EVENT" + // DropEventCommand represents DROP EVENT statement + DropEventCommand = "DROP EVENT" + // CreateTriggerCommand represents CREATE TRIGGER statement + CreateTriggerCommand = "CREATE TRIGGER" + // DropTriggerCommand represents DROP TRIGGER statement + DropTriggerCommand = "DROP TRIGGER" + // CreateFunctionCommand represents CREATE FUNCTION statement for stored functions + CreateFunctionCommand = "CREATE FUNCTION" + // AlterFunctionCommand represents ALTER FUNCTION statement + AlterFunctionCommand = "ALTER FUNCTION" + // DropFunctionCommand represents DROP FUNCTION statement + DropFunctionCommand = "DROP FUNCTION" + // AlterProcedureCommand represents ALTER PROCEDURE statement + AlterProcedureCommand = "ALTER PROCEDURE" + // ReturnCommand represents RETURN statement + ReturnCommand = "RETURN" + // AlterViewCommand represents ALTER VIEW statement + AlterViewCommand = "ALTER VIEW" + // CreateServerCommand represents CREATE SERVER statement + CreateServerCommand = "CREATE SERVER" + // AlterServerCommand represents ALTER SERVER statement + AlterServerCommand = "ALTER SERVER" + // DropServerCommand represents DROP SERVER statement + DropServerCommand = "DROP SERVER" + // CreateTablespaceCommand represents CREATE TABLESPACE statement + CreateTablespaceCommand = "CREATE TABLESPACE" + // AlterTablespaceCommand represents ALTER TABLESPACE statement + AlterTablespaceCommand = "ALTER TABLESPACE" + // DropTablespaceCommand represents DROP TABLESPACE statement + DropTablespaceCommand = "DROP TABLESPACE" + // CreateLogfileGroupCommand represents CREATE LOGFILE GROUP statement + CreateLogfileGroupCommand = "CREATE LOGFILE GROUP" + // AlterLogfileGroupCommand represents ALTER LOGFILE GROUP statement + AlterLogfileGroupCommand = "ALTER LOGFILE GROUP" + // DropLogfileGroupCommand represents DROP LOGFILE GROUP statement + DropLogfileGroupCommand = "DROP LOGFILE GROUP" + // CreateSpatialReferenceSystemCommand represents CREATE SPATIAL REFERENCE SYSTEM statement + CreateSpatialReferenceSystemCommand = "CREATE SPATIAL REFERENCE SYSTEM" + // DropSpatialReferenceSystemCommand represents DROP SPATIAL REFERENCE SYSTEM statement + DropSpatialReferenceSystemCommand = "DROP SPATIAL REFERENCE SYSTEM" + // CreateLibraryCommand represents CREATE LIBRARY statement + CreateLibraryCommand = "CREATE LIBRARY" + // AlterLibraryCommand represents ALTER LIBRARY statement + AlterLibraryCommand = "ALTER LIBRARY" + // DropLibraryCommand represents DROP LIBRARY statement + DropLibraryCommand = "DROP LIBRARY" + // CreateJSONDualityViewCommand represents CREATE JSON DUALITY VIEW statement + CreateJSONDualityViewCommand = "CREATE JSON DUALITY VIEW" + // AlterJSONDualityViewCommand represents ALTER JSON DUALITY VIEW statement + AlterJSONDualityViewCommand = "ALTER JSON DUALITY VIEW" + // DropMaskingPolicyCommand represents DROP MASKING POLICY statement + DropMaskingPolicyCommand = "DROP MASKING POLICY" // UnknownCommand represents unknown statements UnknownCommand = "UNKNOWN" // SetOprCommand represents UNION/INTERSECT/EXCEPT statement @@ -1502,3 +1600,237 @@ func (n *LoadIndexStmt) SEMCommand() string { func (n *ResetPersistStmt) SEMCommand() string { return ResetPersistCommand } + +// SEMCommand returns the command string for the statement. +func (n *XAStmt) SEMCommand() string { + switch n.Op { + case XAOpStart: + return XAStartCommand + case XAOpEnd: + return XAEndCommand + case XAOpPrepare: + return XAPrepareCommand + case XAOpCommit: + return XACommitCommand + case XAOpRollback: + return XARollbackCommand + case XAOpRecover: + return XARecoverCommand + } + return UnknownCommand +} + +// SEMCommand returns the command string for the statement. +func (n *LockInstanceStmt) SEMCommand() string { + return LockInstanceCommand +} + +// SEMCommand returns the command string for the statement. +func (n *UnlockInstanceStmt) SEMCommand() string { + return UnlockInstanceCommand +} + +// SEMCommand returns the command string for the statement. +func (n *PurgeBinaryLogsStmt) SEMCommand() string { + return PurgeBinaryLogsCommand +} + +// SEMCommand returns the command string for the statement. +func (n *ResetBinaryLogsAndGtidsStmt) SEMCommand() string { + return ResetBinaryLogsAndGtidsCommand +} + +// SEMCommand returns the command string for the statement. +func (n *ChangeReplicationFilterStmt) SEMCommand() string { + return ChangeReplicationFilterCommand +} + +// SEMCommand returns the command string for the statement. +func (n *ResetReplicaStmt) SEMCommand() string { + return ResetReplicaCommand +} + +// SEMCommand returns the command string for the statement. +func (n *StartReplicaStmt) SEMCommand() string { + return StartReplicaCommand +} + +// SEMCommand returns the command string for the statement. +func (n *StopReplicaStmt) SEMCommand() string { + return StopReplicaCommand +} + +// SEMCommand returns the command string for the statement. +func (n *StartGroupReplicationStmt) SEMCommand() string { + return StartGroupReplicationCommand +} + +// SEMCommand returns the command string for the statement. +func (n *StopGroupReplicationStmt) SEMCommand() string { + return StopGroupReplicationCommand +} + +// SEMCommand returns the command string for the statement. +func (n *HandlerOpenStmt) SEMCommand() string { + return HandlerOpenCommand +} + +// SEMCommand returns the command string for the statement. +func (n *HandlerReadStmt) SEMCommand() string { + return HandlerReadCommand +} + +// SEMCommand returns the command string for the statement. +func (n *HandlerCloseStmt) SEMCommand() string { + return HandlerCloseCommand +} + +// SEMCommand returns the command string for the statement. +func (n *ImportTableStmt) SEMCommand() string { + return ImportTableCommand +} + +// SEMCommand returns the command string for the statement. +func (n *LoadXMLStmt) SEMCommand() string { + return LoadXMLCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateEventStmt) SEMCommand() string { + return CreateEventCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterEventStmt) SEMCommand() string { + return AlterEventCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropEventStmt) SEMCommand() string { + return DropEventCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateTriggerStmt) SEMCommand() string { + return CreateTriggerCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropTriggerStmt) SEMCommand() string { + return DropTriggerCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateFunctionStmt) SEMCommand() string { + return CreateFunctionCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterFunctionStmt) SEMCommand() string { + return AlterFunctionCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropFunctionStmt) SEMCommand() string { + return DropFunctionCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterProcedureStmt) SEMCommand() string { + return AlterProcedureCommand +} + +// SEMCommand returns the command string for the statement. +func (n *ReturnStmt) SEMCommand() string { + return ReturnCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterViewStmt) SEMCommand() string { + return AlterViewCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateServerStmt) SEMCommand() string { + return CreateServerCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterServerStmt) SEMCommand() string { + return AlterServerCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropServerStmt) SEMCommand() string { + return DropServerCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateTablespaceStmt) SEMCommand() string { + return CreateTablespaceCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterTablespaceStmt) SEMCommand() string { + return AlterTablespaceCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropTablespaceStmt) SEMCommand() string { + return DropTablespaceCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateLogfileGroupStmt) SEMCommand() string { + return CreateLogfileGroupCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterLogfileGroupStmt) SEMCommand() string { + return AlterLogfileGroupCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropLogfileGroupStmt) SEMCommand() string { + return DropLogfileGroupCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateSpatialReferenceSystemStmt) SEMCommand() string { + return CreateSpatialReferenceSystemCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropSpatialReferenceSystemStmt) SEMCommand() string { + return DropSpatialReferenceSystemCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateLibraryStmt) SEMCommand() string { + return CreateLibraryCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterLibraryStmt) SEMCommand() string { + return AlterLibraryCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropLibraryStmt) SEMCommand() string { + return DropLibraryCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateJSONDualityViewStmt) SEMCommand() string { + return CreateJSONDualityViewCommand +} + +// SEMCommand returns the command string for the statement. +func (n *AlterJSONDualityViewStmt) SEMCommand() string { + return AlterJSONDualityViewCommand +} + +// SEMCommand returns the command string for the statement. +func (n *DropMaskingPolicyStmt) SEMCommand() string { + return DropMaskingPolicyCommand +} diff --git a/ast/xa.go b/ast/xa.go new file mode 100644 index 0000000..eaf0455 --- /dev/null +++ b/ast/xa.go @@ -0,0 +1,221 @@ +// Copyright 2026 The sqlc Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package ast + +import ( + "github.com/sqlc-dev/marino/format" +) + +// The MySQL XA transaction statements (MySQL 26.7 §15.3.8) and the +// backup lock statements LOCK INSTANCE FOR BACKUP / UNLOCK INSTANCE +// (§15.3.5). + +var ( + _ Node = &XID{} + + _ StmtNode = &XAStmt{} + _ StmtNode = &LockInstanceStmt{} + _ StmtNode = &UnlockInstanceStmt{} +) + +// XID is the transaction identifier of an XA statement: +// xid: gtrid [, bqual [, formatID]]. +// Gtrid and Bqual are string literals; HasBqual distinguishes an absent +// bqual from an empty one, and HasFormatID requires HasBqual. +type XID struct { + node + + Gtrid string + Bqual string + HasBqual bool + FormatID uint64 + HasFormatID bool +} + +// Restore implements Node interface. +func (n *XID) Restore(ctx *format.RestoreCtx) error { + ctx.WriteString(n.Gtrid) + if n.HasBqual { + ctx.WritePlain(", ") + ctx.WriteString(n.Bqual) + if n.HasFormatID { + ctx.WritePlainf(", %d", n.FormatID) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *XID) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*XID) + return v.Leave(n) +} + +// XAOp is the operation of an XAStmt. +type XAOp int + +const ( + // XAOpStart is XA START (the XA BEGIN spelling parses to it too). + XAOpStart XAOp = iota + // XAOpEnd is XA END. + XAOpEnd + // XAOpPrepare is XA PREPARE. + XAOpPrepare + // XAOpCommit is XA COMMIT. + XAOpCommit + // XAOpRollback is XA ROLLBACK. + XAOpRollback + // XAOpRecover is XA RECOVER. + XAOpRecover +) + +// XAStartOption is the JOIN/RESUME modifier of XA START. +type XAStartOption int + +const ( + // XAStartNone omits the modifier. + XAStartNone XAStartOption = iota + // XAStartJoin is XA START ... JOIN. + XAStartJoin + // XAStartResume is XA START ... RESUME. + XAStartResume +) + +// XAStmt is an XA transaction statement: +// +// XA {START|BEGIN} xid [JOIN|RESUME] +// XA END xid [SUSPEND [FOR MIGRATE]] +// XA PREPARE xid +// XA COMMIT xid [ONE PHASE] +// XA ROLLBACK xid +// XA RECOVER [CONVERT XID] +// +// Xid is nil for XA RECOVER only. ForMigrate requires Suspend; both +// belong to XA END. OnePhase belongs to XA COMMIT and ConvertXID to +// XA RECOVER. +type XAStmt struct { + stmtNode + + Op XAOp + Xid *XID + StartOption XAStartOption + Suspend bool + ForMigrate bool + OnePhase bool + ConvertXID bool +} + +// Restore implements Node interface. +func (n *XAStmt) Restore(ctx *format.RestoreCtx) error { + switch n.Op { + case XAOpStart: + ctx.WriteKeyWord("XA START ") + case XAOpEnd: + ctx.WriteKeyWord("XA END ") + case XAOpPrepare: + ctx.WriteKeyWord("XA PREPARE ") + case XAOpCommit: + ctx.WriteKeyWord("XA COMMIT ") + case XAOpRollback: + ctx.WriteKeyWord("XA ROLLBACK ") + case XAOpRecover: + ctx.WriteKeyWord("XA RECOVER") + if n.ConvertXID { + ctx.WriteKeyWord(" CONVERT XID") + } + return nil + } + if err := n.Xid.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore XAStmt.Xid") + } + switch n.StartOption { + case XAStartJoin: + ctx.WriteKeyWord(" JOIN") + case XAStartResume: + ctx.WriteKeyWord(" RESUME") + } + if n.Suspend { + ctx.WriteKeyWord(" SUSPEND") + if n.ForMigrate { + ctx.WriteKeyWord(" FOR MIGRATE") + } + } + if n.OnePhase { + ctx.WriteKeyWord(" ONE PHASE") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *XAStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*XAStmt) + if n.Xid != nil { + node, ok := n.Xid.Accept(v) + if !ok { + return n, false + } + n.Xid = node.(*XID) + } + return v.Leave(n) +} + +// LockInstanceStmt is a LOCK INSTANCE FOR BACKUP statement. +type LockInstanceStmt struct { + stmtNode +} + +// Restore implements Node interface. +func (n *LockInstanceStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("LOCK INSTANCE FOR BACKUP") + return nil +} + +// Accept implements Node Accept interface. +func (n *LockInstanceStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*LockInstanceStmt) + return v.Leave(n) +} + +// UnlockInstanceStmt is an UNLOCK INSTANCE statement. +type UnlockInstanceStmt struct { + stmtNode +} + +// Restore implements Node interface. +func (n *UnlockInstanceStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("UNLOCK INSTANCE") + return nil +} + +// Accept implements Node Accept interface. +func (n *UnlockInstanceStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*UnlockInstanceStmt) + return v.Leave(n) +} diff --git a/parser/keyword_classes.go b/parser/keyword_classes.go index d6ffd7b..9e3e08f 100644 --- a/parser/keyword_classes.go +++ b/parser/keyword_classes.go @@ -430,6 +430,38 @@ var unReservedKeywordNames = []string{ "UPGRADE", "USE_FRM", "VCPU", + "MIGRATE", + "ONE", + "PHASE", + "SUSPEND", + "XA", + "XID", + "FILTER", + "GROUP_REPLICATION", + "GTIDS", + "IO_THREAD", + "SQL_THREAD", + "CONCURRENT", + "DUMPFILE", + "PREV", + "XML", + "AT", + "COMPLETION", + "CONTAINS", + "DATAFILE", + "DUALITY", + "ENDS", + "EVERY", + "FOLLOWS", + "INNODB", + "LOGFILE", + "OPTIONS", + "PRECEDES", + "ROTATE", + "SERVER", + "STARTS", + "UNDOFILE", + "WRAPPER", "CODE", "LIBRARY", "MUTEX", @@ -439,6 +471,7 @@ var unReservedKeywordNames = []string{ // notKeywordTokenNames lists the NotKeywordToken production alternatives of parser.y. var notKeywordTokenNames = []string{ + "JSON_DUALITY_OBJECT", "ADDDATE", "APPROX_COUNT_DISTINCT", "APPROX_PERCENTILE", diff --git a/parser/keywords.go b/parser/keywords.go index b07f399..25f3b14 100644 --- a/parser/keywords.go +++ b/parser/keywords.go @@ -34,6 +34,7 @@ var Keywords = []KeywordsType{ {"ARRAY", true, "reserved"}, {"AS", true, "reserved"}, {"ASC", true, "reserved"}, + {"BEFORE", true, "reserved"}, {"BETWEEN", true, "reserved"}, {"BIGINT", true, "reserved"}, {"BINARY", true, "reserved"}, @@ -75,12 +76,14 @@ var Keywords = []KeywordsType{ {"DENSE_RANK", true, "reserved"}, {"DESC", true, "reserved"}, {"DESCRIBE", true, "reserved"}, + {"DETERMINISTIC", true, "reserved"}, {"DISTINCT", true, "reserved"}, {"DISTINCTROW", true, "reserved"}, {"DIV", true, "reserved"}, {"DOUBLE", true, "reserved"}, {"DROP", true, "reserved"}, {"DUAL", true, "reserved"}, + {"EACH", true, "reserved"}, {"ELSE", true, "reserved"}, {"ELSEIF", true, "reserved"}, {"ENCLOSED", true, "reserved"}, @@ -164,6 +167,7 @@ var Keywords = []KeywordsType{ {"MINUTE_MICROSECOND", true, "reserved"}, {"MINUTE_SECOND", true, "reserved"}, {"MOD", true, "reserved"}, + {"MODIFIES", true, "reserved"}, {"NATURAL", true, "reserved"}, {"NOT", true, "reserved"}, {"NO_WRITE_TO_BINLOG", true, "reserved"}, @@ -191,6 +195,7 @@ var Keywords = []KeywordsType{ {"RANGE", true, "reserved"}, {"RANK", true, "reserved"}, {"READ", true, "reserved"}, + {"READS", true, "reserved"}, {"REAL", true, "reserved"}, {"RECURSIVE", true, "reserved"}, {"REFERENCES", true, "reserved"}, @@ -202,6 +207,7 @@ var Keywords = []KeywordsType{ {"REQUIRE", true, "reserved"}, {"RESIGNAL", true, "reserved"}, {"RESTRICT", true, "reserved"}, + {"RETURN", true, "reserved"}, {"REVOKE", true, "reserved"}, {"RIGHT", true, "reserved"}, {"RLIKE", true, "reserved"}, @@ -238,6 +244,7 @@ var Keywords = []KeywordsType{ {"TRAILING", true, "reserved"}, {"TRIGGER", true, "reserved"}, {"TRUE", true, "reserved"}, + {"UNDO", true, "reserved"}, {"UNION", true, "reserved"}, {"UNIQUE", true, "reserved"}, {"UNLOCK", true, "reserved"}, @@ -279,6 +286,7 @@ var Keywords = []KeywordsType{ {"ANY", false, "unreserved"}, {"APPLY", false, "unreserved"}, {"ASCII", false, "unreserved"}, + {"AT", false, "unreserved"}, {"ATTRIBUTE", false, "unreserved"}, {"ATTRIBUTES", false, "unreserved"}, {"AUTOEXTEND_SIZE", false, "unreserved"}, @@ -334,16 +342,19 @@ var Keywords = []KeywordsType{ {"COMMIT", false, "unreserved"}, {"COMMITTED", false, "unreserved"}, {"COMPACT", false, "unreserved"}, + {"COMPLETION", false, "unreserved"}, {"COMPONENT", false, "unreserved"}, {"COMPRESSED", false, "unreserved"}, {"COMPRESSION", false, "unreserved"}, {"COMPRESSION_LEVEL", false, "unreserved"}, {"COMPRESSION_TYPE", false, "unreserved"}, {"CONCURRENCY", false, "unreserved"}, + {"CONCURRENT", false, "unreserved"}, {"CONFIG", false, "unreserved"}, {"CONNECTION", false, "unreserved"}, {"CONSISTENCY", false, "unreserved"}, {"CONSISTENT", false, "unreserved"}, + {"CONTAINS", false, "unreserved"}, {"CONTEXT", false, "unreserved"}, {"CPU", false, "unreserved"}, {"CSV_BACKSLASH_ESCAPE", false, "unreserved"}, @@ -356,6 +367,7 @@ var Keywords = []KeywordsType{ {"CURRENT", false, "unreserved"}, {"CYCLE", false, "unreserved"}, {"DATA", false, "unreserved"}, + {"DATAFILE", false, "unreserved"}, {"DATE", false, "unreserved"}, {"DATETIME", false, "unreserved"}, {"DAY", false, "unreserved"}, @@ -371,6 +383,8 @@ var Keywords = []KeywordsType{ {"DISCARD", false, "unreserved"}, {"DISK", false, "unreserved"}, {"DO", false, "unreserved"}, + {"DUALITY", false, "unreserved"}, + {"DUMPFILE", false, "unreserved"}, {"DUPLICATE", false, "unreserved"}, {"DYNAMIC", false, "unreserved"}, {"ENABLE", false, "unreserved"}, @@ -379,6 +393,7 @@ var Keywords = []KeywordsType{ {"ENCRYPTION_KEYFILE", false, "unreserved"}, {"ENCRYPTION_METHOD", false, "unreserved"}, {"END", false, "unreserved"}, + {"ENDS", false, "unreserved"}, {"ENFORCED", false, "unreserved"}, {"ENGINE", false, "unreserved"}, {"ENGINES", false, "unreserved"}, @@ -389,6 +404,7 @@ var Keywords = []KeywordsType{ {"ESCAPE", false, "unreserved"}, {"EVENT", false, "unreserved"}, {"EVENTS", false, "unreserved"}, + {"EVERY", false, "unreserved"}, {"EVOLVE", false, "unreserved"}, {"EXCHANGE", false, "unreserved"}, {"EXCLUSIVE", false, "unreserved"}, @@ -402,10 +418,12 @@ var Keywords = []KeywordsType{ {"FAULTS", false, "unreserved"}, {"FIELDS", false, "unreserved"}, {"FILE", false, "unreserved"}, + {"FILTER", false, "unreserved"}, {"FIRST", false, "unreserved"}, {"FIXED", false, "unreserved"}, {"FLUSH", false, "unreserved"}, {"FOLLOWING", false, "unreserved"}, + {"FOLLOWS", false, "unreserved"}, {"FORMAT", false, "unreserved"}, {"FOUND", false, "unreserved"}, {"FULL", false, "unreserved"}, @@ -413,6 +431,8 @@ var Keywords = []KeywordsType{ {"GENERAL", false, "unreserved"}, {"GLOBAL", false, "unreserved"}, {"GRANTS", false, "unreserved"}, + {"GROUP_REPLICATION", false, "unreserved"}, + {"GTIDS", false, "unreserved"}, {"HANDLER", false, "unreserved"}, {"HASH", false, "unreserved"}, {"HELP", false, "unreserved"}, @@ -429,12 +449,14 @@ var Keywords = []KeywordsType{ {"INCREMENT", false, "unreserved"}, {"INCREMENTAL", false, "unreserved"}, {"INDEXES", false, "unreserved"}, + {"INNODB", false, "unreserved"}, {"INSERT_METHOD", false, "unreserved"}, {"INSTALL", false, "unreserved"}, {"INSTANCE", false, "unreserved"}, {"INVISIBLE", false, "unreserved"}, {"INVOKER", false, "unreserved"}, {"IO", false, "unreserved"}, + {"IO_THREAD", false, "unreserved"}, {"IPC", false, "unreserved"}, {"ISOLATION", false, "unreserved"}, {"ISSUER", false, "unreserved"}, @@ -454,6 +476,7 @@ var Keywords = []KeywordsType{ {"LOCAL", false, "unreserved"}, {"LOCATION", false, "unreserved"}, {"LOCKED", false, "unreserved"}, + {"LOGFILE", false, "unreserved"}, {"LOGS", false, "unreserved"}, {"MANUAL", false, "unreserved"}, {"MASKING", false, "unreserved"}, @@ -470,6 +493,7 @@ var Keywords = []KeywordsType{ {"MEMORY", false, "unreserved"}, {"MERGE", false, "unreserved"}, {"MICROSECOND", false, "unreserved"}, + {"MIGRATE", false, "unreserved"}, {"MINUTE", false, "unreserved"}, {"MINVALUE", false, "unreserved"}, {"MIN_ROWS", false, "unreserved"}, @@ -499,11 +523,13 @@ var Keywords = []KeywordsType{ {"OLTP_READ_ONLY", false, "unreserved"}, {"OLTP_READ_WRITE", false, "unreserved"}, {"OLTP_WRITE_ONLY", false, "unreserved"}, + {"ONE", false, "unreserved"}, {"ONLINE", false, "unreserved"}, {"ONLY", false, "unreserved"}, {"ON_DUPLICATE", false, "unreserved"}, {"OPEN", false, "unreserved"}, {"OPTIONAL", false, "unreserved"}, + {"OPTIONS", false, "unreserved"}, {"PACK_KEYS", false, "unreserved"}, {"PAGE", false, "unreserved"}, {"PAGE_CHECKSUM", false, "unreserved"}, @@ -522,13 +548,16 @@ var Keywords = []KeywordsType{ {"PERSIST", false, "unreserved"}, {"PER_DB", false, "unreserved"}, {"PER_TABLE", false, "unreserved"}, + {"PHASE", false, "unreserved"}, {"PLUGIN", false, "unreserved"}, {"PLUGINS", false, "unreserved"}, {"POINT", false, "unreserved"}, {"POLICY", false, "unreserved"}, + {"PRECEDES", false, "unreserved"}, {"PRECEDING", false, "unreserved"}, {"PREPARE", false, "unreserved"}, {"PRESERVE", false, "unreserved"}, + {"PREV", false, "unreserved"}, {"PRE_SPLIT_REGIONS", false, "unreserved"}, {"PRIVILEGES", false, "unreserved"}, {"PROCESS", false, "unreserved"}, @@ -569,6 +598,7 @@ var Keywords = []KeywordsType{ {"ROLE", false, "unreserved"}, {"ROLLBACK", false, "unreserved"}, {"ROLLUP", false, "unreserved"}, + {"ROTATE", false, "unreserved"}, {"ROUTINE", false, "unreserved"}, {"ROW_COUNT", false, "unreserved"}, {"ROW_FORMAT", false, "unreserved"}, @@ -588,6 +618,7 @@ var Keywords = []KeywordsType{ {"SEQUENCE", false, "unreserved"}, {"SERIAL", false, "unreserved"}, {"SERIALIZABLE", false, "unreserved"}, + {"SERVER", false, "unreserved"}, {"SESSION", false, "unreserved"}, {"SETVAL", false, "unreserved"}, {"SHARD_ROW_ID_BITS", false, "unreserved"}, @@ -607,6 +638,7 @@ var Keywords = []KeywordsType{ {"SQL_BUFFER_RESULT", false, "unreserved"}, {"SQL_CACHE", false, "unreserved"}, {"SQL_NO_CACHE", false, "unreserved"}, + {"SQL_THREAD", false, "unreserved"}, {"SQL_TSI_DAY", false, "unreserved"}, {"SQL_TSI_HOUR", false, "unreserved"}, {"SQL_TSI_MINUTE", false, "unreserved"}, @@ -617,6 +649,7 @@ var Keywords = []KeywordsType{ {"SQL_TSI_YEAR", false, "unreserved"}, {"STACKED", false, "unreserved"}, {"START", false, "unreserved"}, + {"STARTS", false, "unreserved"}, {"STATS_AUTO_RECALC", false, "unreserved"}, {"STATS_COL_CHOICE", false, "unreserved"}, {"STATS_COL_LIST", false, "unreserved"}, @@ -632,6 +665,7 @@ var Keywords = []KeywordsType{ {"SUBPARTITION", false, "unreserved"}, {"SUBPARTITIONS", false, "unreserved"}, {"SUPER", false, "unreserved"}, + {"SUSPEND", false, "unreserved"}, {"SWAPS", false, "unreserved"}, {"SWITCHES", false, "unreserved"}, {"SYSTEM", false, "unreserved"}, @@ -665,6 +699,7 @@ var Keywords = []KeywordsType{ {"UNBOUNDED", false, "unreserved"}, {"UNCOMMITTED", false, "unreserved"}, {"UNDEFINED", false, "unreserved"}, + {"UNDOFILE", false, "unreserved"}, {"UNICODE", false, "unreserved"}, {"UNINSTALL", false, "unreserved"}, {"UNKNOWN", false, "unreserved"}, @@ -687,7 +722,11 @@ var Keywords = []KeywordsType{ {"WITHOUT", false, "unreserved"}, {"WITH_SYS_TABLE", false, "unreserved"}, {"WORKLOAD", false, "unreserved"}, + {"WRAPPER", false, "unreserved"}, {"X509", false, "unreserved"}, + {"XA", false, "unreserved"}, + {"XID", false, "unreserved"}, + {"XML", false, "unreserved"}, {"YEAR", false, "unreserved"}, {"ADMIN", false, "tidb"}, {"BATCH", false, "tidb"}, diff --git a/parser/keywords_test.go b/parser/keywords_test.go index 4bb3652..a5f4b4f 100644 --- a/parser/keywords_test.go +++ b/parser/keywords_test.go @@ -43,8 +43,8 @@ func TestKeywords(t *testing.T) { } func TestKeywordsLength(t *testing.T) { - if !reflect.DeepEqual(712, len(parser.Keywords)) { - t.Fatalf("got %v, want %v", len(parser.Keywords), 712) + if !reflect.DeepEqual(751, len(parser.Keywords)) { + t.Fatalf("got %v, want %v", len(parser.Keywords), 751) } reservedNr := 0 @@ -53,8 +53,8 @@ func TestKeywordsLength(t *testing.T) { reservedNr += 1 } } - if !reflect.DeepEqual(239, reservedNr) { - t.Fatalf("got %v, want %v", reservedNr, 239) + if !reflect.DeepEqual(246, reservedNr) { + t.Fatalf("got %v, want %v", reservedNr, 246) } } diff --git a/parser/lexer_test.go b/parser/lexer_test.go index 7086b6f..b7f1a4a 100644 --- a/parser/lexer_test.go +++ b/parser/lexer_test.go @@ -58,7 +58,7 @@ type testLiteralValue struct { func TestSingleCharOther(t *testing.T) { table := []testCaseItem{ - {"AT", identifier}, + {"AT", at}, {"?", paramMarker}, {"PLACEHOLDER", identifier}, {"=", eq}, diff --git a/parser/misc.go b/parser/misc.go index de90833..d01329a 100644 --- a/parser/misc.go +++ b/parser/misc.go @@ -182,6 +182,7 @@ var tokenMap = map[string]int{ "ASC": asc, "ASCII": ascii, "APPLY": apply, + "AT": at, "ATTRIBUTE": attribute, "ATTRIBUTES": attributes, "BATCH": batch, @@ -201,6 +202,7 @@ var tokenMap = map[string]int{ "BACKUP": backup, "BACKUPS": backups, "BDR": bdr, + "BEFORE": before, "BEGIN": begin, "BETWEEN": between, "BERNOULLI": bernoulli, @@ -271,10 +273,12 @@ var tokenMap = map[string]int{ "COMMIT": commit, "COMMITTED": committed, "COMPACT": compact, + "COMPLETION": completion, "COMPONENT": component, "COMPRESS": compress, "COMPRESSED": compressed, "COMPRESSION": compression, + "CONCURRENT": concurrent, "CONCURRENCY": concurrency, "CONFIG": config, "CONNECTION": connection, @@ -282,6 +286,7 @@ var tokenMap = map[string]int{ "CONSISTENT": consistent, "CONSTRAINT": constraint, "CONSTRAINTS": constraints, + "CONTAINS": contains, "CONTEXT": context, "CONTINUE": continueKwd, "CONVERT": convert, @@ -323,6 +328,7 @@ var tokenMap = map[string]int{ "DATABASES": databases, "DATE_ADD": dateAdd, "DATE_SUB": dateSub, + "DATAFILE": datafile, "DATE": dateType, "DATETIME": datetimeType, "DAY_HOUR": dayHour, @@ -345,6 +351,7 @@ var tokenMap = map[string]int{ "DEPTH": depth, "DESC": desc, "DESCRIBE": describe, + "DETERMINISTIC": deterministic, "DIAGNOSTICS": diagnostics, "DIGEST": digest, "DIRECTORY": directory, @@ -365,10 +372,13 @@ var tokenMap = map[string]int{ "DRY": dry, "DRYRUN": dryRun, "DUAL": dual, + "DUALITY": duality, "DUMP": dump, + "DUMPFILE": dumpfile, "DUPLICATE": duplicate, "DURATION": timeDuration, "DYNAMIC": dynamic, + "EACH": each, "ELSE": elseKwd, "ELSEIF": elseIfKwd, "ENABLE": enable, @@ -376,6 +386,7 @@ var tokenMap = map[string]int{ "ENCLOSED": enclosed, "ENCRYPTION": encryption, "END": end, + "ENDS": ends, "END_TIME": endTime, "ENFORCED": enforced, "ENGINE": engine, @@ -389,6 +400,7 @@ var tokenMap = map[string]int{ "ESCAPED": escaped, "EVENT": event, "EVENTS": events, + "EVERY": every, "EVOLVE": evolve, "EXACT": exact, "EXEC_ELAPSED": execElapsed, @@ -412,6 +424,7 @@ var tokenMap = map[string]int{ "FETCH": fetch, "FIELDS": fields, "FILE": file, + "FILTER": filter, "FIRST": first, "FIXED": fixed, "FLASHBACK": flashback, @@ -423,6 +436,7 @@ var tokenMap = map[string]int{ "FOLLOWERS": followers, "FOLLOWER_CONSTRAINTS": followerConstraints, "FOLLOWING": following, + "FOLLOWS": follows, "FOR": forKwd, "FORCE": force, "FOREIGN": foreign, @@ -442,7 +456,9 @@ var tokenMap = map[string]int{ "GRANT": grant, "GRANTS": grants, "GROUP_CONCAT": groupConcat, + "GROUP_REPLICATION": groupReplication, "GROUP": group, + "GTIDS": gtids, "HASH": hash, "HANDLER": handler, "HAVING": having, @@ -470,6 +486,7 @@ var tokenMap = map[string]int{ "INDEXES": indexes, "INFILE": infile, "INNER": inner, + "INNODB": innodb, "INOUT": inout, "INPLACE": inplace, "INSERT_METHOD": insertMethod, @@ -491,6 +508,7 @@ var tokenMap = map[string]int{ "INVERTED": inverted, "INVISIBLE": invisible, "INVOKER": invoker, + "IO_THREAD": ioThread, "ITERATE": iterate, "IO": io, "RU_PER_SEC": ruRate, @@ -510,6 +528,7 @@ var tokenMap = map[string]int{ "JSON_ARRAYAGG": jsonArrayagg, "JSON_OBJECTAGG": jsonObjectAgg, "JSON_SUM_CRC32": jsonSumCrc32, + "JSON_DUALITY_OBJECT": jsonDualityObject, "JSON": jsonType, "KEY_BLOCK_SIZE": keyBlockSize, "KEY": key, @@ -547,6 +566,7 @@ var tokenMap = map[string]int{ "LOCK": lock, "LOCKED": locked, "LOG": log, + "LOGFILE": logfile, "LOGS": logs, "LONG": long, "LONGBLOB": longblobType, @@ -575,6 +595,7 @@ var tokenMap = map[string]int{ "METADATA": metadata, "MICROSECOND": microsecond, "MIDDLEINT": middleIntType, + "MIGRATE": migrate, "MIN_ROWS": minRows, "MIN": min, "MINUTE_MICROSECOND": minuteMicrosecond, @@ -583,6 +604,7 @@ var tokenMap = map[string]int{ "MINVALUE": minValue, "MOD": mod, "MODE": mode, + "MODIFIES": modifies, "MODIFY": modify, "MONITOR": monitor, "MONTH": month, @@ -622,6 +644,7 @@ var tokenMap = map[string]int{ "TPCH_10": tpch10, "ON_DUPLICATE": onDuplicate, "ON": on, + "ONE": one, "ONLINE": online, "ONLY": only, "OPEN": open, @@ -631,6 +654,7 @@ var tokenMap = map[string]int{ "OPTION": option, "OPTIONAL": optional, "OPTIONALLY": optionally, + "OPTIONS": options, "OR": or, "ORDER": order, "OUT": out, @@ -655,6 +679,7 @@ var tokenMap = map[string]int{ "PER_TABLE": per_table, "PERSIST": persist, "PESSIMISTIC": pessimistic, + "PHASE": phase, "PLACEMENT": placement, "PLAN": plan, "PLAN_CACHE": planCache, @@ -665,11 +690,13 @@ var tokenMap = map[string]int{ "POLICY": policy, "POSITION": position, "PRE_SPLIT_REGIONS": preSplitRegions, + "PRECEDES": precedes, "PRECEDING": preceding, "PREDICATE": predicate, "PRECISION": precisionType, "PREPARE": prepare, "PRESERVE": preserve, + "PREV": prev, "PRIMARY": primary, "PRIMARY_REGION": primaryRegion, "PRIVILEGES": privileges, @@ -691,6 +718,7 @@ var tokenMap = map[string]int{ "RATE_LIMIT": rateLimit, "RAW": raw, "READ": read, + "READS": reads, "READ_ONLY": readOnly, "REAL": realType, "REBUILD": rebuild, @@ -739,6 +767,7 @@ var tokenMap = map[string]int{ "ROLE": role, "ROLLBACK": rollback, "ROLLUP": rollup, + "ROTATE": rotate, "ROUTINE": routine, "ROW_COUNT": rowCount, "ROW_FORMAT": rowFormat, @@ -747,6 +776,7 @@ var tokenMap = map[string]int{ "RTREE": rtree, "HYPO": hypo, "RESUME": resume, + "RETURN": returnKwd, "RETURNS": returns, "RUN": run, "RUNNING": running, @@ -772,6 +802,7 @@ var tokenMap = map[string]int{ "SEQUENCE": sequence, "SERIAL": serial, "SERIALIZABLE": serializable, + "SERVER": server, "SESSION": session, "SESSION_STATES": sessionStates, "SET": set, @@ -803,6 +834,7 @@ var tokenMap = map[string]int{ "SQL_CALC_FOUND_ROWS": sqlCalcFoundRows, "SQL_NO_CACHE": sqlNoCache, "SQL_SMALL_RESULT": sqlSmallResult, + "SQL_THREAD": sqlThread, "SQL_TSI_DAY": sqlTsiDay, "SQL_TSI_HOUR": sqlTsiHour, "SQL_TSI_MINUTE": sqlTsiMinute, @@ -822,6 +854,7 @@ var tokenMap = map[string]int{ "START_TIME": startTime, "START_TS": startTS, "STARTING": starting, + "STARTS": starts, "STATISTICS": statistics, "STATS_AUTO_RECALC": statsAutoRecalc, "STATS_BUCKETS": statsBuckets, @@ -858,6 +891,7 @@ var tokenMap = map[string]int{ "SUM": sum, "SUPER": super, "SURVIVAL_PREFERENCES": survivalPreferences, + "SUSPEND": suspend, "SWAPS": swaps, "SWITCHES": switchesSym, "SWITCH_GROUP": switchGroup, @@ -925,6 +959,8 @@ var tokenMap = map[string]int{ "UNBOUNDED": unbounded, "UNCOMMITTED": uncommitted, "UNDEFINED": undefined, + "UNDO": undo, + "UNDOFILE": undofile, "UNICODE": unicodeSym, "UNINSTALL": uninstall, "UNION": union, @@ -978,9 +1014,13 @@ var tokenMap = map[string]int{ "WIDTH": width, "WITH": with, "WITHOUT": without, + "WRAPPER": wrapper, "WRITE": write, "WORKLOAD": workload, "X509": x509, + "XA": xa, + "XID": xid, + "XML": xml, "XOR": xor, "YEAR_MONTH": yearMonth, "YEAR": yearType, diff --git a/parser/parse_alter.go b/parser/parse_alter.go index bdfc9c3..70f175a 100644 --- a/parser/parse_alter.go +++ b/parser/parse_alter.go @@ -57,6 +57,31 @@ func (r *rdParser) parseAlterStmtFamily() ast.StmtNode { return r.parseAlterPolicyStmt() case resource: return r.parseAlterResourceGroupStmt() + case event: + return r.parseAlterEventStmt() + case view, algorithm, sql: + return r.parseAlterViewStmt() + case definer: + // "ALTER" "DEFINER" eq Username prefixes views and events; the + // token after the clause decides. + if r.peekPastDefiner() == event { + return r.parseAlterEventStmt() + } + return r.parseAlterViewStmt() + case procedure: + return r.parseAlterProcedureStmt() + case function: + return r.parseAlterFunctionStmt() + case server: + return r.parseAlterServerStmt() + case tablespace, undo: + return r.parseAlterTablespaceStmt() + case logfile: + return r.parseAlterLogfileGroupStmt() + case library: + return r.parseAlterLibraryStmt() + case jsonType: + return r.parseAlterJSONDualityViewStmt() default: r.unsupported(fmt.Sprintf("ALTER %s", r.at(r.i+1).lit)) return nil @@ -1498,25 +1523,62 @@ func (r *rdParser) parseAlterUserStmt() ast.StmtNode { /**************************************Other ALTER kinds***************************************/ -// parseAlterInstanceStmt implements AlterInstanceStmt and InstanceOption. +// parseAlterInstanceStmt implements AlterInstanceStmt and +// InstanceOption: +// +// "ROTATE" ("INNODB" | "BINLOG") "MASTER" "KEY" +// | "RELOAD" "TLS" ["FOR" "CHANNEL" Identifier] +// ["NO" "ROLLBACK" "ON" "ERROR"] +// | "RELOAD" "KEYRING" +// | ("ENABLE" | "DISABLE") "INNODB" "REDO_LOG" +// +// KEYRING and REDO_LOG are not keywords and are matched in identifier +// position. func (r *rdParser) parseAlterInstanceStmt() ast.StmtNode { r.expect(alter) r.expect(instance) - // InstanceOption: "RELOAD" "TLS" ["NO" "ROLLBACK" "ON" "ERROR"] + switch r.tok() { + case rotate: + r.advance() + stmt := &ast.AlterInstanceStmt{} + switch r.tok() { + case innodb: + stmt.RotateInnoDBMasterKey = true + case binlog: + stmt.RotateBinlogMasterKey = true + default: + r.syntaxError() + } + r.advance() + r.expect(master) + r.expect(key) + return stmt + case enable, disable: + stmt := &ast.AlterInstanceStmt{EnableInnoDBRedoLog: r.tok() == enable} + stmt.DisableInnoDBRedoLog = !stmt.EnableInnoDBRedoLog + r.advance() + r.expect(innodb) + r.expectIdentLit("REDO_LOG") + return stmt + } r.expect(reload) - r.expect(tls) + if r.tok() != tls { + r.expectIdentLit("KEYRING") + return &ast.AlterInstanceStmt{ReloadKeyring: true} + } + r.advance() + stmt := &ast.AlterInstanceStmt{ReloadTLS: true} + if r.accept(forKwd) { + r.expect(channel) + stmt.Channel = r.parseIdentifier() + } if r.accept(no) { r.expect(rollback) r.expect(on) r.expect(errorKwd) - return &ast.AlterInstanceStmt{ - ReloadTLS: true, - NoRollbackOnError: true, - } - } - return &ast.AlterInstanceStmt{ - ReloadTLS: true, + stmt.NoRollbackOnError = true } + return stmt } // parseAlterRangeStmt implements AlterRangeStmt: diff --git a/parser/parse_brie.go b/parser/parse_brie.go index 8fb2e0f..819735d 100644 --- a/parser/parse_brie.go +++ b/parser/parse_brie.go @@ -27,12 +27,13 @@ import ( ) func init() { + // The stop and purge tokens are owned by parse_replication.go's + // families, which route "STOP BACKUP LOGS" and "PURGE BACKUP LOGS" + // here. rdRegister(backup, (*rdParser).parseBackupStmt) rdRegister(restore, (*rdParser).parseRestoreStmt) - rdRegister(stop, (*rdParser).parseStopBackupStmt) rdRegister(pause, (*rdParser).parsePauseBackupStmt) rdRegister(resume, (*rdParser).parseResumeBackupStmt) - rdRegister(purge, (*rdParser).parsePurgeBackupStmt) } // parseBackupStmt implements the BRIEStmt alternatives diff --git a/parser/parse_create_misc.go b/parser/parse_create_misc.go index 69bc145..a9b5bda 100644 --- a/parser/parse_create_misc.go +++ b/parser/parse_create_misc.go @@ -995,8 +995,9 @@ func (r *rdParser) parseDirectResourceGroupBackgroundOption() *ast.ResourceGroup // parseCreateMaskingPolicyStmt implements: // // CreateMaskingPolicyStmt: "CREATE" OrReplace "MASKING" "POLICY" -// IfNotExists PolicyName "ON" TableName '(' Identifier ')' "AS" -// Expression MaskingPolicyRestrictOnOpt MaskingPolicyStateOpt +// IfNotExists PolicyName "ON" TableName '(' Identifier ')' +// ("AS" Expression | "USING" '(' Expression ')') +// MaskingPolicyRestrictOnOpt MaskingPolicyStateOpt func (r *rdParser) parseCreateMaskingPolicyStmt() ast.StmtNode { r.expect(create) orReplace := false @@ -1014,8 +1015,17 @@ func (r *rdParser) parseCreateMaskingPolicyStmt() ast.StmtNode { r.expect(int('(')) column := r.parseIdentifier() r.expect(int(')')) - r.expect(as) - expr := r.parseExpression() + var expr ast.ExprNode + usingForm := false + if r.accept(using) { + usingForm = true + r.expect(int('(')) + expr = r.parseExpression() + r.expect(int(')')) + } else { + r.expect(as) + expr = r.parseExpression() + } restrictOps := r.parseMaskingPolicyRestrictOnOpt() state := r.parseMaskingPolicyStateOpt() if orReplace && ifNotExists { @@ -1030,6 +1040,7 @@ func (r *rdParser) parseCreateMaskingPolicyStmt() ast.StmtNode { Expr: expr, RestrictOps: restrictOps, MaskingPolicyState: *state, + Using: usingForm, } } diff --git a/parser/parse_create_table.go b/parser/parse_create_table.go index 3b8364a..c01122a 100644 --- a/parser/parse_create_table.go +++ b/parser/parse_create_table.go @@ -49,12 +49,34 @@ func (r *rdParser) parseCreateStmtFamily() ast.StmtNode { return r.parseCreateBindingStmt() case database: return r.parseCreateDatabaseStmt() - case index, unique, spatial, fulltext, vectorType, columnar: + case spatial: + if r.la(2) != index { + // "CREATE" "SPATIAL" "REFERENCE" "SYSTEM" ... + return r.parseCreateSpatialReferenceSystemStmt() + } + return r.parseCreateIndexStmt() + case index, unique, fulltext, vectorType, columnar: // IndexKeyTypeOpt "INDEX" return r.parseCreateIndexStmt() - case view, algorithm, definer, sql: - // ViewAlgorithm/ViewDefiner/ViewSQLSecurity ... "VIEW" + case view, algorithm, sql: + // ViewAlgorithm/ViewSQLSecurity ... "VIEW" return r.parseCreateViewStmt() + case definer: + // "CREATE" "DEFINER" eq Username prefixes views, events, + // triggers, and stored routines; the token after the clause + // decides. + switch r.peekPastDefiner() { + case event: + return r.parseCreateEventStmt() + case trigger: + return r.parseCreateTriggerStmt() + case procedure: + return r.parseCreateProcedureStmt() + case function: + return r.parseCreateFunctionFamily() + default: + return r.parseCreateViewStmt() + } case or: // "CREATE" "OR" "REPLACE" ... switch r.la(3) { @@ -62,6 +84,12 @@ func (r *rdParser) parseCreateStmtFamily() ast.StmtNode { return r.parseCreatePolicyStmt() case masking: return r.parseCreateMaskingPolicyStmt() + case spatial: + return r.parseCreateSpatialReferenceSystemStmt() + case library: + return r.parseCreateLibraryStmt() + case jsonType: + return r.parseCreateJSONDualityViewStmt() default: return r.parseCreateViewStmt() } @@ -85,9 +113,21 @@ func (r *rdParser) parseCreateStmtFamily() ast.StmtNode { case procedure: return r.parseCreateProcedureStmt() case function, aggregate: - // Only the loadable function form parses; a stored function - // fails inside (at its parameter list). - return r.parseCreateLoadableFunctionStmt() + return r.parseCreateFunctionFamily() + case event: + return r.parseCreateEventStmt() + case trigger: + return r.parseCreateTriggerStmt() + case server: + return r.parseCreateServerStmt() + case tablespace, undo: + return r.parseCreateTablespaceStmt() + case logfile: + return r.parseCreateLogfileGroupStmt() + case library: + return r.parseCreateLibraryStmt() + case jsonType: + return r.parseCreateJSONDualityViewStmt() default: // No production continues here; the automaton shifts CREATE and // errors at the lookahead, so advance before reporting. diff --git a/parser/parse_dml.go b/parser/parse_dml.go index db78d03..ea515df 100644 --- a/parser/parse_dml.go +++ b/parser/parse_dml.go @@ -434,6 +434,90 @@ func (r *rdParser) parseLoadDataStmt() ast.StmtNode { return x } +// parseImportTableStmt implements ImportTableStmt (MySQL 26.7 §15.2.6): +// "IMPORT" "TABLE" "FROM" stringLit (',' stringLit)*. +// The statement postdates the goyacc grammar; the shape follows the +// MySQL 26.7 reference manual. +func (r *rdParser) parseImportTableStmt() ast.StmtNode { + r.expect(importKwd) + r.expect(tableKwd) + r.expect(from) + stmt := &ast.ImportTableStmt{SdiFiles: []string{r.expect(stringLit).lit}} + for r.accept(int(',')) { + stmt.SdiFiles = append(stmt.SdiFiles, r.expect(stringLit).lit) + } + return stmt +} + +// parseLoadXMLStmt implements LoadXMLStmt (MySQL 26.7 §15.2.10): +// +// "LOAD" "XML" ["LOW_PRIORITY" | "CONCURRENT"] ["LOCAL"] "INFILE" +// stringLit ["REPLACE" | "IGNORE"] "INTO" "TABLE" TableName +// [CharsetKw CharsetName] ["ROWS" "IDENTIFIED" "BY" stringLit] +// ["IGNORE" NUM ("LINES" | "ROWS")] +// ColumnNameOrUserVarListOptWithBrackets ["SET" LoadDataSetList] +// +// The statement postdates the goyacc grammar; the shape follows the +// MySQL 26.7 reference manual, reusing the LOAD DATA helpers. +func (r *rdParser) parseLoadXMLStmt() ast.StmtNode { + r.expect(load) + r.expect(xml) + x := &ast.LoadXMLStmt{FileLocRef: ast.FileLocServerOrRemote} + switch r.tok() { + case lowPriority: + r.advance() + x.LowPriority = true + case concurrent: + r.advance() + x.Concurrent = true + } + if r.accept(local) { + x.FileLocRef = ast.FileLocClient + } + r.expect(infile) + x.Path = r.expect(stringLit).lit + x.OnDuplicate = ast.OnDuplicateKeyHandlingError + switch r.tok() { + case ignore: + r.advance() + x.OnDuplicate = ast.OnDuplicateKeyHandlingIgnore + case replace: + r.advance() + x.OnDuplicate = ast.OnDuplicateKeyHandlingReplace + } + r.expect(into) + r.expect(tableKwd) + x.Table = r.parseTableName() + if (r.tok() == character || r.tok() == charType) && r.la(1) == set { + r.advance() + r.advance() + cs := r.parseCharsetName() + x.Charset = &cs + } + if r.tok() == rows && r.la(1) == identified { + r.advance() + r.advance() + r.expect(by) + x.RowsIdentifiedBy = r.expect(stringLit).lit + } + if r.tok() == ignore { + // "IGNORE" NUM ("LINES" | "ROWS"); Restore() canonicalizes the + // LINES spelling to ROWS. + r.advance() + v := getUint64FromNUM(r.expect(intLit).item) + if r.tok() != lines && r.tok() != rows { + r.syntaxError() + } + r.advance() + x.IgnoreRows = &v + } + x.ColumnsAndUserVars = r.parseColumnNameOrUserVarListOptWithBrackets() + if r.accept(set) { + x.ColumnAssignments = r.parseLoadDataSetList() + } + return x +} + // parseColumnNameOrUserVarListOptWithBrackets implements the production // of the same name. func (r *rdParser) parseColumnNameOrUserVarListOptWithBrackets() []*ast.ColumnNameOrUserVar { diff --git a/parser/parse_drop.go b/parser/parse_drop.go index ca23953..724486e 100644 --- a/parser/parse_drop.go +++ b/parser/parse_drop.go @@ -95,6 +95,77 @@ func (r *rdParser) parseDropStmtFamily() ast.StmtNode { return r.parseDropBindingStmt() case procedure: return r.parseDropProcedureStmt() + case event: + // DropEventStmt: "DROP" "EVENT" IfExists TableName + r.advance() + r.advance() + ifExists := r.parseIfExists() + return &ast.DropEventStmt{IfExists: ifExists, EventName: r.parseTableName()} + case trigger: + // DropTriggerStmt: "DROP" "TRIGGER" IfExists TableName + r.advance() + r.advance() + ifExists := r.parseIfExists() + return &ast.DropTriggerStmt{IfExists: ifExists, TriggerName: r.parseTableName()} + case function: + // DropFunctionStmt: "DROP" "FUNCTION" IfExists TableName + r.advance() + r.advance() + ifExists := r.parseIfExists() + return &ast.DropFunctionStmt{IfExists: ifExists, FunctionName: r.parseTableName()} + case server: + // DropServerStmt: "DROP" "SERVER" IfExists Identifier + r.advance() + r.advance() + ifExists := r.parseIfExists() + return &ast.DropServerStmt{IfExists: ifExists, ServerName: ast.NewCIStr(r.parseIdentifier())} + case tablespace, undo: + // DropTablespaceStmt: "DROP" ["UNDO"] "TABLESPACE" Identifier + // TablespaceOptions + r.advance() + stmt := &ast.DropTablespaceStmt{Undo: r.accept(undo)} + r.expect(tablespace) + stmt.Name = ast.NewCIStr(r.parseIdentifier()) + stmt.Options = r.parseTablespaceOptions() + return stmt + case logfile: + // DropLogfileGroupStmt: "DROP" "LOGFILE" "GROUP" Identifier + // TablespaceOptions + r.advance() + r.advance() + r.expect(group) + stmt := &ast.DropLogfileGroupStmt{GroupName: ast.NewCIStr(r.parseIdentifier())} + stmt.Options = r.parseTablespaceOptions() + return stmt + case library: + // DropLibraryStmt: "DROP" "LIBRARY" IfExists TableName + r.advance() + r.advance() + ifExists := r.parseIfExists() + return &ast.DropLibraryStmt{IfExists: ifExists, LibraryName: r.parseTableName()} + case spatial: + // DropSpatialReferenceSystemStmt: "DROP" "SPATIAL" "REFERENCE" + // "SYSTEM" IfExists NUM + r.advance() + r.advance() + r.expectIdentLit("REFERENCE") + r.expect(system) + ifExists := r.parseIfExists() + return &ast.DropSpatialReferenceSystemStmt{ + IfExists: ifExists, + Srid: getUint64FromNUM(r.expect(intLit).item), + } + case masking: + // DropMaskingPolicyStmt: "DROP" "MASKING" "POLICY" IfExists + // PolicyName + r.advance() + r.advance() + r.expect(policy) + ifExists := r.parseIfExists() + return &ast.DropMaskingPolicyStmt{ + IfExists: ifExists, + PolicyName: ast.NewCIStr(r.parseIdentifier()), + } default: r.unsupported(fmt.Sprintf("DROP %s", r.at(r.i+1).lit)) } diff --git a/parser/parse_func.go b/parser/parse_func.go index e8a108c..8051151 100644 --- a/parser/parse_func.go +++ b/parser/parse_func.go @@ -101,6 +101,25 @@ func (r *rdParser) parseSimpleExprAtom() ast.ExprNode { return result } return r.setOrigin(r.parseSubSelect(), start) + case jsonDualityObject: + // SimpleExpr: "JSON_DUALITY_OBJECT" '(' [stringLit ':' Expression + // (',' stringLit ':' Expression)*] ')' — the select-list + // constructor of JSON duality views (MySQL 26.7 §15.1.14). + r.advance() + r.expect(int('(')) + x := &ast.JSONDualityObjectExpr{} + if r.tok() != int(')') { + for { + key := r.expect(stringLit).lit + r.expect(int(':')) + x.Items = append(x.Items, &ast.JSONDualityObjectItem{Key: key, Value: r.parseExpression()}) + if !r.accept(int(',')) { + break + } + } + } + r.expect(int(')')) + return r.setOrigin(x, start) case row: // SimpleExpr: "ROW" '(' ExpressionList ',' Expression ')' r.advance() diff --git a/parser/parse_handler.go b/parser/parse_handler.go new file mode 100644 index 0000000..fc0aba4 --- /dev/null +++ b/parser/parse_handler.go @@ -0,0 +1,120 @@ +// Copyright 2026 The sqlc Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +// The HANDLER statements (MySQL 26.7 §15.2.5). These postdate the +// goyacc grammar; the productions below are written from the MySQL 26.7 +// reference manual in the same style as parser.y. + +import ( + "github.com/sqlc-dev/marino/ast" +) + +func init() { + rdRegister(handler, (*rdParser).parseHandlerStmt) +} + +// parseHandlerStmt implements the HANDLER statement family: +// +// "HANDLER" TableName "OPEN" [["AS"] Identifier] +// | "HANDLER" TableName "READ" HandlerReadTail +// | "HANDLER" TableName "CLOSE" +func (r *rdParser) parseHandlerStmt() ast.StmtNode { + r.expect(handler) + table := r.parseTableName() + switch r.tok() { + case open: + r.advance() + stmt := &ast.HandlerOpenStmt{Table: table} + if r.accept(as) { + stmt.Alias = ast.NewCIStr(r.parseIdentifier()) + } else if isIdentifierTok(r.tok()) { + stmt.Alias = ast.NewCIStr(r.parseIdentifier()) + } + return stmt + case read: + r.advance() + return r.parseHandlerReadTail(table) + case close: + r.advance() + return &ast.HandlerCloseStmt{Table: table} + } + r.syntaxError() + return nil +} + +// parseHandlerReadTail implements the tail of HANDLER ... READ: +// +// Identifier HandlerCompareOp '(' ExpressionListOpt ')' HandlerReadTail2 +// | Identifier ("FIRST" | "NEXT" | "PREV" | "LAST") HandlerReadTail2 +// | ("FIRST" | "NEXT") HandlerReadTail2 +// +// with HandlerReadTail2: ["WHERE" Expression] [LimitClause]. An index +// name may also be "PRIMARY", naming the primary key. +func (r *rdParser) parseHandlerReadTail(table *ast.TableName) ast.StmtNode { + stmt := &ast.HandlerReadStmt{Table: table} + switch r.tok() { + case first: + r.advance() + stmt.Direction = ast.HandlerReadFirst + case next: + r.advance() + stmt.Direction = ast.HandlerReadNext + default: + stmt.IndexName = r.parseCacheIndexName() + switch r.tok() { + case first: + r.advance() + stmt.Direction = ast.HandlerReadFirst + case next: + r.advance() + stmt.Direction = ast.HandlerReadNext + case prev: + r.advance() + stmt.Direction = ast.HandlerReadPrev + case last: + r.advance() + stmt.Direction = ast.HandlerReadLast + case eq: + r.advance() + stmt.Op = ast.HandlerOpEQ + case le: + r.advance() + stmt.Op = ast.HandlerOpLE + case ge: + r.advance() + stmt.Op = ast.HandlerOpGE + case int('<'): + r.advance() + stmt.Op = ast.HandlerOpLT + case int('>'): + r.advance() + stmt.Op = ast.HandlerOpGT + default: + r.syntaxError() + } + if stmt.Op != ast.HandlerOpNone { + r.expect(int('(')) + stmt.Values = r.parseExpressionListOpt() + r.expect(int(')')) + } + } + if r.accept(where) { + stmt.Where = r.parseExpression() + } + if r.tok() == limit { + stmt.Limit = r.parseSelectStmtLimit() + } + return stmt +} diff --git a/parser/parse_misc.go b/parser/parse_misc.go index 14041cb..a57cf01 100644 --- a/parser/parse_misc.go +++ b/parser/parse_misc.go @@ -321,11 +321,19 @@ func (r *rdParser) parseRestartStmt() ast.StmtNode { } // parseLockStmtFamily dispatches LOCK-leading statements: LockTablesStmt -// here, LockStatsStmt in parse_tidb.go. +// and LockInstanceStmt here, LockStatsStmt in parse_tidb.go. func (r *rdParser) parseLockStmtFamily() ast.StmtNode { if r.la(1) == stats { return r.parseLockStatsStmt() } + if r.la(1) == instance { + // LockInstanceStmt: "LOCK" "INSTANCE" "FOR" "BACKUP" + r.advance() + r.advance() + r.expect(forKwd) + r.expect(backup) + return &ast.LockInstanceStmt{} + } // LockTablesStmt: "LOCK" TablesTerminalSym TableLockList r.expect(lock) if !r.accept(tables) && !r.accept(tableKwd) { @@ -367,11 +375,18 @@ func (r *rdParser) parseTableLock() ast.TableLock { } // parseUnlockStmtFamily dispatches UNLOCK-leading statements: -// UnlockTablesStmt here, UnlockStatsStmt in parse_tidb.go. +// UnlockTablesStmt and UnlockInstanceStmt here, UnlockStatsStmt in +// parse_tidb.go. func (r *rdParser) parseUnlockStmtFamily() ast.StmtNode { if r.la(1) == stats { return r.parseUnlockStatsStmt() } + if r.la(1) == instance { + // UnlockInstanceStmt: "UNLOCK" "INSTANCE" + r.advance() + r.advance() + return &ast.UnlockInstanceStmt{} + } // UnlockTablesStmt: "UNLOCK" TablesTerminalSym r.expect(unlock) if !r.accept(tables) && !r.accept(tableKwd) { diff --git a/parser/parse_mysql_admin.go b/parser/parse_mysql_admin.go index 4956678..d75826f 100644 --- a/parser/parse_mysql_admin.go +++ b/parser/parse_mysql_admin.go @@ -126,19 +126,15 @@ func (r *rdParser) parseRepairTablesStmt() ast.StmtNode { } } -// parseCreateLoadableFunctionStmt implements CreateLoadableFunctionStmt -// (the CREATE FUNCTION statement for loadable functions; stored -// functions do not parse): +// finishCreateLoadableFunctionStmt implements the rest of +// CreateLoadableFunctionStmt (the CREATE FUNCTION statement for +// loadable functions) after parseCreateFunctionFamily has parsed +// through the function name: // // "CREATE" ["AGGREGATE"] "FUNCTION" IfNotExists Identifier // "RETURNS" ("STRING" | "INTEGER" | "INT" | "REAL" | "DECIMAL") // "SONAME" stringLit -func (r *rdParser) parseCreateLoadableFunctionStmt() ast.StmtNode { - r.expect(create) - aggregateOpt := r.accept(aggregate) - r.expect(function) - ifNotExists := r.parseIfNotExists() - name := r.parseIdentifier() +func (r *rdParser) finishCreateLoadableFunctionStmt(aggregateOpt, ifNotExists bool, name ast.CIStr) ast.StmtNode { r.expect(returns) var returnType ast.LoadableFunctionReturnType switch r.tok() { @@ -158,7 +154,7 @@ func (r *rdParser) parseCreateLoadableFunctionStmt() ast.StmtNode { return &ast.CreateLoadableFunctionStmt{ IfNotExists: ifNotExists, Aggregate: aggregateOpt, - FunctionName: ast.NewCIStr(name), + FunctionName: name, ReturnType: returnType, SoName: r.expect(stringLit).lit, } @@ -355,11 +351,18 @@ func (r *rdParser) parseCacheIndexName() ast.CIStr { return ast.NewCIStr(r.parseIdentifier()) } -// parseResetStmt implements the RESET statement family, of which only -// ResetPersistStmt is supported: -// "RESET" "PERSIST" [["IF" "EXISTS"] Identifier]. +// parseResetStmt implements the RESET statement family: +// ResetPersistStmt ("RESET" "PERSIST" [["IF" "EXISTS"] Identifier]) +// here, with ResetReplicaStmt and ResetBinaryLogsAndGtidsStmt in +// parse_replication.go. func (r *rdParser) parseResetStmt() ast.StmtNode { - if r.la(1) != persist { + switch r.la(1) { + case replica: + return r.parseResetReplicaStmt() + case binaryType: + return r.parseResetBinaryLogsAndGtidsStmt() + case persist: + default: // The other RESET forms do not parse; fail at RESET the way an // unregistered statement head does. r.syntaxError() diff --git a/parser/parse_mysql_ddl.go b/parser/parse_mysql_ddl.go new file mode 100644 index 0000000..e13c9dc --- /dev/null +++ b/parser/parse_mysql_ddl.go @@ -0,0 +1,889 @@ +// Copyright 2026 The sqlc Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +// The MySQL data definition statements that postdate the goyacc grammar +// (MySQL 26.7 §15.1): events, triggers, stored functions (and the ALTER +// forms of stored routines), ALTER VIEW, servers, tablespaces, logfile +// groups, spatial reference systems, libraries, and JSON duality views. +// The productions are written from the MySQL 26.7 reference manual in +// the same style as parser.y. The CREATE/ALTER/DROP statement heads +// dispatch here from parse_create_table.go, parse_alter.go, and +// parse_drop.go. + +import ( + "strings" + + "github.com/sqlc-dev/marino/ast" + "github.com/sqlc-dev/marino/auth" +) + +// parseDefinerOpt implements DefinerOpt: empty | "DEFINER" eq Username. +// It returns nil for the empty alternative. +func (r *rdParser) parseDefinerOpt() *auth.UserIdentity { + if r.tok() != definer { + return nil + } + r.advance() + r.expect(eq) + return r.parseUsername() +} + +// peekPastDefiner reports the token following the "DEFINER" eq Username +// clause that begins one token past the cursor (which sits on CREATE or +// ALTER), consuming nothing. It returns -1 when the clause does not +// parse; the caller's default alternative then reports the error. +func (r *rdParser) peekPastDefiner() int { + tok := -1 + m := r.mark() + r.try(func() { + r.advance() // CREATE or ALTER + r.advance() // DEFINER + r.expect(eq) + r.parseUsername() + tok = r.tok() + }) + r.rewind(m) + return tok +} + +// expectIdentLit consumes the current token, which must occupy +// identifier position and match name case-insensitively. It is used for +// the words of these productions that are not keywords (non-reserved in +// MySQL). +func (r *rdParser) expectIdentLit(name string) { + if !isIdentifierTok(r.tok()) || !strings.EqualFold(r.cur().lit, name) { + r.syntaxError() + } + r.advance() +} + +/* + * Events + */ + +// parseEventSchedule implements the schedule of ON SCHEDULE: +// +// "AT" Expression +// | "EVERY" Expression TimeUnit ["STARTS" Expression] +// ["ENDS" Expression] +func (r *rdParser) parseEventSchedule() *ast.EventSchedule { + if r.accept(at) { + return &ast.EventSchedule{At: r.parseExpression()} + } + r.expect(every) + sched := &ast.EventSchedule{Every: r.parseExpression(), Unit: r.parseTimeUnit()} + if r.accept(starts) { + sched.Starts = r.parseExpression() + } + if r.accept(ends) { + sched.Ends = r.parseExpression() + } + return sched +} + +// parseEventCompletion implements the ON COMPLETION clause, with ON +// already consumed: "COMPLETION" ["NOT"] "PRESERVE". +func (r *rdParser) parseEventCompletion() ast.EventCompletion { + r.expect(completion) + completionV := ast.EventCompletionPreserve + if r.accept(not) { + completionV = ast.EventCompletionNotPreserve + } + r.expect(preserve) + return completionV +} + +// parseEventStateOpt implements the event state clause: +// empty | "ENABLE" | "DISABLE" ["ON" "REPLICA"]. +func (r *rdParser) parseEventStateOpt() ast.EventState { + switch r.tok() { + case enable: + r.advance() + return ast.EventStateEnable + case disable: + r.advance() + if r.accept(on) { + r.expect(replica) + return ast.EventStateDisableOnReplica + } + return ast.EventStateDisable + } + return ast.EventStateDefault +} + +// parseEventBody parses an event, trigger, or function body statement +// and records its source text the way CreateProcedureStmt does. +func (r *rdParser) parseRoutineBody() ast.StmtNode { + bodyStart := r.cur().offset + body := r.parseProcedureProcStmt() + r.p.setNodeText(body, strings.TrimSpace(r.src[bodyStart:r.cur().offset])) + return body +} + +// parseCreateEventStmt implements CreateEventStmt: +// +// "CREATE" DefinerOpt "EVENT" IfNotExists TableName +// "ON" "SCHEDULE" EventSchedule ["ON" "COMPLETION" ["NOT"] +// "PRESERVE"] EventStateOpt ["COMMENT" stringLit] +// "DO" ProcedureProcStmt +func (r *rdParser) parseCreateEventStmt() ast.StmtNode { + r.expect(create) + stmt := &ast.CreateEventStmt{Definer: r.parseDefinerOpt()} + r.expect(event) + stmt.IfNotExists = r.parseIfNotExists() + stmt.EventName = r.parseTableName() + r.expect(on) + r.expect(schedule) + stmt.Schedule = r.parseEventSchedule() + if r.accept(on) { + stmt.Completion = r.parseEventCompletion() + } + stmt.State = r.parseEventStateOpt() + if r.accept(comment) { + s := r.expect(stringLit).lit + stmt.Comment = &s + } + r.expect(do) + stmt.Body = r.parseRoutineBody() + return stmt +} + +// parseAlterEventStmt implements AlterEventStmt: +// +// "ALTER" DefinerOpt "EVENT" TableName ["ON" "SCHEDULE" EventSchedule] +// ["ON" "COMPLETION" ["NOT"] "PRESERVE"] ["RENAME" "TO" TableName] +// EventStateOpt ["COMMENT" stringLit] ["DO" ProcedureProcStmt] +func (r *rdParser) parseAlterEventStmt() ast.StmtNode { + r.expect(alter) + stmt := &ast.AlterEventStmt{Definer: r.parseDefinerOpt()} + r.expect(event) + stmt.EventName = r.parseTableName() + if r.tok() == on && r.la(1) == schedule { + r.advance() + r.advance() + stmt.Schedule = r.parseEventSchedule() + } + if r.tok() == on && r.la(1) == completion { + r.advance() + stmt.Completion = r.parseEventCompletion() + } + if r.accept(rename) { + r.expect(to) + stmt.RenameTo = r.parseTableName() + } + stmt.State = r.parseEventStateOpt() + if r.accept(comment) { + s := r.expect(stringLit).lit + stmt.Comment = &s + } + if r.accept(do) { + stmt.Body = r.parseRoutineBody() + } + return stmt +} + +/* + * Triggers + */ + +// parseCreateTriggerStmt implements CreateTriggerStmt: +// +// "CREATE" DefinerOpt "TRIGGER" IfNotExists TableName +// ("BEFORE" | "AFTER") ("INSERT" | "UPDATE" | "DELETE") +// "ON" TableName "FOR" "EACH" "ROW" +// [("FOLLOWS" | "PRECEDES") Identifier] ProcedureProcStmt +func (r *rdParser) parseCreateTriggerStmt() ast.StmtNode { + r.expect(create) + stmt := &ast.CreateTriggerStmt{Definer: r.parseDefinerOpt()} + r.expect(trigger) + stmt.IfNotExists = r.parseIfNotExists() + stmt.TriggerName = r.parseTableName() + switch r.tok() { + case before: + stmt.TriggerTime = ast.TriggerTimeBefore + case after: + stmt.TriggerTime = ast.TriggerTimeAfter + default: + r.syntaxError() + } + r.advance() + switch r.tok() { + case insert: + stmt.TriggerEvent = ast.TriggerEventInsert + case update: + stmt.TriggerEvent = ast.TriggerEventUpdate + case deleteKwd: + stmt.TriggerEvent = ast.TriggerEventDelete + default: + r.syntaxError() + } + r.advance() + r.expect(on) + stmt.Table = r.parseTableName() + r.expect(forKwd) + r.expect(each) + r.expect(row) + switch r.tok() { + case follows: + r.advance() + stmt.Order = &ast.TriggerOrder{OtherTrigger: ast.NewCIStr(r.parseIdentifier())} + case precedes: + r.advance() + stmt.Order = &ast.TriggerOrder{Precedes: true, OtherTrigger: ast.NewCIStr(r.parseIdentifier())} + } + stmt.Body = r.parseRoutineBody() + return stmt +} + +/* + * Stored routines + */ + +// parseRoutineCharacteristics implements the characteristic list of the +// stored routine statements: +// +// ("COMMENT" stringLit | "LANGUAGE" (Identifier | "SQL") +// | ["NOT"] "DETERMINISTIC" | "CONTAINS" "SQL" | "NO" "SQL" +// | "READS" "SQL" "DATA" | "MODIFIES" "SQL" "DATA" +// | "SQL" "SECURITY" ("DEFINER" | "INVOKER"))* +// +// MySQL rejects [NOT] DETERMINISTIC in the ALTER statements at a later +// stage; the list is shared here and leaves that to semantics. +func (r *rdParser) parseRoutineCharacteristics() ast.RoutineCharacteristics { + var c ast.RoutineCharacteristics + for { + switch r.tok() { + case comment: + r.advance() + s := r.expect(stringLit).lit + c.Comment = &s + case language: + r.advance() + if r.tok() == sql { + r.advance() + c.Language = ast.NewCIStr("SQL") + } else { + c.Language = ast.NewCIStr(strings.ToUpper(r.parseIdentifier())) + } + case deterministic: + r.advance() + c.Determinism = ast.RoutineDeterministic + case not: + if r.la(1) != deterministic { + return c + } + r.advance() + r.advance() + c.Determinism = ast.RoutineNotDeterministic + case contains: + r.advance() + r.expect(sql) + c.DataAccess = ast.RoutineContainsSQL + case no: + if r.la(1) != sql { + return c + } + r.advance() + r.advance() + c.DataAccess = ast.RoutineNoSQL + case reads: + r.advance() + r.expect(sql) + r.expect(data) + c.DataAccess = ast.RoutineReadsSQLData + case modifies: + r.advance() + r.expect(sql) + r.expect(data) + c.DataAccess = ast.RoutineModifiesSQLData + case sql: + if r.la(1) != security { + return c + } + r.advance() + r.advance() + var s ast.ViewSecurity + switch r.tok() { + case definer: + s = ast.SecurityDefiner + case invoker: + s = ast.SecurityInvoker + default: + r.syntaxError() + } + r.advance() + c.Security = &s + default: + return c + } + } +} + +// parseCreateFunctionFamily dispatches CREATE FUNCTION between the +// stored function form (CreateFunctionStmt, recognized by its mandatory +// parameter list) and the loadable function form +// (CreateLoadableFunctionStmt, whose name is followed directly by +// RETURNS): +// +// "CREATE" DefinerOpt ["AGGREGATE"] "FUNCTION" IfNotExists ... +func (r *rdParser) parseCreateFunctionFamily() ast.StmtNode { + r.expect(create) + definer := r.parseDefinerOpt() + aggregateOpt := definer == nil && r.accept(aggregate) + r.expect(function) + ifNotExists := r.parseIfNotExists() + name := r.parseTableName() + if !aggregateOpt && r.tok() == int('(') { + return r.finishCreateStoredFunctionStmt(definer, ifNotExists, name) + } + if definer != nil || name.Schema.O != "" { + // The loadable form takes neither a definer nor a qualified name. + r.syntaxError() + } + return r.finishCreateLoadableFunctionStmt(aggregateOpt, ifNotExists, name.Name) +} + +// finishCreateStoredFunctionStmt implements the rest of +// CreateFunctionStmt after the function name: +// +// '(' [Identifier Type (',' Identifier Type)*] ')' "RETURNS" Type +// RoutineCharacteristics ProcedureProcStmt +func (r *rdParser) finishCreateStoredFunctionStmt(definer *auth.UserIdentity, ifNotExists bool, name *ast.TableName) ast.StmtNode { + stmt := &ast.CreateFunctionStmt{ + IfNotExists: ifNotExists, + Definer: definer, + FunctionName: name, + } + r.expect(int('(')) + if r.tok() != int(')') { + for { + param := &ast.FunctionParam{ParamName: r.parseIdentifier()} + param.ParamType = r.parseType() + stmt.Params = append(stmt.Params, param) + if !r.accept(int(',')) { + break + } + } + } + r.expect(int(')')) + r.expect(returns) + stmt.ReturnType = r.parseType() + stmt.Characteristics = r.parseRoutineCharacteristics() + stmt.Body = r.parseRoutineBody() + return stmt +} + +// parseAlterFunctionStmt implements AlterFunctionStmt: +// "ALTER" "FUNCTION" TableName RoutineCharacteristics. +func (r *rdParser) parseAlterFunctionStmt() ast.StmtNode { + r.expect(alter) + r.expect(function) + return &ast.AlterFunctionStmt{ + FunctionName: r.parseTableName(), + Characteristics: r.parseRoutineCharacteristics(), + } +} + +// parseAlterProcedureStmt implements AlterProcedureStmt: +// "ALTER" "PROCEDURE" TableName RoutineCharacteristics. +func (r *rdParser) parseAlterProcedureStmt() ast.StmtNode { + r.expect(alter) + r.expect(procedure) + return &ast.AlterProcedureStmt{ + ProcedureName: r.parseTableName(), + Characteristics: r.parseRoutineCharacteristics(), + } +} + +/* + * ALTER VIEW + */ + +// parseAlterViewStmt implements AlterViewStmt: +// +// "ALTER" ViewAlgorithm ViewDefiner ViewSQLSecurity "VIEW" ViewName +// ViewFieldList "AS" CreateViewSelectOpt ViewCheckOption +// +// mirroring parseCreateViewStmt, including its recording of the select +// statement's source text. +func (r *rdParser) parseAlterViewStmt() ast.StmtNode { + r.expect(alter) + algorithmV := ast.AlgorithmUndefined + if r.tok() == algorithm { + r.advance() + r.expect(eq) + switch r.tok() { + case undefined: + algorithmV = ast.AlgorithmUndefined + case merge: + algorithmV = ast.AlgorithmMerge + case temptable: + algorithmV = ast.AlgorithmTemptable + default: + r.syntaxError() + } + r.advance() + } + definerV := &auth.UserIdentity{CurrentUser: true} + if d := r.parseDefinerOpt(); d != nil { + definerV = d + } + securityV := ast.SecurityDefiner + if r.tok() == sql { + r.advance() + r.expect(security) + switch r.tok() { + case definer: + securityV = ast.SecurityDefiner + case invoker: + securityV = ast.SecurityInvoker + default: + r.syntaxError() + } + r.advance() + } + r.expect(view) + viewName := r.parseTableName() + var cols []ast.CIStr + if r.tok() == int('(') { + r.advance() + cols = []ast.CIStr{ast.NewCIStr(r.parseIdentifier())} + for r.accept(int(',')) { + cols = append(cols, ast.NewCIStr(r.parseIdentifier())) + } + r.expect(int(')')) + } + r.expect(as) + startOffset := r.cur().offset + selStmt := r.parseCreateViewSelectOpt() + endOffset := r.cur().offset + x := &ast.AlterViewStmt{ + ViewName: viewName, + Select: selStmt, + Algorithm: algorithmV, + Definer: definerV, + Security: securityV, + } + if cols != nil { + x.Cols = cols + } + if r.tok() == with { + r.advance() + switch r.tok() { + case cascaded: + x.CheckOption = ast.CheckOptionCascaded + case local: + x.CheckOption = ast.CheckOptionLocal + default: + r.syntaxError() + } + r.advance() + r.expect(check) + r.expect(option) + } else { + x.CheckOption = ast.CheckOptionCascaded + } + r.p.setNodeText(selStmt, strings.TrimSpace(r.src[startOffset:endOffset])) + return x +} + +/* + * Servers + */ + +// parseServerOptions implements the OPTIONS clause of CREATE/ALTER +// SERVER: "OPTIONS" '(' ServerOption (',' ServerOption)* ')'. +func (r *rdParser) parseServerOptions() []*ast.ServerOption { + r.expect(options) + r.expect(int('(')) + opts := []*ast.ServerOption{r.parseServerOption()} + for r.accept(int(',')) { + opts = append(opts, r.parseServerOption()) + } + r.expect(int(')')) + return opts +} + +// parseServerOption implements ServerOption: +// +// ("HOST" | "DATABASE" | "USER" | "PASSWORD" | "SOCKET" | "OWNER") +// stringLit +// | "PORT" NUM +// +// HOST, SOCKET, OWNER, and PORT are not keywords and are matched in +// identifier position. +func (r *rdParser) parseServerOption() *ast.ServerOption { + var name string + switch { + case r.tok() == user || r.tok() == password || r.tok() == database: + name = strings.ToUpper(r.cur().lit) + case isIdentifierTok(r.tok()): + name = strings.ToUpper(r.cur().lit) + switch name { + case "HOST", "SOCKET", "OWNER", "PORT": + default: + r.syntaxError() + } + default: + r.syntaxError() + } + r.advance() + if name == "PORT" { + return &ast.ServerOption{Name: name, Value: ast.NewValueExpr(r.expect(intLit).item, "", "")} + } + return &ast.ServerOption{Name: name, Value: ast.NewValueExpr(r.expect(stringLit).lit, "", "")} +} + +// parseCreateServerStmt implements CreateServerStmt: +// +// "CREATE" "SERVER" Identifier "FOREIGN" "DATA" "WRAPPER" Identifier +// ServerOptions +func (r *rdParser) parseCreateServerStmt() ast.StmtNode { + r.expect(create) + r.expect(server) + stmt := &ast.CreateServerStmt{ServerName: ast.NewCIStr(r.parseIdentifier())} + r.expect(foreign) + r.expect(data) + r.expect(wrapper) + stmt.Wrapper = ast.NewCIStr(r.parseIdentifier()) + stmt.Options = r.parseServerOptions() + return stmt +} + +// parseAlterServerStmt implements AlterServerStmt: +// "ALTER" "SERVER" Identifier ServerOptions. +func (r *rdParser) parseAlterServerStmt() ast.StmtNode { + r.expect(alter) + r.expect(server) + return &ast.AlterServerStmt{ + ServerName: ast.NewCIStr(r.parseIdentifier()), + Options: r.parseServerOptions(), + } +} + +/* + * Tablespaces and logfile groups + */ + +// parseTablespaceOptions implements the option list shared by the +// tablespace and logfile group statements: +// +// (("INITIAL_SIZE" | "MAX_SIZE" | "EXTENT_SIZE" | "FILE_BLOCK_SIZE" +// | "UNDO_BUFFER_SIZE" | "REDO_BUFFER_SIZE" | "AUTOEXTEND_SIZE" +// | "NODEGROUP") EqOpt LengthNum +// | ("ENCRYPTION" | "COMMENT" | "ENGINE_ATTRIBUTE") EqOpt stringLit +// | "ENGINE" EqOpt (Identifier | stringLit) +// | "WAIT")* +// +// The size option names that are not keywords are matched in identifier +// position. +func (r *rdParser) parseTablespaceOptions() []*ast.TablespaceOption { + var opts []*ast.TablespaceOption + for { + switch r.tok() { + case autoextendSize: + r.advance() + r.parseEqOpt() + opts = append(opts, &ast.TablespaceOption{Tp: ast.TablespaceOptionAutoextendSize, UintValue: getUint64FromNUM(r.expect(intLit).item)}) + case nodegroup: + r.advance() + r.parseEqOpt() + opts = append(opts, &ast.TablespaceOption{Tp: ast.TablespaceOptionNodegroup, UintValue: getUint64FromNUM(r.expect(intLit).item)}) + case wait: + r.advance() + opts = append(opts, &ast.TablespaceOption{Tp: ast.TablespaceOptionWait}) + case encryption: + r.advance() + r.parseEqOpt() + opts = append(opts, &ast.TablespaceOption{Tp: ast.TablespaceOptionEncryption, StrValue: r.expect(stringLit).lit}) + case comment: + r.advance() + r.parseEqOpt() + opts = append(opts, &ast.TablespaceOption{Tp: ast.TablespaceOptionComment, StrValue: r.expect(stringLit).lit}) + case engine_attribute: + r.advance() + r.parseEqOpt() + opts = append(opts, &ast.TablespaceOption{Tp: ast.TablespaceOptionEngineAttribute, StrValue: r.expect(stringLit).lit}) + case engine: + r.advance() + r.parseEqOpt() + var name string + if r.tok() == stringLit { + name = r.cur().lit + r.advance() + } else { + name = r.parseIdentifier() + } + opts = append(opts, &ast.TablespaceOption{Tp: ast.TablespaceOptionEngine, StrValue: name}) + default: + var tp ast.TablespaceOptionType + switch { + case !isIdentifierTok(r.tok()): + return opts + case strings.EqualFold(r.cur().lit, "INITIAL_SIZE"): + tp = ast.TablespaceOptionInitialSize + case strings.EqualFold(r.cur().lit, "MAX_SIZE"): + tp = ast.TablespaceOptionMaxSize + case strings.EqualFold(r.cur().lit, "EXTENT_SIZE"): + tp = ast.TablespaceOptionExtentSize + case strings.EqualFold(r.cur().lit, "FILE_BLOCK_SIZE"): + tp = ast.TablespaceOptionFileBlockSize + case strings.EqualFold(r.cur().lit, "UNDO_BUFFER_SIZE"): + tp = ast.TablespaceOptionUndoBufferSize + case strings.EqualFold(r.cur().lit, "REDO_BUFFER_SIZE"): + tp = ast.TablespaceOptionRedoBufferSize + default: + return opts + } + r.advance() + r.parseEqOpt() + opts = append(opts, &ast.TablespaceOption{Tp: tp, UintValue: getUint64FromNUM(r.expect(intLit).item)}) + } + } +} + +// parseCreateTablespaceStmt implements CreateTablespaceStmt: +// +// "CREATE" ["UNDO"] "TABLESPACE" Identifier +// ["ADD" "DATAFILE" stringLit] ["USE" "LOGFILE" "GROUP" Identifier] +// TablespaceOptions +func (r *rdParser) parseCreateTablespaceStmt() ast.StmtNode { + r.expect(create) + stmt := &ast.CreateTablespaceStmt{Undo: r.accept(undo)} + r.expect(tablespace) + stmt.Name = ast.NewCIStr(r.parseIdentifier()) + if r.accept(add) { + r.expect(datafile) + stmt.Datafile = r.expect(stringLit).lit + } + if r.accept(use) { + r.expect(logfile) + r.expect(group) + stmt.UseLogfileGroup = ast.NewCIStr(r.parseIdentifier()) + } + stmt.Options = r.parseTablespaceOptions() + return stmt +} + +// parseAlterTablespaceStmt implements AlterTablespaceStmt: +// +// "ALTER" ["UNDO"] "TABLESPACE" Identifier +// [("ADD" | "DROP") "DATAFILE" stringLit | "RENAME" "TO" Identifier +// | "SET" ("ACTIVE" | "INACTIVE")] TablespaceOptions +// +// ACTIVE and INACTIVE are not keywords and are matched in identifier +// position. +func (r *rdParser) parseAlterTablespaceStmt() ast.StmtNode { + r.expect(alter) + stmt := &ast.AlterTablespaceStmt{Undo: r.accept(undo)} + r.expect(tablespace) + stmt.Name = ast.NewCIStr(r.parseIdentifier()) + switch r.tok() { + case add: + r.advance() + r.expect(datafile) + stmt.Op = ast.AlterTablespaceAddDatafile + stmt.Datafile = r.expect(stringLit).lit + case drop: + r.advance() + r.expect(datafile) + stmt.Op = ast.AlterTablespaceDropDatafile + stmt.Datafile = r.expect(stringLit).lit + case rename: + r.advance() + r.expect(to) + stmt.Op = ast.AlterTablespaceRenameTo + stmt.NewName = ast.NewCIStr(r.parseIdentifier()) + case set: + r.advance() + if isIdentifierTok(r.tok()) && strings.EqualFold(r.cur().lit, "ACTIVE") { + stmt.Op = ast.AlterTablespaceSetActive + } else if isIdentifierTok(r.tok()) && strings.EqualFold(r.cur().lit, "INACTIVE") { + stmt.Op = ast.AlterTablespaceSetInactive + } else { + r.syntaxError() + } + r.advance() + } + stmt.Options = r.parseTablespaceOptions() + return stmt +} + +// parseCreateLogfileGroupStmt implements CreateLogfileGroupStmt: +// +// "CREATE" "LOGFILE" "GROUP" Identifier "ADD" "UNDOFILE" stringLit +// TablespaceOptions +func (r *rdParser) parseCreateLogfileGroupStmt() ast.StmtNode { + r.expect(create) + r.expect(logfile) + r.expect(group) + stmt := &ast.CreateLogfileGroupStmt{GroupName: ast.NewCIStr(r.parseIdentifier())} + r.expect(add) + r.expect(undofile) + stmt.Undofile = r.expect(stringLit).lit + stmt.Options = r.parseTablespaceOptions() + return stmt +} + +// parseAlterLogfileGroupStmt implements AlterLogfileGroupStmt: +// +// "ALTER" "LOGFILE" "GROUP" Identifier "ADD" "UNDOFILE" stringLit +// TablespaceOptions +func (r *rdParser) parseAlterLogfileGroupStmt() ast.StmtNode { + r.expect(alter) + r.expect(logfile) + r.expect(group) + stmt := &ast.AlterLogfileGroupStmt{GroupName: ast.NewCIStr(r.parseIdentifier())} + r.expect(add) + r.expect(undofile) + stmt.Undofile = r.expect(stringLit).lit + stmt.Options = r.parseTablespaceOptions() + return stmt +} + +/* + * Spatial reference systems + */ + +// parseCreateSpatialReferenceSystemStmt implements +// CreateSpatialReferenceSystemStmt: +// +// "CREATE" OrReplace "SPATIAL" "REFERENCE" "SYSTEM" IfNotExists NUM +// SRSAttribute* +// SRSAttribute: ("NAME" | "DEFINITION" | "DESCRIPTION") stringLit +// | "ORGANIZATION" stringLit "IDENTIFIED" "BY" NUM +// +// REFERENCE and the attribute names are not keywords and are matched in +// identifier position. +func (r *rdParser) parseCreateSpatialReferenceSystemStmt() ast.StmtNode { + r.expect(create) + stmt := &ast.CreateSpatialReferenceSystemStmt{} + if r.tok() == or { + r.advance() + r.expect(replace) + stmt.OrReplace = true + } + r.expect(spatial) + r.expectIdentLit("REFERENCE") + r.expect(system) + stmt.IfNotExists = r.parseIfNotExists() + stmt.Srid = getUint64FromNUM(r.expect(intLit).item) + for isIdentifierTok(r.tok()) { + attr := &ast.SRSAttribute{} + switch strings.ToUpper(r.cur().lit) { + case "NAME": + attr.Tp = ast.SRSAttributeName + case "DEFINITION": + attr.Tp = ast.SRSAttributeDefinition + case "ORGANIZATION": + attr.Tp = ast.SRSAttributeOrganization + case "DESCRIPTION": + attr.Tp = ast.SRSAttributeDescription + default: + r.syntaxError() + } + r.advance() + attr.StrValue = r.expect(stringLit).lit + if attr.Tp == ast.SRSAttributeOrganization { + r.expect(identified) + r.expect(by) + attr.OrgID = getUint64FromNUM(r.expect(intLit).item) + } + stmt.Attributes = append(stmt.Attributes, attr) + } + return stmt +} + +/* + * Libraries + */ + +// parseCreateLibraryStmt implements CreateLibraryStmt: +// +// "CREATE" OrReplace "LIBRARY" IfNotExists TableName +// "LANGUAGE" Identifier "AS" stringLit +func (r *rdParser) parseCreateLibraryStmt() ast.StmtNode { + r.expect(create) + stmt := &ast.CreateLibraryStmt{} + if r.tok() == or { + r.advance() + r.expect(replace) + stmt.OrReplace = true + } + r.expect(library) + stmt.IfNotExists = r.parseIfNotExists() + stmt.LibraryName = r.parseTableName() + r.expect(language) + stmt.Language = ast.NewCIStr(strings.ToUpper(r.parseIdentifier())) + r.expect(as) + stmt.Code = r.expect(stringLit).lit + return stmt +} + +// parseAlterLibraryStmt implements AlterLibraryStmt: +// "ALTER" "LIBRARY" TableName "COMMENT" stringLit. +func (r *rdParser) parseAlterLibraryStmt() ast.StmtNode { + r.expect(alter) + r.expect(library) + stmt := &ast.AlterLibraryStmt{LibraryName: r.parseTableName()} + r.expect(comment) + stmt.Comment = r.expect(stringLit).lit + return stmt +} + +/* + * JSON duality views + */ + +// parseCreateJSONDualityViewStmt implements CreateJSONDualityViewStmt: +// +// "CREATE" OrReplace "JSON" "DUALITY" "VIEW" IfNotExists TableName +// "AS" CreateViewSelectOpt +// +// recording the select statement's source text like CreateViewStmt. +func (r *rdParser) parseCreateJSONDualityViewStmt() ast.StmtNode { + r.expect(create) + stmt := &ast.CreateJSONDualityViewStmt{} + if r.tok() == or { + r.advance() + r.expect(replace) + stmt.OrReplace = true + } + r.expect(jsonType) + r.expect(duality) + r.expect(view) + stmt.IfNotExists = r.parseIfNotExists() + stmt.ViewName = r.parseTableName() + r.expect(as) + startOffset := r.cur().offset + stmt.Select = r.parseCreateViewSelectOpt() + r.p.setNodeText(stmt.Select, strings.TrimSpace(r.src[startOffset:r.cur().offset])) + return stmt +} + +// parseAlterJSONDualityViewStmt implements AlterJSONDualityViewStmt: +// "ALTER" "JSON" "DUALITY" "VIEW" TableName "AS" CreateViewSelectOpt. +func (r *rdParser) parseAlterJSONDualityViewStmt() ast.StmtNode { + r.expect(alter) + r.expect(jsonType) + r.expect(duality) + r.expect(view) + stmt := &ast.AlterJSONDualityViewStmt{ViewName: r.parseTableName()} + r.expect(as) + startOffset := r.cur().offset + stmt.Select = r.parseCreateViewSelectOpt() + r.p.setNodeText(stmt.Select, strings.TrimSpace(r.src[startOffset:r.cur().offset])) + return stmt +} diff --git a/parser/parse_procedure.go b/parser/parse_procedure.go index 9ae1676..79437ef 100644 --- a/parser/parse_procedure.go +++ b/parser/parse_procedure.go @@ -73,14 +73,15 @@ func (r *rdParser) parseProcedureCall() *ast.FuncCallExpr { // parseCreateProcedureStmt implements: // -// CreateProcedureStmt: "CREATE" "PROCEDURE" IfNotExists TableName '(' -// OptSpPdparams ')' ProcedureProcStmt +// CreateProcedureStmt: "CREATE" DefinerOpt "PROCEDURE" IfNotExists +// TableName '(' OptSpPdparams ')' ProcedureProcStmt // // The action records the body statement's source text — from the body's // first token through the reduce-time lookahead (parser.yylval.offset) — // and the parameter list's source text between the parentheses. func (r *rdParser) parseCreateProcedureStmt() ast.StmtNode { r.expect(create) + definerV := r.parseDefinerOpt() r.expect(procedure) ifNotExists := r.parseIfNotExists() procName := r.parseTableName() @@ -94,6 +95,7 @@ func (r *rdParser) parseCreateProcedureStmt() ast.StmtNode { ProcedureName: procName, ProcedureParam: params, ProcedureBody: body, + Definer: definerV, } r.p.setNodeText(body, strings.TrimSpace(r.src[bodyStart:r.cur().offset])) startOffset := lparen.offset @@ -192,6 +194,11 @@ func (r *rdParser) parseProcedureProcStmt() ast.StmtNode { // ProcedureLeave: "LEAVE" identifier r.advance() return &ast.ProcedureJump{Name: r.expect(identifier).lit, IsLeave: true} + case returnKwd: + // ReturnStmt: "RETURN" Expression — the routine_body form of + // stored functions; MySQL rejects it elsewhere at a later stage. + r.advance() + return &ast.ReturnStmt{Expr: r.parseExpression()} case identifier: if r.la(1) == int(':') { return r.parseProcedureLabeled() diff --git a/parser/parse_replication.go b/parser/parse_replication.go index d13ea97..fcbb465 100644 --- a/parser/parse_replication.go +++ b/parser/parse_replication.go @@ -13,6 +13,22 @@ package parser +// The MySQL replication statements (MySQL 26.7 §15.4): CHANGE +// REPLICATION SOURCE TO, CHANGE REPLICATION FILTER, PURGE BINARY LOGS, +// RESET BINARY LOGS AND GTIDS, RESET REPLICA, START/STOP REPLICA, and +// START/STOP GROUP_REPLICATION. All of these postdate the goyacc +// grammar; the productions are written from the MySQL 26.7 reference +// manual in the same style as parser.y. The removed pre-8.4 spellings +// (CHANGE MASTER TO, PURGE MASTER LOGS, RESET MASTER, RESET/START/STOP +// SLAVE) are not parsed. +// +// This file owns the START, STOP, and PURGE statement heads and routes +// their non-replication alternatives onward: START to +// BeginTransactionStmt (parse_txn.go), STOP and PURGE to the BRIE +// statement family (parse_brie.go). The RESET head stays with +// parse_mysql_admin.go, which routes RESET REPLICA and RESET BINARY +// LOGS AND GTIDS here. + import ( "strings" @@ -20,20 +36,68 @@ import ( ) func init() { - rdRegister(change, (*rdParser).parseChangeReplicationSourceStmt) + rdRegister(change, (*rdParser).parseChangeStmtFamily) + rdRegister(start, (*rdParser).parseStartStmtFamily) + rdRegister(stop, (*rdParser).parseStopStmtFamily) + rdRegister(purge, (*rdParser).parsePurgeStmtFamily) +} + +// parseChangeStmtFamily dispatches the CHANGE statement family: +// ChangeReplicationSourceStmt | ChangeReplicationFilterStmt. +func (r *rdParser) parseChangeStmtFamily() ast.StmtNode { + if r.la(2) == filter { + return r.parseChangeReplicationFilterStmt() + } + return r.parseChangeReplicationSourceStmt() +} + +// parseStartStmtFamily dispatches the START statement family on the +// token after "START": StartReplicaStmt, StartGroupReplicationStmt, or +// the "START TRANSACTION" alternative of BeginTransactionStmt. +func (r *rdParser) parseStartStmtFamily() ast.StmtNode { + switch r.la(1) { + case replica: + return r.parseStartReplicaStmt() + case groupReplication: + return r.parseStartGroupReplicationStmt() + } + return r.parseBeginTransactionStmt() +} + +// parseStopStmtFamily dispatches the STOP statement family on the token +// after "STOP": StopReplicaStmt, StopGroupReplicationStmt, or the +// "STOP BACKUP LOGS" BRIEStmt. +func (r *rdParser) parseStopStmtFamily() ast.StmtNode { + switch r.la(1) { + case replica: + return r.parseStopReplicaStmt() + case groupReplication: + // StopGroupReplicationStmt: "STOP" "GROUP_REPLICATION" + r.advance() + r.advance() + return &ast.StopGroupReplicationStmt{} + } + return r.parseStopBackupStmt() } -// parseChangeReplicationSourceStmt implements ChangeReplicationSourceStmt. -// The statement postdates the goyacc grammar (parser.y has no production -// for it); the shape follows the MySQL 26.7 reference manual: +// parsePurgeStmtFamily dispatches the PURGE statement family on the +// token after "PURGE": PurgeBinaryLogsStmt or the "PURGE BACKUP LOGS" +// BRIEStmt. +func (r *rdParser) parsePurgeStmtFamily() ast.StmtNode { + if r.la(1) == binaryType { + return r.parsePurgeBinaryLogsStmt() + } + return r.parsePurgeBackupStmt() +} + +// parseChangeReplicationSourceStmt implements ChangeReplicationSourceStmt: // // "CHANGE" "REPLICATION" "SOURCE" "TO" ReplicationSourceOption -// ("," ReplicationSourceOption)* ("FOR" "CHANNEL" stringLit)? +// ("," ReplicationSourceOption)* ForChannelOpt // // Option names are not validated against the server's option list, so the // MySQL 26.7 Change Stream Applier options (APPLIER_VERSION, // APPLIER_WORKER_COUNT, APPLIER_EVENT_MEMORY_LIMIT) parse like any other. -// The removed CHANGE MASTER TO spelling is not parsed, matching MySQL 8.4+. func (r *rdParser) parseChangeReplicationSourceStmt() ast.StmtNode { r.expect(change) r.expect(replication) @@ -45,10 +109,7 @@ func (r *rdParser) parseChangeReplicationSourceStmt() ast.StmtNode { for r.accept(int(',')) { stmt.Options = append(stmt.Options, r.parseReplicationSourceOption()) } - if r.accept(forKwd) { - r.expect(channel) - stmt.Channel = r.expect(stringLit).lit - } + stmt.Channel = r.parseForChannelOpt() return stmt } @@ -57,6 +118,12 @@ func (r *rdParser) parseChangeReplicationSourceStmt() ast.StmtNode { func (r *rdParser) parseReplicationSourceOption() *ast.ReplicationSourceOption { name := strings.ToUpper(r.parseIdentifier()) r.expect(eq) + return &ast.ReplicationSourceOption{Name: name, Value: r.parseReplicationOptionValue()} +} + +// parseReplicationOptionValue implements the literal value of a +// replication option: stringLit | intLit | decLit | floatLit. +func (r *rdParser) parseReplicationOptionValue() ast.ValueExpr { var value ast.ValueExpr switch r.tok() { case stringLit: @@ -68,5 +135,290 @@ func (r *rdParser) parseReplicationSourceOption() *ast.ReplicationSourceOption { default: r.syntaxError() } - return &ast.ReplicationSourceOption{Name: name, Value: value} + return value +} + +// parseForChannelOpt implements ForChannelOpt: +// empty | "FOR" "CHANNEL" stringLit. It returns "" for the empty +// alternative. +func (r *rdParser) parseForChannelOpt() string { + if !r.accept(forKwd) { + return "" + } + r.expect(channel) + return r.expect(stringLit).lit +} + +// parseChangeReplicationFilterStmt implements ChangeReplicationFilterStmt: +// +// "CHANGE" "REPLICATION" "FILTER" ReplicationFilter +// ("," ReplicationFilter)* ForChannelOpt +func (r *rdParser) parseChangeReplicationFilterStmt() ast.StmtNode { + r.expect(change) + r.expect(replication) + r.expect(filter) + stmt := &ast.ChangeReplicationFilterStmt{ + Filters: []*ast.ReplicationFilter{r.parseReplicationFilter()}, + } + for r.accept(int(',')) { + stmt.Filters = append(stmt.Filters, r.parseReplicationFilter()) + } + stmt.Channel = r.parseForChannelOpt() + return stmt +} + +// parseReplicationFilter implements ReplicationFilter. The filter names +// are not keywords (non-reserved in MySQL) and are matched +// case-insensitively in identifier position; the name selects the value +// form: +// +// {"REPLICATE_DO_DB" | "REPLICATE_IGNORE_DB"} eq '(' DBNameListOpt ')' +// | {"REPLICATE_DO_TABLE" | "REPLICATE_IGNORE_TABLE"} eq +// '(' TableNameListOpt ')' +// | {"REPLICATE_WILD_DO_TABLE" | "REPLICATE_WILD_IGNORE_TABLE"} eq +// '(' StringListOpt ')' +// | "REPLICATE_REWRITE_DB" eq '(' DBPairListOpt ')' +// +// An empty '(' ')' value clears the filter. +func (r *rdParser) parseReplicationFilter() *ast.ReplicationFilter { + if !isIdentifierTok(r.tok()) { + r.syntaxError() + } + var tp ast.ReplicationFilterType + switch strings.ToUpper(r.cur().lit) { + case "REPLICATE_DO_DB": + tp = ast.ReplicationFilterDoDB + case "REPLICATE_IGNORE_DB": + tp = ast.ReplicationFilterIgnoreDB + case "REPLICATE_DO_TABLE": + tp = ast.ReplicationFilterDoTable + case "REPLICATE_IGNORE_TABLE": + tp = ast.ReplicationFilterIgnoreTable + case "REPLICATE_WILD_DO_TABLE": + tp = ast.ReplicationFilterWildDoTable + case "REPLICATE_WILD_IGNORE_TABLE": + tp = ast.ReplicationFilterWildIgnoreTable + case "REPLICATE_REWRITE_DB": + tp = ast.ReplicationFilterRewriteDB + default: + r.syntaxError() + } + r.advance() + r.expect(eq) + r.expect(int('(')) + f := &ast.ReplicationFilter{Tp: tp} + if r.tok() != int(')') { + switch tp { + case ast.ReplicationFilterDoDB, ast.ReplicationFilterIgnoreDB: + f.DBNames = []ast.CIStr{ast.NewCIStr(r.parseIdentifier())} + for r.accept(int(',')) { + f.DBNames = append(f.DBNames, ast.NewCIStr(r.parseIdentifier())) + } + case ast.ReplicationFilterDoTable, ast.ReplicationFilterIgnoreTable: + f.Tables = []*ast.TableName{r.parseTableName()} + for r.accept(int(',')) { + f.Tables = append(f.Tables, r.parseTableName()) + } + case ast.ReplicationFilterWildDoTable, ast.ReplicationFilterWildIgnoreTable: + f.Patterns = []string{r.expect(stringLit).lit} + for r.accept(int(',')) { + f.Patterns = append(f.Patterns, r.expect(stringLit).lit) + } + case ast.ReplicationFilterRewriteDB: + f.Rewrites = []*ast.ReplicationRewriteDB{r.parseReplicationRewriteDB()} + for r.accept(int(',')) { + f.Rewrites = append(f.Rewrites, r.parseReplicationRewriteDB()) + } + } + } + r.expect(int(')')) + return f +} + +// parseReplicationRewriteDB implements the (from_db, to_db) pair of a +// REPLICATE_REWRITE_DB filter. +func (r *rdParser) parseReplicationRewriteDB() *ast.ReplicationRewriteDB { + r.expect(int('(')) + pair := &ast.ReplicationRewriteDB{FromDB: ast.NewCIStr(r.parseIdentifier())} + r.expect(int(',')) + pair.ToDB = ast.NewCIStr(r.parseIdentifier()) + r.expect(int(')')) + return pair +} + +// parsePurgeBinaryLogsStmt implements PurgeBinaryLogsStmt: +// "PURGE" "BINARY" "LOGS" ("TO" stringLit | "BEFORE" Expression). +func (r *rdParser) parsePurgeBinaryLogsStmt() ast.StmtNode { + r.expect(purge) + r.expect(binaryType) + r.expect(logs) + switch r.tok() { + case to: + r.advance() + return &ast.PurgeBinaryLogsStmt{To: r.expect(stringLit).lit} + case before: + r.advance() + return &ast.PurgeBinaryLogsStmt{Before: r.parseExpression()} + } + r.syntaxError() + return nil +} + +// parseResetReplicaStmt implements ResetReplicaStmt: +// "RESET" "REPLICA" ["ALL"] ForChannelOpt. +func (r *rdParser) parseResetReplicaStmt() ast.StmtNode { + r.expect(reset) + r.expect(replica) + stmt := &ast.ResetReplicaStmt{All: r.accept(all)} + stmt.Channel = r.parseForChannelOpt() + return stmt +} + +// parseResetBinaryLogsAndGtidsStmt implements +// ResetBinaryLogsAndGtidsStmt: "RESET" "BINARY" "LOGS" "AND" "GTIDS". +func (r *rdParser) parseResetBinaryLogsAndGtidsStmt() ast.StmtNode { + r.expect(reset) + r.expect(binaryType) + r.expect(logs) + r.expect(and) + r.expect(gtids) + return &ast.ResetBinaryLogsAndGtidsStmt{} +} + +// parseReplicaThreadTypes implements the thread_types list of +// START/STOP REPLICA: empty | ReplicaThreadType (',' ReplicaThreadType)* +// with ReplicaThreadType: "IO_THREAD" | "SQL_THREAD". +func (r *rdParser) parseReplicaThreadTypes() []ast.ReplicaThreadType { + if r.tok() != ioThread && r.tok() != sqlThread { + return nil + } + types := []ast.ReplicaThreadType{r.parseReplicaThreadType()} + for r.accept(int(',')) { + types = append(types, r.parseReplicaThreadType()) + } + return types +} + +func (r *rdParser) parseReplicaThreadType() ast.ReplicaThreadType { + switch r.tok() { + case ioThread: + r.advance() + return ast.ReplicaThreadIO + case sqlThread: + r.advance() + return ast.ReplicaThreadSQL + } + r.syntaxError() + return ast.ReplicaThreadIO +} + +// parseStartReplicaStmt implements StartReplicaStmt: +// +// "START" "REPLICA" ReplicaThreadTypes ["UNTIL" ReplicaUntilOption +// ("," ReplicaUntilOption)*] ReplicaConnectionOptions ForChannelOpt +// +// A ReplicaUntilOption is a name eq value pair (SQL_BEFORE_GTIDS, +// SOURCE_LOG_FILE, RELAY_LOG_POS, ...) or the bare SQL_AFTER_MTS_GAPS; +// like the CHANGE REPLICATION SOURCE TO options the names are matched +// in identifier position and not validated against the server's list. +func (r *rdParser) parseStartReplicaStmt() ast.StmtNode { + r.expect(start) + r.expect(replica) + stmt := &ast.StartReplicaStmt{ThreadTypes: r.parseReplicaThreadTypes()} + if r.accept(until) { + stmt.Until = []*ast.ReplicationSourceOption{r.parseReplicaUntilOption()} + for r.accept(int(',')) { + stmt.Until = append(stmt.Until, r.parseReplicaUntilOption()) + } + } + stmt.ConnectionOptions = r.parseReplicaConnectionOptions() + stmt.Channel = r.parseForChannelOpt() + return stmt +} + +// parseReplicaUntilOption implements ReplicaUntilOption: +// Identifier [eq (stringLit | intLit | decLit | floatLit)]. +func (r *rdParser) parseReplicaUntilOption() *ast.ReplicationSourceOption { + name := strings.ToUpper(r.parseIdentifier()) + opt := &ast.ReplicationSourceOption{Name: name} + if r.accept(eq) { + opt.Value = r.parseReplicationOptionValue() + } + return opt +} + +// parseReplicaConnectionOptions implements the space-separated +// connection options of START REPLICA: ("USER" | "PASSWORD" | +// "DEFAULT_AUTH" | "PLUGIN_DIR") eq stringLit, in any order. +// DEFAULT_AUTH and PLUGIN_DIR are matched in identifier position. +func (r *rdParser) parseReplicaConnectionOptions() []*ast.ReplicationSourceOption { + var opts []*ast.ReplicationSourceOption + for { + var name string + switch { + case r.tok() == user || r.tok() == password: + name = strings.ToUpper(r.cur().lit) + case isIdentifierTok(r.tok()): + name = strings.ToUpper(r.cur().lit) + if name != "DEFAULT_AUTH" && name != "PLUGIN_DIR" { + return opts + } + default: + return opts + } + r.advance() + r.expect(eq) + opts = append(opts, &ast.ReplicationSourceOption{ + Name: name, + Value: ast.NewValueExpr(r.expect(stringLit).lit, "", ""), + }) + } +} + +// parseStopReplicaStmt implements StopReplicaStmt: +// "STOP" "REPLICA" ReplicaThreadTypes ForChannelOpt. +func (r *rdParser) parseStopReplicaStmt() ast.StmtNode { + r.expect(stop) + r.expect(replica) + stmt := &ast.StopReplicaStmt{ThreadTypes: r.parseReplicaThreadTypes()} + stmt.Channel = r.parseForChannelOpt() + return stmt +} + +// parseStartGroupReplicationStmt implements StartGroupReplicationStmt: +// +// "START" "GROUP_REPLICATION" [GroupReplicationOption +// ("," GroupReplicationOption)*] +// +// with GroupReplicationOption: ("USER" | "PASSWORD" | "DEFAULT_AUTH") +// eq stringLit; DEFAULT_AUTH is matched in identifier position. +func (r *rdParser) parseStartGroupReplicationStmt() ast.StmtNode { + r.expect(start) + r.expect(groupReplication) + stmt := &ast.StartGroupReplicationStmt{} + if r.tok() != user && r.tok() != password && + !(isIdentifierTok(r.tok()) && strings.EqualFold(r.cur().lit, "DEFAULT_AUTH")) { + return stmt + } + stmt.Options = []*ast.ReplicationSourceOption{r.parseGroupReplicationOption()} + for r.accept(int(',')) { + stmt.Options = append(stmt.Options, r.parseGroupReplicationOption()) + } + return stmt +} + +func (r *rdParser) parseGroupReplicationOption() *ast.ReplicationSourceOption { + switch { + case r.tok() == user || r.tok() == password: + case isIdentifierTok(r.tok()) && strings.EqualFold(r.cur().lit, "DEFAULT_AUTH"): + default: + r.syntaxError() + } + name := strings.ToUpper(r.cur().lit) + r.advance() + r.expect(eq) + return &ast.ReplicationSourceOption{ + Name: name, + Value: ast.NewValueExpr(r.expect(stringLit).lit, "", ""), + } } diff --git a/parser/parse_select.go b/parser/parse_select.go index 636014a..8fdb3a4 100644 --- a/parser/parse_select.go +++ b/parser/parse_select.go @@ -256,16 +256,28 @@ func (r *rdParser) parseSelectStmt() *ast.SelectStmt { switch r.tok() { case selectKwd: st := r.parseSelectStmtBasic() + // MySQL also accepts the SelectStmtIntoOption between the field + // list and FROM (the trailing position is handled by + // parseSelectTail, which skips it once one is set). The INTO + // token bounds the last field's recorded text in that case. + fieldEnd := rdToken{offset: -1} + if r.tok() == into && st.Having == nil { + fieldEnd = *r.cur() + st.SelectIntoOpt = r.parseSelectStmtIntoOption() + } switch { case r.tok() == from && st.Having == nil && r.la(1) == dual: // SelectStmtFromDualTable: SelectStmtBasic FromDual // WhereClauseOptional, then SelectStmtGroup OrderByOptional ... fromTok := *r.cur() + if fieldEnd.offset < 0 { + fieldEnd = fromTok + } r.advance() r.expect(dual) lastField := st.Fields.Fields[len(st.Fields.Fields)-1] if lastField.Expr != nil && lastField.AsName.O == "" { - r.p.setNodeText(lastField, r.src[lastField.Offset:fromTok.offset-1]) + r.p.setNodeText(lastField, r.src[lastField.Offset:fieldEnd.offset-1]) } if r.accept(where) { st.Where = r.parseExpression() @@ -279,11 +291,14 @@ func (r *rdParser) parseSelectStmt() *ast.SelectStmt { // WhereClauseOptional SelectStmtGroup HavingClause // WindowClauseOptional, then OrderByOptional ... fromTok := *r.cur() + if fieldEnd.offset < 0 { + fieldEnd = fromTok + } r.advance() st.From = &ast.TableRefsClause{TableRefs: r.parseTableRefs()} lastField := st.Fields.Fields[len(st.Fields.Fields)-1] if lastField.Expr != nil && lastField.AsName.O == "" { - r.p.setNodeText(lastField, r.src[lastField.Offset:r.endOffsetAt(fromTok.offset)]) + r.p.setNodeText(lastField, r.src[lastField.Offset:r.endOffsetAt(fieldEnd.offset)]) } if r.accept(where) { st.Where = r.parseExpression() @@ -351,8 +366,10 @@ func (r *rdParser) parseSelectTail(st *ast.SelectStmt) { if lock := r.parseSelectLockOpt(); lock != nil { st.LockInfo = lock } - if opt := r.parseSelectStmtIntoOption(); opt != nil { - st.SelectIntoOpt = opt + if st.SelectIntoOpt == nil { + if opt := r.parseSelectStmtIntoOption(); opt != nil { + st.SelectIntoOpt = opt + } } } @@ -670,23 +687,55 @@ func (r *rdParser) parseSelectLockOpt() *ast.SelectLockInfo { return nil } -// parseSelectStmtIntoOption implements SelectStmtIntoOption. +// parseSelectStmtIntoOption implements SelectStmtIntoOption: +// +// "INTO" "OUTFILE" stringLit Fields Lines +// | "INTO" "DUMPFILE" stringLit +// | "INTO" IntoVar (',' IntoVar)* +// +// with IntoVar: UserVariable | Identifier (a stored program variable, +// only meaningful inside a routine body). func (r *rdParser) parseSelectStmtIntoOption() *ast.SelectIntoOption { if r.tok() != into { return nil } r.advance() - r.expect(outfile) - x := &ast.SelectIntoOption{Tp: ast.SelectIntoOutfile, FileName: r.expect(stringLit).lit} - if fields := r.parseFieldsClause(); fields != nil { - x.FieldsInfo = fields + switch r.tok() { + case outfile: + r.advance() + x := &ast.SelectIntoOption{Tp: ast.SelectIntoOutfile, FileName: r.expect(stringLit).lit} + if fields := r.parseFieldsClause(); fields != nil { + x.FieldsInfo = fields + } + if lines := r.parseLinesClause(); lines != nil { + x.LinesInfo = lines + } + return x + case dumpfile: + r.advance() + return &ast.SelectIntoOption{Tp: ast.SelectIntoDumpfile, FileName: r.expect(stringLit).lit} } - if lines := r.parseLinesClause(); lines != nil { - x.LinesInfo = lines + x := &ast.SelectIntoOption{Tp: ast.SelectIntoVars, Vars: []ast.ExprNode{r.parseSelectIntoVar()}} + for r.accept(int(',')) { + x.Vars = append(x.Vars, r.parseSelectIntoVar()) } return x } +// parseSelectIntoVar implements IntoVar. The variable expressions carry +// no origin position: SelectStmt.Accept does not traverse +// SelectIntoOpt, so a recorded position could never be normalized by +// visitors. +func (r *rdParser) parseSelectIntoVar() ast.ExprNode { + if r.tok() == singleAtIdentifier { + t := r.expect(singleAtIdentifier) + return &ast.VariableExpr{Name: strings.TrimPrefix(t.lit, "@")} + } + return &ast.ColumnNameExpr{ + Name: &ast.ColumnName{Name: ast.NewCIStr(r.parseIdentifier())}, + } +} + // parseFieldsClause implements Fields: FieldsOrColumns FieldItemList. func (r *rdParser) parseFieldsClause() *ast.FieldsClause { if r.tok() != fields && r.tok() != columns { diff --git a/parser/parse_txn.go b/parser/parse_txn.go index 6a152e3..355eb31 100644 --- a/parser/parse_txn.go +++ b/parser/parse_txn.go @@ -21,8 +21,9 @@ import ( ) func init() { + // The start token is owned by parse_replication.go's + // parseStartStmtFamily, which routes "START TRANSACTION" here. rdRegister(begin, (*rdParser).parseBeginTransactionStmt) - rdRegister(start, (*rdParser).parseBeginTransactionStmt) rdRegister(commit, (*rdParser).parseCommitStmt) rdRegister(rollback, (*rdParser).parseRollbackStmt) rdRegister(savepoint, (*rdParser).parseSavepointStmt) diff --git a/parser/parse_xa.go b/parser/parse_xa.go new file mode 100644 index 0000000..63053bd --- /dev/null +++ b/parser/parse_xa.go @@ -0,0 +1,107 @@ +// Copyright 2026 The sqlc Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +// The MySQL XA transaction statements (MySQL 26.7 §15.3.8). These +// postdate the goyacc grammar; the productions below are written from +// the MySQL 26.7 reference manual in the same style as parser.y. (The +// LOCK INSTANCE FOR BACKUP / UNLOCK INSTANCE statements of §15.3.5 are +// alternatives of the LOCK/UNLOCK statement families in parse_misc.go.) + +import ( + "github.com/sqlc-dev/marino/ast" +) + +func init() { + rdRegister(xa, (*rdParser).parseXAStmt) +} + +// parseXAStmt implements XAStmt: +// +// "XA" ("START" | "BEGIN") XID ["JOIN" | "RESUME"] +// | "XA" "END" XID ["SUSPEND" ["FOR" "MIGRATE"]] +// | "XA" "PREPARE" XID +// | "XA" "COMMIT" XID ["ONE" "PHASE"] +// | "XA" "ROLLBACK" XID +// | "XA" "RECOVER" ["CONVERT" "XID"] +// +// The XA BEGIN spelling parses to XAOpStart, so Restore() canonicalizes +// it to XA START. +func (r *rdParser) parseXAStmt() ast.StmtNode { + r.expect(xa) + switch r.tok() { + case start, begin: + r.advance() + stmt := &ast.XAStmt{Op: ast.XAOpStart, Xid: r.parseXID()} + switch r.tok() { + case join: + r.advance() + stmt.StartOption = ast.XAStartJoin + case resume: + r.advance() + stmt.StartOption = ast.XAStartResume + } + return stmt + case end: + r.advance() + stmt := &ast.XAStmt{Op: ast.XAOpEnd, Xid: r.parseXID()} + if r.accept(suspend) { + stmt.Suspend = true + if r.accept(forKwd) { + r.expect(migrate) + stmt.ForMigrate = true + } + } + return stmt + case prepare: + r.advance() + return &ast.XAStmt{Op: ast.XAOpPrepare, Xid: r.parseXID()} + case commit: + r.advance() + stmt := &ast.XAStmt{Op: ast.XAOpCommit, Xid: r.parseXID()} + if r.accept(one) { + r.expect(phase) + stmt.OnePhase = true + } + return stmt + case rollback: + r.advance() + return &ast.XAStmt{Op: ast.XAOpRollback, Xid: r.parseXID()} + case recover: + r.advance() + stmt := &ast.XAStmt{Op: ast.XAOpRecover} + if r.accept(convert) { + r.expect(xid) + stmt.ConvertXID = true + } + return stmt + } + r.syntaxError() + return nil +} + +// parseXID implements the xid production: +// stringLit [',' stringLit [',' NUM]]. +func (r *rdParser) parseXID() *ast.XID { + x := &ast.XID{Gtrid: r.expect(stringLit).lit} + if r.accept(int(',')) { + x.Bqual = r.expect(stringLit).lit + x.HasBqual = true + if r.accept(int(',')) { + x.FormatID = getUint64FromNUM(r.expect(intLit).item) + x.HasFormatID = true + } + } + return x +} diff --git a/parser/rd_parser.go b/parser/rd_parser.go index a983e1a..71742db 100644 --- a/parser/rd_parser.go +++ b/parser/rd_parser.go @@ -383,14 +383,20 @@ func (r *rdParser) parseStatement() ast.StmtNode { return r.parseLoadStatsStmt() case index: return r.parseLoadIndexStmt() + case xml: + return r.parseLoadXMLStmt() } r.unsupported("LOAD statement") return nil case importKwd: - if r.la(1) != into { - r.unsupported("IMPORT statement") + switch r.la(1) { + case into: + return r.parseImportIntoStmt() + case tableKwd: + return r.parseImportTableStmt() } - return r.parseImportIntoStmt() + r.unsupported("IMPORT statement") + return nil case batch: return r.parseNonTransactionalDMLStmt() default: diff --git a/parser/testdata/parser/mysql_ddl/input.sql b/parser/testdata/parser/mysql_ddl/input.sql new file mode 100644 index 0000000..123769b --- /dev/null +++ b/parser/testdata/parser/mysql_ddl/input.sql @@ -0,0 +1,147 @@ +CREATE EVENT e ON SCHEDULE AT CURRENT_TIMESTAMP DO SELECT 1 +-- case +CREATE EVENT IF NOT EXISTS db1.e ON SCHEDULE AT '2026-09-01 00:00:00' + INTERVAL 1 DAY DO SELECT 1 +-- case +CREATE EVENT e ON SCHEDULE EVERY 2 HOUR STARTS CURRENT_TIMESTAMP ENDS CURRENT_TIMESTAMP + INTERVAL 1 WEEK ON COMPLETION PRESERVE DISABLE COMMENT 'cleanup' DO SELECT 1 +-- case +CREATE DEFINER = 'admin'@'localhost' EVENT e ON SCHEDULE EVERY 1 DAY ON COMPLETION NOT PRESERVE DISABLE ON REPLICA DO SELECT 1 +-- case +ALTER EVENT myevent ON SCHEDULE EVERY 2 HOUR +-- case +ALTER EVENT myevent ON SCHEDULE AT CURRENT_TIMESTAMP ON COMPLETION PRESERVE RENAME TO yourevent ENABLE COMMENT 'new comment' DO SELECT 1 +-- case +ALTER DEFINER = CURRENT_USER EVENT e COMMENT 'x' +-- case +DROP EVENT e +-- case +DROP EVENT IF EXISTS db1.e +-- case +CREATE TRIGGER trg BEFORE INSERT ON t FOR EACH ROW SET @x = 1 +-- case +CREATE TRIGGER IF NOT EXISTS trg AFTER UPDATE ON t FOR EACH ROW SET @x = 1 +-- case +CREATE TRIGGER trg AFTER DELETE ON t FOR EACH ROW FOLLOWS other_trg SET @x = 1 +-- case +CREATE DEFINER = 'admin'@'localhost' TRIGGER trg BEFORE UPDATE ON t FOR EACH ROW PRECEDES other_trg SET @x = 1 +-- case +CREATE TRIGGER `trg` BEFORE INSERT ON `t` FOR EACH ROW BEGIN SET @`x`=1;SET @`y`=2; END +-- case +DROP TRIGGER trg +-- case +DROP TRIGGER IF EXISTS db1.trg +-- case +CREATE FUNCTION f(x INT) RETURNS INT DETERMINISTIC RETURN x + 1 +-- case +CREATE FUNCTION f() RETURNS VARCHAR(64) COMMENT 'greets' LANGUAGE SQL NOT DETERMINISTIC READS SQL DATA SQL SECURITY INVOKER RETURN 'hi' +-- case +CREATE DEFINER = `admin`@`localhost` FUNCTION `db1`.`f`(`x` INT, `y` CHAR(4)) RETURNS INT CONTAINS SQL SQL SECURITY DEFINER BEGIN RETURN `x`; END +-- case +CREATE FUNCTION f() RETURNS INT MODIFIES SQL DATA RETURN 1 +-- case +CREATE FUNCTION f() RETURNS INT NO SQL RETURN 1 +-- case +ALTER FUNCTION myfunc COMMENT 'some comment' +-- case +ALTER FUNCTION myfunc LANGUAGE SQL READS SQL DATA SQL SECURITY INVOKER +-- case +DROP FUNCTION f +-- case +DROP FUNCTION IF EXISTS db1.f +-- case +ALTER PROCEDURE myproc COMMENT 'some comment' +-- case +ALTER PROCEDURE myproc CONTAINS SQL SQL SECURITY DEFINER +-- case +CREATE DEFINER = 'admin'@'localhost' PROCEDURE p() SELECT 1 +-- case +ALTER VIEW v AS SELECT 1 +-- case +ALTER ALGORITHM = MERGE DEFINER = 'admin'@'localhost' SQL SECURITY INVOKER VIEW v (a, b) AS SELECT 1, 2 WITH LOCAL CHECK OPTION +-- case +ALTER INSTANCE ROTATE INNODB MASTER KEY +-- case +ALTER INSTANCE ROTATE BINLOG MASTER KEY +-- case +ALTER INSTANCE RELOAD TLS +-- case +ALTER INSTANCE RELOAD TLS FOR CHANNEL mysql_admin NO ROLLBACK ON ERROR +-- case +ALTER INSTANCE RELOAD KEYRING +-- case +ALTER INSTANCE ENABLE INNODB REDO_LOG +-- case +ALTER INSTANCE DISABLE INNODB REDO_LOG +-- case +CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (USER 'user', HOST 'host', DATABASE 'db') +-- case +CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (HOST '127.0.0.1', PORT 3306, SOCKET '/tmp/mysql.sock', OWNER 'root', PASSWORD 'secret') +-- case +ALTER SERVER s OPTIONS (USER 'user') +-- case +DROP SERVER s +-- case +DROP SERVER IF EXISTS s +-- case +CREATE TABLESPACE ts ADD DATAFILE 'file.ibd' ENGINE = INNODB +-- case +CREATE TABLESPACE ts ADD DATAFILE 'file.dat' USE LOGFILE GROUP lg1 INITIAL_SIZE = 134217728 EXTENT_SIZE = 4194304 MAX_SIZE = 1073741824 AUTOEXTEND_SIZE = 67108864 NODEGROUP = 1 WAIT COMMENT = 'ndb tablespace' ENGINE = NDB +-- case +CREATE UNDO TABLESPACE undo_ts ADD DATAFILE 'undo.ibu' FILE_BLOCK_SIZE = 8192 ENCRYPTION = 'Y' ENGINE_ATTRIBUTE = '{}' +-- case +ALTER TABLESPACE ts ADD DATAFILE 'file.ibd' ENGINE = NDB +-- case +ALTER TABLESPACE ts DROP DATAFILE 'file.ibd' WAIT ENGINE = NDB +-- case +ALTER TABLESPACE ts RENAME TO ts2 +-- case +ALTER UNDO TABLESPACE undo_ts SET INACTIVE +-- case +ALTER UNDO TABLESPACE undo_ts SET ACTIVE +-- case +DROP TABLESPACE ts ENGINE = INNODB +-- case +DROP UNDO TABLESPACE undo_ts +-- case +CREATE LOGFILE GROUP lg1 ADD UNDOFILE 'undo.dat' ENGINE = NDB +-- case +CREATE LOGFILE GROUP lg1 ADD UNDOFILE 'undo.dat' INITIAL_SIZE = 33554432 UNDO_BUFFER_SIZE = 8388608 REDO_BUFFER_SIZE = 8388608 NODEGROUP = 1 WAIT COMMENT = 'ndb logs' ENGINE = NDB +-- case +ALTER LOGFILE GROUP lg1 ADD UNDOFILE 'undo.dat' ENGINE = NDB +-- case +ALTER LOGFILE GROUP lg1 ADD UNDOFILE 'undo2.dat' INITIAL_SIZE = 33554432 WAIT ENGINE = NDB +-- case +DROP LOGFILE GROUP lg1 ENGINE = NDB +-- case +CREATE SPATIAL REFERENCE SYSTEM 5000 NAME 'my srs' DEFINITION 'GEOGCS[]' +-- case +CREATE OR REPLACE SPATIAL REFERENCE SYSTEM 5000 NAME 'my srs' ORGANIZATION 'EPSG' IDENTIFIED BY 4326 DEFINITION 'GEOGCS[]' DESCRIPTION 'a geographic srs' +-- case +CREATE SPATIAL REFERENCE SYSTEM IF NOT EXISTS 5000 NAME 'my srs' DEFINITION 'GEOGCS[]' +-- case +DROP SPATIAL REFERENCE SYSTEM 5000 +-- case +DROP SPATIAL REFERENCE SYSTEM IF EXISTS 5000 +-- case +CREATE LIBRARY mylib LANGUAGE JAVASCRIPT AS 'export function f() { return 1 }' +-- case +CREATE OR REPLACE LIBRARY IF NOT EXISTS db1.mylib LANGUAGE JAVASCRIPT AS 'export function f() { return 1 }' +-- case +ALTER LIBRARY mylib COMMENT 'updated' +-- case +DROP LIBRARY mylib +-- case +DROP LIBRARY IF EXISTS db1.mylib +-- case +CREATE JSON DUALITY VIEW jdv AS SELECT JSON_DUALITY_OBJECT('id' : t.id) FROM t +-- case +CREATE OR REPLACE JSON DUALITY VIEW jdv AS SELECT JSON_DUALITY_OBJECT('id' : t.id, 'name' : t.name, 'child' : JSON_DUALITY_OBJECT('cid' : c.id)) FROM t +-- case +ALTER JSON DUALITY VIEW jdv AS SELECT JSON_DUALITY_OBJECT('id' : t.id) FROM t +-- case +CREATE MASKING POLICY p ON t (c) USING (mask_inner(c, 1, 1)) +-- case +DROP MASKING POLICY p +-- case +DROP MASKING POLICY IF EXISTS p +-- case +SELECT `at`, `every`, `starts`, `ends`, `server`, `options`, `wrapper`, `contains`, `duality`, `rotate`, `innodb` FROM t diff --git a/parser/testdata/parser/mysql_ddl/output.sql b/parser/testdata/parser/mysql_ddl/output.sql new file mode 100644 index 0000000..ab38616 --- /dev/null +++ b/parser/testdata/parser/mysql_ddl/output.sql @@ -0,0 +1,147 @@ +CREATE EVENT `e` ON SCHEDULE AT CURRENT_TIMESTAMP() DO SELECT 1 +-- case +CREATE EVENT IF NOT EXISTS `db1`.`e` ON SCHEDULE AT DATE_ADD(_UTF8MB4'2026-09-01 00:00:00', INTERVAL 1 DAY) DO SELECT 1 +-- case +CREATE EVENT `e` ON SCHEDULE EVERY 2 HOUR STARTS CURRENT_TIMESTAMP() ENDS DATE_ADD(CURRENT_TIMESTAMP(), INTERVAL 1 WEEK) ON COMPLETION PRESERVE DISABLE COMMENT 'cleanup' DO SELECT 1 +-- case +CREATE DEFINER = `admin`@`localhost` EVENT `e` ON SCHEDULE EVERY 1 DAY ON COMPLETION NOT PRESERVE DISABLE ON REPLICA DO SELECT 1 +-- case +ALTER EVENT `myevent` ON SCHEDULE EVERY 2 HOUR +-- case +ALTER EVENT `myevent` ON SCHEDULE AT CURRENT_TIMESTAMP() ON COMPLETION PRESERVE RENAME TO `yourevent` ENABLE COMMENT 'new comment' DO SELECT 1 +-- case +ALTER DEFINER = CURRENT_USER EVENT `e` COMMENT 'x' +-- case +DROP EVENT `e` +-- case +DROP EVENT IF EXISTS `db1`.`e` +-- case +CREATE TRIGGER `trg` BEFORE INSERT ON `t` FOR EACH ROW SET @`x`=1 +-- case +CREATE TRIGGER IF NOT EXISTS `trg` AFTER UPDATE ON `t` FOR EACH ROW SET @`x`=1 +-- case +CREATE TRIGGER `trg` AFTER DELETE ON `t` FOR EACH ROW FOLLOWS `other_trg` SET @`x`=1 +-- case +CREATE DEFINER = `admin`@`localhost` TRIGGER `trg` BEFORE UPDATE ON `t` FOR EACH ROW PRECEDES `other_trg` SET @`x`=1 +-- case +CREATE TRIGGER `trg` BEFORE INSERT ON `t` FOR EACH ROW BEGIN SET @`x`=1;SET @`y`=2; END +-- case +DROP TRIGGER `trg` +-- case +DROP TRIGGER IF EXISTS `db1`.`trg` +-- case +CREATE FUNCTION `f`(`x` INT) RETURNS INT DETERMINISTIC RETURN `x`+1 +-- case +CREATE FUNCTION `f`() RETURNS VARCHAR(64) COMMENT 'greets' LANGUAGE SQL NOT DETERMINISTIC READS SQL DATA SQL SECURITY INVOKER RETURN _UTF8MB4'hi' +-- case +CREATE DEFINER = `admin`@`localhost` FUNCTION `db1`.`f`(`x` INT, `y` CHAR(4)) RETURNS INT CONTAINS SQL SQL SECURITY DEFINER BEGIN RETURN `x`; END +-- case +CREATE FUNCTION `f`() RETURNS INT MODIFIES SQL DATA RETURN 1 +-- case +CREATE FUNCTION `f`() RETURNS INT NO SQL RETURN 1 +-- case +ALTER FUNCTION `myfunc` COMMENT 'some comment' +-- case +ALTER FUNCTION `myfunc` LANGUAGE SQL READS SQL DATA SQL SECURITY INVOKER +-- case +DROP FUNCTION `f` +-- case +DROP FUNCTION IF EXISTS `db1`.`f` +-- case +ALTER PROCEDURE `myproc` COMMENT 'some comment' +-- case +ALTER PROCEDURE `myproc` CONTAINS SQL SQL SECURITY DEFINER +-- case +CREATE DEFINER = `admin`@`localhost` PROCEDURE `p`() SELECT 1 +-- case +ALTER ALGORITHM = UNDEFINED DEFINER = CURRENT_USER SQL SECURITY DEFINER VIEW `v` AS SELECT 1 +-- case +ALTER ALGORITHM = MERGE DEFINER = `admin`@`localhost` SQL SECURITY INVOKER VIEW `v` (`a`,`b`) AS SELECT 1,2 WITH LOCAL CHECK OPTION +-- case +ALTER INSTANCE ROTATE INNODB MASTER KEY +-- case +ALTER INSTANCE ROTATE BINLOG MASTER KEY +-- case +ALTER INSTANCE RELOAD TLS +-- case +ALTER INSTANCE RELOAD TLS FOR CHANNEL `mysql_admin` NO ROLLBACK ON ERROR +-- case +ALTER INSTANCE RELOAD KEYRING +-- case +ALTER INSTANCE ENABLE INNODB REDO_LOG +-- case +ALTER INSTANCE DISABLE INNODB REDO_LOG +-- case +CREATE SERVER `s` FOREIGN DATA WRAPPER `mysql` OPTIONS (USER 'user', HOST 'host', DATABASE 'db') +-- case +CREATE SERVER `s` FOREIGN DATA WRAPPER `mysql` OPTIONS (HOST '127.0.0.1', PORT 3306, SOCKET '/tmp/mysql.sock', OWNER 'root', PASSWORD 'secret') +-- case +ALTER SERVER `s` OPTIONS (USER 'user') +-- case +DROP SERVER `s` +-- case +DROP SERVER IF EXISTS `s` +-- case +CREATE TABLESPACE `ts` ADD DATAFILE 'file.ibd' ENGINE = INNODB +-- case +CREATE TABLESPACE `ts` ADD DATAFILE 'file.dat' USE LOGFILE GROUP `lg1` INITIAL_SIZE = 134217728 EXTENT_SIZE = 4194304 MAX_SIZE = 1073741824 AUTOEXTEND_SIZE = 67108864 NODEGROUP = 1 WAIT COMMENT = 'ndb tablespace' ENGINE = NDB +-- case +CREATE UNDO TABLESPACE `undo_ts` ADD DATAFILE 'undo.ibu' FILE_BLOCK_SIZE = 8192 ENCRYPTION = 'Y' ENGINE_ATTRIBUTE = '{}' +-- case +ALTER TABLESPACE `ts` ADD DATAFILE 'file.ibd' ENGINE = NDB +-- case +ALTER TABLESPACE `ts` DROP DATAFILE 'file.ibd' WAIT ENGINE = NDB +-- case +ALTER TABLESPACE `ts` RENAME TO `ts2` +-- case +ALTER UNDO TABLESPACE `undo_ts` SET INACTIVE +-- case +ALTER UNDO TABLESPACE `undo_ts` SET ACTIVE +-- case +DROP TABLESPACE `ts` ENGINE = INNODB +-- case +DROP UNDO TABLESPACE `undo_ts` +-- case +CREATE LOGFILE GROUP `lg1` ADD UNDOFILE 'undo.dat' ENGINE = NDB +-- case +CREATE LOGFILE GROUP `lg1` ADD UNDOFILE 'undo.dat' INITIAL_SIZE = 33554432 UNDO_BUFFER_SIZE = 8388608 REDO_BUFFER_SIZE = 8388608 NODEGROUP = 1 WAIT COMMENT = 'ndb logs' ENGINE = NDB +-- case +ALTER LOGFILE GROUP `lg1` ADD UNDOFILE 'undo.dat' ENGINE = NDB +-- case +ALTER LOGFILE GROUP `lg1` ADD UNDOFILE 'undo2.dat' INITIAL_SIZE = 33554432 WAIT ENGINE = NDB +-- case +DROP LOGFILE GROUP `lg1` ENGINE = NDB +-- case +CREATE SPATIAL REFERENCE SYSTEM 5000 NAME 'my srs' DEFINITION 'GEOGCS[]' +-- case +CREATE OR REPLACE SPATIAL REFERENCE SYSTEM 5000 NAME 'my srs' ORGANIZATION 'EPSG' IDENTIFIED BY 4326 DEFINITION 'GEOGCS[]' DESCRIPTION 'a geographic srs' +-- case +CREATE SPATIAL REFERENCE SYSTEM IF NOT EXISTS 5000 NAME 'my srs' DEFINITION 'GEOGCS[]' +-- case +DROP SPATIAL REFERENCE SYSTEM 5000 +-- case +DROP SPATIAL REFERENCE SYSTEM IF EXISTS 5000 +-- case +CREATE LIBRARY `mylib` LANGUAGE JAVASCRIPT AS 'export function f() { return 1 }' +-- case +CREATE OR REPLACE LIBRARY IF NOT EXISTS `db1`.`mylib` LANGUAGE JAVASCRIPT AS 'export function f() { return 1 }' +-- case +ALTER LIBRARY `mylib` COMMENT 'updated' +-- case +DROP LIBRARY `mylib` +-- case +DROP LIBRARY IF EXISTS `db1`.`mylib` +-- case +CREATE JSON DUALITY VIEW `jdv` AS SELECT JSON_DUALITY_OBJECT('id' : `t`.`id`) FROM `t` +-- case +CREATE OR REPLACE JSON DUALITY VIEW `jdv` AS SELECT JSON_DUALITY_OBJECT('id' : `t`.`id`, 'name' : `t`.`name`, 'child' : JSON_DUALITY_OBJECT('cid' : `c`.`id`)) FROM `t` +-- case +ALTER JSON DUALITY VIEW `jdv` AS SELECT JSON_DUALITY_OBJECT('id' : `t`.`id`) FROM `t` +-- case +CREATE MASKING POLICY `p` ON `t` (`c`) USING (MASK_INNER(`c`, 1, 1)) +-- case +DROP MASKING POLICY `p` +-- case +DROP MASKING POLICY IF EXISTS `p` +-- case +SELECT `at`,`every`,`starts`,`ends`,`server`,`options`,`wrapper`,`contains`,`duality`,`rotate`,`innodb` FROM `t` diff --git a/parser/testdata/parser/mysql_dml/input.sql b/parser/testdata/parser/mysql_dml/input.sql new file mode 100644 index 0000000..fd5860b --- /dev/null +++ b/parser/testdata/parser/mysql_dml/input.sql @@ -0,0 +1,51 @@ +HANDLER t OPEN +-- case +HANDLER t OPEN AS h +-- case +HANDLER db1.t OPEN h +-- case +HANDLER t READ FIRST +-- case +HANDLER t READ NEXT +-- case +HANDLER t READ FIRST WHERE c > 1 LIMIT 10 +-- case +HANDLER t READ idx FIRST +-- case +HANDLER t READ idx NEXT +-- case +HANDLER t READ idx PREV +-- case +HANDLER t READ idx LAST +-- case +HANDLER t READ idx = (1, 'a') +-- case +HANDLER t READ idx <= (1) +-- case +HANDLER t READ idx >= (1) WHERE c < 100 +-- case +HANDLER t READ idx < (1) +-- case +HANDLER t READ idx > (1) LIMIT 2, 5 +-- case +HANDLER t READ PRIMARY = (1) +-- case +HANDLER t CLOSE +-- case +IMPORT TABLE FROM 't.sdi' +-- case +IMPORT TABLE FROM 't.sdi', 'u.sdi' +-- case +LOAD XML INFILE 'f.xml' INTO TABLE t +-- case +LOAD XML LOW_PRIORITY LOCAL INFILE 'f.xml' REPLACE INTO TABLE db1.t +-- case +LOAD XML CONCURRENT INFILE 'f.xml' IGNORE INTO TABLE t CHARACTER SET utf8mb4 ROWS IDENTIFIED BY '' IGNORE 2 LINES (a, @b) SET c = 1 +-- case +SELECT 1 INTO @a +-- case +SELECT a, b INTO @x, @y FROM t LIMIT 1 +-- case +SELECT 1 INTO DUMPFILE '/tmp/out' +-- case +SELECT prev, xml, dumpfile FROM concurrent diff --git a/parser/testdata/parser/mysql_dml/output.sql b/parser/testdata/parser/mysql_dml/output.sql new file mode 100644 index 0000000..25d1729 --- /dev/null +++ b/parser/testdata/parser/mysql_dml/output.sql @@ -0,0 +1,51 @@ +HANDLER `t` OPEN +-- case +HANDLER `t` OPEN AS `h` +-- case +HANDLER `db1`.`t` OPEN AS `h` +-- case +HANDLER `t` READ FIRST +-- case +HANDLER `t` READ NEXT +-- case +HANDLER `t` READ FIRST WHERE `c`>1 LIMIT 10 +-- case +HANDLER `t` READ `idx` FIRST +-- case +HANDLER `t` READ `idx` NEXT +-- case +HANDLER `t` READ `idx` PREV +-- case +HANDLER `t` READ `idx` LAST +-- case +HANDLER `t` READ `idx` = (1, _UTF8MB4'a') +-- case +HANDLER `t` READ `idx` <= (1) +-- case +HANDLER `t` READ `idx` >= (1) WHERE `c`<100 +-- case +HANDLER `t` READ `idx` < (1) +-- case +HANDLER `t` READ `idx` > (1) LIMIT 2,5 +-- case +HANDLER `t` READ `PRIMARY` = (1) +-- case +HANDLER `t` CLOSE +-- case +IMPORT TABLE FROM 't.sdi' +-- case +IMPORT TABLE FROM 't.sdi', 'u.sdi' +-- case +LOAD XML INFILE 'f.xml' INTO TABLE `t` +-- case +LOAD XML LOW_PRIORITY LOCAL INFILE 'f.xml' REPLACE INTO TABLE `db1`.`t` +-- case +LOAD XML CONCURRENT INFILE 'f.xml' IGNORE INTO TABLE `t` CHARACTER SET utf8mb4 ROWS IDENTIFIED BY '' IGNORE 2 ROWS (`a`,@`b`) SET `c`=1 +-- case +SELECT 1 INTO @`a` +-- case +SELECT `a`,`b` FROM `t` LIMIT 1 INTO @`x`, @`y` +-- case +SELECT 1 INTO DUMPFILE '/tmp/out' +-- case +SELECT `prev`,`xml`,`dumpfile` FROM `concurrent` diff --git a/parser/testdata/parser/mysql_replication/input.sql b/parser/testdata/parser/mysql_replication/input.sql new file mode 100644 index 0000000..4a59db3 --- /dev/null +++ b/parser/testdata/parser/mysql_replication/input.sql @@ -0,0 +1,63 @@ +PURGE BINARY LOGS TO 'binlog.000001' +-- case +PURGE BINARY LOGS BEFORE '2026-08-18 00:00:00' +-- case +PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY +-- case +RESET BINARY LOGS AND GTIDS +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_DB = (db1) +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_DB = (db1, db2), REPLICATE_IGNORE_DB = (db3) +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_TABLE = (db1.t1, db2.t2) +-- case +CHANGE REPLICATION FILTER REPLICATE_IGNORE_TABLE = (db1.t1) +-- case +CHANGE REPLICATION FILTER REPLICATE_WILD_DO_TABLE = ('db1.old%') +-- case +CHANGE REPLICATION FILTER REPLICATE_WILD_IGNORE_TABLE = ('db1.new%', 'db2.%') +-- case +CHANGE REPLICATION FILTER REPLICATE_REWRITE_DB = ((db1, db2)) +-- case +CHANGE REPLICATION FILTER REPLICATE_REWRITE_DB = ((db1, db2), (db3, db4)) FOR CHANNEL 'ch1' +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_DB = () +-- case +RESET REPLICA +-- case +RESET REPLICA ALL +-- case +RESET REPLICA ALL FOR CHANNEL 'ch1' +-- case +START REPLICA +-- case +START REPLICA IO_THREAD +-- case +START REPLICA IO_THREAD, SQL_THREAD +-- case +START REPLICA UNTIL SQL_BEFORE_GTIDS = '3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56' +-- case +START REPLICA UNTIL SQL_AFTER_GTIDS = '3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56' +-- case +START REPLICA UNTIL SOURCE_LOG_FILE = 'source1-bin.000291', SOURCE_LOG_POS = 137 +-- case +START REPLICA SQL_THREAD UNTIL RELAY_LOG_FILE = 'replica-relay-bin.000015', RELAY_LOG_POS = 5722 +-- case +START REPLICA UNTIL SQL_AFTER_MTS_GAPS +-- case +START REPLICA USER = 'u' PASSWORD = 'p' DEFAULT_AUTH = 'auth_plugin' FOR CHANNEL 'ch1' +-- case +STOP REPLICA +-- case +STOP REPLICA SQL_THREAD FOR CHANNEL 'ch1' +-- case +STOP REPLICA IO_THREAD, SQL_THREAD +-- case +START GROUP_REPLICATION +-- case +START GROUP_REPLICATION USER = 'u', PASSWORD = 'p', DEFAULT_AUTH = 'auth_plugin' +-- case +STOP GROUP_REPLICATION +-- case +SELECT filter, gtids, io_thread, sql_thread FROM group_replication diff --git a/parser/testdata/parser/mysql_replication/output.sql b/parser/testdata/parser/mysql_replication/output.sql new file mode 100644 index 0000000..1b70d41 --- /dev/null +++ b/parser/testdata/parser/mysql_replication/output.sql @@ -0,0 +1,63 @@ +PURGE BINARY LOGS TO 'binlog.000001' +-- case +PURGE BINARY LOGS BEFORE _UTF8MB4'2026-08-18 00:00:00' +-- case +PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 3 DAY) +-- case +RESET BINARY LOGS AND GTIDS +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_DB = (`db1`) +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_DB = (`db1`, `db2`), REPLICATE_IGNORE_DB = (`db3`) +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_TABLE = (`db1`.`t1`, `db2`.`t2`) +-- case +CHANGE REPLICATION FILTER REPLICATE_IGNORE_TABLE = (`db1`.`t1`) +-- case +CHANGE REPLICATION FILTER REPLICATE_WILD_DO_TABLE = ('db1.old%') +-- case +CHANGE REPLICATION FILTER REPLICATE_WILD_IGNORE_TABLE = ('db1.new%', 'db2.%') +-- case +CHANGE REPLICATION FILTER REPLICATE_REWRITE_DB = ((`db1`, `db2`)) +-- case +CHANGE REPLICATION FILTER REPLICATE_REWRITE_DB = ((`db1`, `db2`), (`db3`, `db4`)) FOR CHANNEL 'ch1' +-- case +CHANGE REPLICATION FILTER REPLICATE_DO_DB = () +-- case +RESET REPLICA +-- case +RESET REPLICA ALL +-- case +RESET REPLICA ALL FOR CHANNEL 'ch1' +-- case +START REPLICA +-- case +START REPLICA IO_THREAD +-- case +START REPLICA IO_THREAD, SQL_THREAD +-- case +START REPLICA UNTIL SQL_BEFORE_GTIDS = '3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56' +-- case +START REPLICA UNTIL SQL_AFTER_GTIDS = '3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56' +-- case +START REPLICA UNTIL SOURCE_LOG_FILE = 'source1-bin.000291', SOURCE_LOG_POS = 137 +-- case +START REPLICA SQL_THREAD UNTIL RELAY_LOG_FILE = 'replica-relay-bin.000015', RELAY_LOG_POS = 5722 +-- case +START REPLICA UNTIL SQL_AFTER_MTS_GAPS +-- case +START REPLICA USER = 'u' PASSWORD = 'p' DEFAULT_AUTH = 'auth_plugin' FOR CHANNEL 'ch1' +-- case +STOP REPLICA +-- case +STOP REPLICA SQL_THREAD FOR CHANNEL 'ch1' +-- case +STOP REPLICA IO_THREAD, SQL_THREAD +-- case +START GROUP_REPLICATION +-- case +START GROUP_REPLICATION USER = 'u', PASSWORD = 'p', DEFAULT_AUTH = 'auth_plugin' +-- case +STOP GROUP_REPLICATION +-- case +SELECT `filter`,`gtids`,`io_thread`,`sql_thread` FROM `group_replication` diff --git a/parser/testdata/parser/mysql_txn/input.sql b/parser/testdata/parser/mysql_txn/input.sql new file mode 100644 index 0000000..4ba4c8f --- /dev/null +++ b/parser/testdata/parser/mysql_txn/input.sql @@ -0,0 +1,37 @@ +XA START 'xid1' +-- case +XA BEGIN 'xid1' +-- case +XA START 'gtrid', 'bqual' +-- case +XA START 'gtrid', 'bqual', 7 +-- case +XA START 'xid1' JOIN +-- case +XA START 'xid1' RESUME +-- case +XA END 'xid1' +-- case +XA END 'gtrid', 'bqual', 7 +-- case +XA END 'xid1' SUSPEND +-- case +XA END 'xid1' SUSPEND FOR MIGRATE +-- case +XA PREPARE 'xid1' +-- case +XA COMMIT 'xid1' +-- case +XA COMMIT 'xid1' ONE PHASE +-- case +XA ROLLBACK 'xid1' +-- case +XA RECOVER +-- case +XA RECOVER CONVERT XID +-- case +LOCK INSTANCE FOR BACKUP +-- case +UNLOCK INSTANCE +-- case +SELECT xa, xid, one, phase FROM suspend WHERE migrate = 1 diff --git a/parser/testdata/parser/mysql_txn/output.sql b/parser/testdata/parser/mysql_txn/output.sql new file mode 100644 index 0000000..8409825 --- /dev/null +++ b/parser/testdata/parser/mysql_txn/output.sql @@ -0,0 +1,37 @@ +XA START 'xid1' +-- case +XA START 'xid1' +-- case +XA START 'gtrid', 'bqual' +-- case +XA START 'gtrid', 'bqual', 7 +-- case +XA START 'xid1' JOIN +-- case +XA START 'xid1' RESUME +-- case +XA END 'xid1' +-- case +XA END 'gtrid', 'bqual', 7 +-- case +XA END 'xid1' SUSPEND +-- case +XA END 'xid1' SUSPEND FOR MIGRATE +-- case +XA PREPARE 'xid1' +-- case +XA COMMIT 'xid1' +-- case +XA COMMIT 'xid1' ONE PHASE +-- case +XA ROLLBACK 'xid1' +-- case +XA RECOVER +-- case +XA RECOVER CONVERT XID +-- case +LOCK INSTANCE FOR BACKUP +-- case +UNLOCK INSTANCE +-- case +SELECT `xa`,`xid`,`one`,`phase` FROM `suspend` WHERE `migrate`=1 diff --git a/parser/testdata/parser/mysql_unsupported_ddl/input.sql b/parser/testdata/parser/mysql_unsupported_ddl/input.sql deleted file mode 100644 index 275bd3b..0000000 --- a/parser/testdata/parser/mysql_unsupported_ddl/input.sql +++ /dev/null @@ -1,57 +0,0 @@ -ALTER EVENT myevent ON SCHEDULE EVERY 2 HOUR --- case -ALTER FUNCTION myfunc COMMENT 'some comment' --- case -ALTER INSTANCE ROTATE INNODB MASTER KEY --- case -ALTER JSON DUALITY VIEW jdv AS SELECT JSON_DUALITY_OBJECT('id' : t.id) FROM t --- case -ALTER LIBRARY mylib COMMENT 'updated' --- case -ALTER LOGFILE GROUP lg1 ADD UNDOFILE 'undo.dat' ENGINE = NDB --- case -ALTER PROCEDURE myproc COMMENT 'some comment' --- case -ALTER SERVER s OPTIONS (USER 'user') --- case -ALTER TABLESPACE ts ADD DATAFILE 'file.ibd' ENGINE = NDB --- case -ALTER VIEW v AS SELECT 1 --- case -CREATE EVENT e ON SCHEDULE AT CURRENT_TIMESTAMP DO SELECT 1 --- case -CREATE FUNCTION f(x INT) RETURNS INT DETERMINISTIC RETURN x + 1 --- case -CREATE JSON DUALITY VIEW jdv AS SELECT JSON_DUALITY_OBJECT('id' : t.id) FROM t --- case -CREATE LIBRARY mylib LANGUAGE JAVASCRIPT AS 'export function f() { return 1 }' --- case -CREATE LOGFILE GROUP lg1 ADD UNDOFILE 'undo.dat' ENGINE = NDB --- case -CREATE MASKING POLICY p ON t (c) USING (mask_inner(c, 1, 1)) --- case -CREATE SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (USER 'user', HOST 'host', DATABASE 'db') --- case -CREATE SPATIAL REFERENCE SYSTEM 5000 NAME 'my srs' DEFINITION 'GEOGCS[]' --- case -CREATE TABLESPACE ts ADD DATAFILE 'file.ibd' ENGINE = INNODB --- case -CREATE TRIGGER trg BEFORE INSERT ON t FOR EACH ROW SET @x = 1 --- case -DROP EVENT e --- case -DROP FUNCTION f --- case -DROP LIBRARY mylib --- case -DROP LOGFILE GROUP lg1 ENGINE = NDB --- case -DROP MASKING POLICY p --- case -DROP SERVER s --- case -DROP SPATIAL REFERENCE SYSTEM 5000 --- case -DROP TABLESPACE ts ENGINE = INNODB --- case -DROP TRIGGER trg diff --git a/parser/testdata/parser/mysql_unsupported_ddl/output.sql b/parser/testdata/parser/mysql_unsupported_ddl/output.sql deleted file mode 100644 index c4a8ce5..0000000 --- a/parser/testdata/parser/mysql_unsupported_ddl/output.sql +++ /dev/null @@ -1,57 +0,0 @@ --- error: line 1 column 5 near "ALTER EVENT myevent ON SCHEDULE EVERY 2 HOUR" --- case --- error: line 1 column 5 near "ALTER FUNCTION myfunc COMMENT 'some comment'" --- case --- error: line 1 column 21 near "ROTATE INNODB MASTER KEY" --- case --- error: line 1 column 5 near "ALTER JSON DUALITY VIEW jdv AS SELECT JSON_DUALITY_OBJECT('id' : t.id) FROM t" --- case --- error: line 1 column 5 near "ALTER LIBRARY mylib COMMENT 'updated'" --- case --- error: line 1 column 5 near "ALTER LOGFILE GROUP lg1 ADD UNDOFILE 'undo.dat' ENGINE = NDB" --- case --- error: line 1 column 5 near "ALTER PROCEDURE myproc COMMENT 'some comment'" --- case --- error: line 1 column 5 near "ALTER SERVER s OPTIONS (USER 'user')" --- case --- error: line 1 column 5 near "ALTER TABLESPACE ts ADD DATAFILE 'file.ibd' ENGINE = NDB" --- case --- error: line 1 column 5 near "ALTER VIEW v AS SELECT 1" --- case --- error: line 1 column 12 near "EVENT e ON SCHEDULE AT CURRENT_TIMESTAMP DO SELECT 1" --- case --- error: line 1 column 18 near "(x INT) RETURNS INT DETERMINISTIC RETURN x + 1" --- case --- error: line 1 column 11 near "JSON DUALITY VIEW jdv AS SELECT JSON_DUALITY_OBJECT('id' : t.id) FROM t" --- case --- error: line 1 column 14 near "LIBRARY mylib LANGUAGE JAVASCRIPT AS 'export function f() { return 1 }'" --- case --- error: line 1 column 14 near "LOGFILE GROUP lg1 ADD UNDOFILE 'undo.dat' ENGINE = NDB" --- case --- error: line 1 column 38 near "USING (mask_inner(c, 1, 1))" --- case --- error: line 1 column 13 near "SERVER s FOREIGN DATA WRAPPER mysql OPTIONS (USER 'user', HOST 'host', DATABASE 'db')" --- case --- error: line 1 column 24 near "REFERENCE SYSTEM 5000 NAME 'my srs' DEFINITION 'GEOGCS[]'" --- case --- error: line 1 column 17 near "TABLESPACE ts ADD DATAFILE 'file.ibd' ENGINE = INNODB" --- case --- error: line 1 column 14 near "TRIGGER trg BEFORE INSERT ON t FOR EACH ROW SET @x = 1" --- case --- error: line 1 column 4 near "DROP EVENT e" --- case --- error: line 1 column 4 near "DROP FUNCTION f" --- case --- error: line 1 column 4 near "DROP LIBRARY mylib" --- case --- error: line 1 column 4 near "DROP LOGFILE GROUP lg1 ENGINE = NDB" --- case --- error: line 1 column 4 near "DROP MASKING POLICY p" --- case --- error: line 1 column 4 near "DROP SERVER s" --- case --- error: line 1 column 4 near "DROP SPATIAL REFERENCE SYSTEM 5000" --- case --- error: line 1 column 4 near "DROP TABLESPACE ts ENGINE = INNODB" --- case --- error: line 1 column 4 near "DROP TRIGGER trg" diff --git a/parser/testdata/parser/mysql_unsupported_dml/input.sql b/parser/testdata/parser/mysql_unsupported_dml/input.sql deleted file mode 100644 index 32dd239..0000000 --- a/parser/testdata/parser/mysql_unsupported_dml/input.sql +++ /dev/null @@ -1,13 +0,0 @@ -HANDLER t OPEN --- case -HANDLER t READ FIRST --- case -HANDLER t READ NEXT --- case -HANDLER t CLOSE --- case -IMPORT TABLE FROM 't.sdi' --- case -LOAD XML INFILE 'f.xml' INTO TABLE t --- case -SELECT 1 INTO @a diff --git a/parser/testdata/parser/mysql_unsupported_dml/output.sql b/parser/testdata/parser/mysql_unsupported_dml/output.sql deleted file mode 100644 index f179149..0000000 --- a/parser/testdata/parser/mysql_unsupported_dml/output.sql +++ /dev/null @@ -1,13 +0,0 @@ --- error: line 1 column 7 near "HANDLER t OPEN" --- case --- error: line 1 column 7 near "HANDLER t READ FIRST" --- case --- error: line 1 column 7 near "HANDLER t READ NEXT" --- case --- error: line 1 column 7 near "HANDLER t CLOSE" --- case --- error: line 1 column 6 near "IMPORT TABLE FROM 't.sdi'" --- case --- error: line 1 column 4 near "LOAD XML INFILE 'f.xml' INTO TABLE t" --- case --- error: line 1 column 16 near "@a" diff --git a/parser/testdata/parser/mysql_unsupported_replication/input.sql b/parser/testdata/parser/mysql_unsupported_replication/input.sql deleted file mode 100644 index 43a863c..0000000 --- a/parser/testdata/parser/mysql_unsupported_replication/input.sql +++ /dev/null @@ -1,19 +0,0 @@ -PURGE BINARY LOGS TO 'binlog.000001' --- case -PURGE BINARY LOGS BEFORE '2026-08-18 00:00:00' --- case -RESET BINARY LOGS AND GTIDS --- case -CHANGE REPLICATION FILTER REPLICATE_DO_DB = (db1) --- case -RESET REPLICA --- case -RESET REPLICA ALL --- case -START REPLICA --- case -STOP REPLICA --- case -START GROUP_REPLICATION --- case -STOP GROUP_REPLICATION diff --git a/parser/testdata/parser/mysql_unsupported_replication/output.sql b/parser/testdata/parser/mysql_unsupported_replication/output.sql deleted file mode 100644 index a3c8d9a..0000000 --- a/parser/testdata/parser/mysql_unsupported_replication/output.sql +++ /dev/null @@ -1,19 +0,0 @@ --- error: line 1 column 12 near "BINARY LOGS TO 'binlog.000001'" --- case --- error: line 1 column 12 near "BINARY LOGS BEFORE '2026-08-18 00:00:00'" --- case --- error: line 1 column 5 near "RESET BINARY LOGS AND GTIDS" --- case --- error: line 1 column 25 near "FILTER REPLICATE_DO_DB = (db1)" --- case --- error: line 1 column 5 near "RESET REPLICA" --- case --- error: line 1 column 5 near "RESET REPLICA ALL" --- case --- error: line 1 column 13 near "REPLICA" --- case --- error: line 1 column 12 near "REPLICA" --- case --- error: line 1 column 23 near "GROUP_REPLICATION" --- case --- error: line 1 column 22 near "GROUP_REPLICATION" diff --git a/parser/testdata/parser/mysql_unsupported_txn/input.sql b/parser/testdata/parser/mysql_unsupported_txn/input.sql deleted file mode 100644 index 5c444ad..0000000 --- a/parser/testdata/parser/mysql_unsupported_txn/input.sql +++ /dev/null @@ -1,17 +0,0 @@ -LOCK INSTANCE FOR BACKUP --- case -UNLOCK INSTANCE --- case -XA START 'xid1' --- case -XA END 'xid1' --- case -XA PREPARE 'xid1' --- case -XA COMMIT 'xid1' --- case -XA COMMIT 'xid1' ONE PHASE --- case -XA ROLLBACK 'xid1' --- case -XA RECOVER diff --git a/parser/testdata/parser/mysql_unsupported_txn/output.sql b/parser/testdata/parser/mysql_unsupported_txn/output.sql deleted file mode 100644 index ab95b66..0000000 --- a/parser/testdata/parser/mysql_unsupported_txn/output.sql +++ /dev/null @@ -1,17 +0,0 @@ --- error: line 1 column 13 near "INSTANCE FOR BACKUP" --- case --- error: line 1 column 15 near "INSTANCE" --- case --- error: line 1 column 2 near "XA START 'xid1'" --- case --- error: line 1 column 2 near "XA END 'xid1'" --- case --- error: line 1 column 2 near "XA PREPARE 'xid1'" --- case --- error: line 1 column 2 near "XA COMMIT 'xid1'" --- case --- error: line 1 column 2 near "XA COMMIT 'xid1' ONE PHASE" --- case --- error: line 1 column 2 near "XA ROLLBACK 'xid1'" --- case --- error: line 1 column 2 near "XA RECOVER" diff --git a/parser/token_kinds.go b/parser/token_kinds.go index a4bad8a..df0ff3a 100644 --- a/parser/token_kinds.go +++ b/parser/token_kinds.go @@ -81,6 +81,7 @@ const ( as = 57369 asc = 57370 ascii = 57608 + at = 58296 asof = 57347 assignmentEq = 58207 attribute = 57609 @@ -98,6 +99,7 @@ const ( backups = 57620 batch = 58126 bdr = 57621 + before = 58286 begin = 57622 bernoulli = 57623 between = 57371 @@ -197,6 +199,7 @@ const ( commit = 57657 committed = 57658 compact = 57659 + completion = 58297 component = 58261 compress = 58007 compressed = 57660 @@ -204,12 +207,14 @@ const ( compressionLevel = 57662 compressionType = 57663 concurrency = 57664 + concurrent = 58292 config = 57665 connection = 57666 consistency = 57667 consistent = 57668 constraint = 57386 constraints = 58008 + contains = 58298 context = 57669 continueKwd = 57387 convert = 57388 @@ -244,6 +249,7 @@ const ( databases = 57399 dateAdd = 58013 dateSub = 58014 + datafile = 58299 dateType = 57681 datetimeType = 57682 day = 57683 @@ -267,6 +273,7 @@ const ( depth = 58163 desc = 57409 describe = 57410 + deterministic = 58300 diagnostics = 58253 digest = 57688 directory = 57689 @@ -288,10 +295,13 @@ const ( dry = 58167 dryRun = 58017 dual = 57416 + duality = 58301 dump = 58018 + dumpfile = 58293 duplicate = 57695 dynamic = 57696 elseIfKwd = 57418 + each = 58302 elseKwd = 57417 empty = 58220 enable = 57697 @@ -301,6 +311,7 @@ const ( encryptionKeyFile = 57700 encryptionMethod = 57701 end = 57702 + ends = 58303 endTime = 58019 enforced = 57703 engine = 57704 @@ -314,6 +325,7 @@ const ( escaped = 57420 event = 57711 events = 57712 + every = 58304 evolve = 57713 exact = 58020 except = 57421 @@ -338,6 +350,7 @@ const ( fetch = 57426 fields = 57723 file = 57724 + filter = 58287 first = 57725 firstValue = 57427 fixed = 57726 @@ -351,6 +364,7 @@ const ( followerConstraints = 58026 followers = 58027 following = 57728 + follows = 58305 forKwd = 57431 force = 57432 foreign = 57433 @@ -373,6 +387,8 @@ const ( group = 57438 groupConcat = 58031 groups = 57439 + groupReplication = 58288 + gtids = 58289 handler = 57736 hash = 57737 having = 57440 @@ -410,6 +426,7 @@ const ( indexes = 57751 infile = 57450 inner = 57451 + innodb = 58306 inout = 57452 inplace = 58033 insert = 57453 @@ -434,6 +451,7 @@ const ( inverted = 58036 invisible = 57754 invoker = 57755 + ioThread = 58290 io = 57756 ioReadBandwidth = 58037 ioWriteBandwidth = 58038 @@ -448,6 +466,7 @@ const ( jsonArrayagg = 58039 jsonObjectAgg = 58040 jsonSumCrc32 = 58041 + jsonDualityObject = 58307 jsonType = 57760 jss = 58211 juss = 58212 @@ -492,6 +511,7 @@ const ( lock = 57484 locked = 57773 log = 58047 + logfile = 58308 logs = 57774 long = 57485 longblobType = 57486 @@ -543,6 +563,7 @@ const ( metadata = 58051 microsecond = 57788 middleIntType = 57494 + migrate = 58280 min = 58052 minRows = 57791 minValue = 57790 @@ -552,6 +573,7 @@ const ( mod = 57497 mode = 57792 moderated = 58114 + modifies = 58309 modify = 57793 monitor = 57794 month = 57795 @@ -601,6 +623,7 @@ const ( oltpWriteOnly = 57817 on = 57506 onDuplicate = 57820 + one = 58281 online = 57818 only = 57819 open = 57821 @@ -610,6 +633,7 @@ const ( option = 57508 optional = 57822 optionally = 57509 + options = 58310 optionallyEnclosedBy = 57351 or = 57510 order = 57511 @@ -639,6 +663,7 @@ const ( percentRank = 57517 persist = 58265 pessimistic = 58176 + phase = 58282 pipes = 57359 pipesAsOr = 57838 placement = 58057 @@ -651,11 +676,13 @@ const ( policy = 57841 position = 58060 preSplitRegions = 57845 + precedes = 58311 preceding = 57842 precisionType = 57518 predicate = 58061 prepare = 57843 preserve = 57844 + prev = 58294 primary = 57519 primaryRegion = 58062 priority = 58063 @@ -679,6 +706,7 @@ const ( rateLimit = 57857 raw = 58178 read = 57523 + reads = 58312 readOnly = 58066 realType = 57524 rebuild = 57858 @@ -719,6 +747,7 @@ const ( restores = 57876 restrict = 57533 resume = 57877 + returnKwd = 58313 returns = 58267 reuse = 57878 reverse = 57879 @@ -728,6 +757,7 @@ const ( role = 57880 rollback = 57881 rollup = 57882 + rotate = 58314 routine = 57883 row = 57537 rowCount = 57884 @@ -761,6 +791,7 @@ const ( sequence = 57899 serial = 57900 serializable = 57901 + server = 58315 session = 57902 sessionStates = 58185 set = 57542 @@ -794,6 +825,7 @@ const ( sqlCalcFoundRows = 57551 sqlNoCache = 57919 sqlSmallResult = 57552 + sqlThread = 58291 sqlTsiDay = 57920 sqlTsiHour = 57921 sqlTsiMinute = 57922 @@ -812,6 +844,7 @@ const ( startTS = 58080 startTime = 58079 starting = 57554 + starts = 58316 statistics = 58187 stats = 58188 statsAutoRecalc = 57929 @@ -851,6 +884,7 @@ const ( sum = 58090 super = 57942 survivalPreferences = 58091 + suspend = 58283 swaps = 57943 switchGroup = 58092 switchesSym = 57944 @@ -924,6 +958,8 @@ const ( uncommitted = 57973 undefined = 57974 underscoreCS = 57352 + undo = 58317 + undofile = 58318 unicodeSym = 57975 uninstall = 58271 union = 57569 @@ -981,8 +1017,12 @@ const ( withSysTable = 57991 without = 57990 workload = 57992 + wrapper = 58319 write = 57592 x509 = 57993 + xa = 58284 + xid = 58285 + xml = 58295 xor = 57593 yearMonth = 57594 yearType = 57994