diff --git a/ast/admin.go b/ast/admin.go new file mode 100644 index 0000000..2a93fd4 --- /dev/null +++ b/ast/admin.go @@ -0,0 +1,718 @@ +// 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/auth" + "github.com/sqlc-dev/marino/format" +) + +// The MySQL database administration statements (MySQL 26.7 §15.7): +// table maintenance (CHECK/CHECKSUM/REPAIR TABLE), components, plugins, +// and loadable functions, CLONE, CACHE INDEX, LOAD INDEX INTO CACHE, +// and RESET PERSIST. + +var ( + _ Node = &CacheTableIndex{} + + _ StmtNode = &CheckTableStmt{} + _ StmtNode = &ChecksumTableStmt{} + _ StmtNode = &RepairTablesStmt{} + _ StmtNode = &CreateLoadableFunctionStmt{} + _ StmtNode = &InstallComponentStmt{} + _ StmtNode = &UninstallComponentStmt{} + _ StmtNode = &InstallPluginStmt{} + _ StmtNode = &UninstallPluginStmt{} + _ StmtNode = &CloneStmt{} + _ StmtNode = &CacheIndexStmt{} + _ StmtNode = &LoadIndexStmt{} + _ StmtNode = &ResetPersistStmt{} + + _ SensitiveStmtNode = &CloneStmt{} +) + +// CheckTableOption is one check option of a CHECK TABLE statement. +type CheckTableOption int + +const ( + // CheckTableForUpgrade is FOR UPGRADE. + CheckTableForUpgrade CheckTableOption = iota + // CheckTableQuick is QUICK. + CheckTableQuick + // CheckTableFast is FAST. + CheckTableFast + // CheckTableMedium is MEDIUM. + CheckTableMedium + // CheckTableExtended is EXTENDED. + CheckTableExtended + // CheckTableChanged is CHANGED. + CheckTableChanged +) + +// String implements fmt.Stringer interface. +func (n CheckTableOption) String() string { + switch n { + case CheckTableForUpgrade: + return "FOR UPGRADE" + case CheckTableQuick: + return "QUICK" + case CheckTableFast: + return "FAST" + case CheckTableMedium: + return "MEDIUM" + case CheckTableExtended: + return "EXTENDED" + case CheckTableChanged: + return "CHANGED" + } + return "" +} + +// CheckTableStmt is a CHECK TABLE statement: +// CHECK TABLE tbl_name [, tbl_name] ... [option] ... +// Options keeps the source order (MySQL accepts them in any order and +// repeated). +type CheckTableStmt struct { + stmtNode + + Tables []*TableName + Options []CheckTableOption +} + +// Restore implements Node interface. +func (n *CheckTableStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CHECK TABLE ") + 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 CheckTableStmt.Tables[%d]", i) + } + } + for _, opt := range n.Options { + ctx.WritePlain(" ") + ctx.WriteKeyWord(opt.String()) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *CheckTableStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CheckTableStmt) + for i, table := range n.Tables { + node, ok := table.Accept(v) + if !ok { + return n, false + } + n.Tables[i] = node.(*TableName) + } + return v.Leave(n) +} + +// ChecksumType is the QUICK/EXTENDED modifier of a CHECKSUM TABLE +// statement. +type ChecksumType int + +const ( + // ChecksumTypeDefault omits the modifier. + ChecksumTypeDefault ChecksumType = iota + // ChecksumTypeQuick is QUICK. + ChecksumTypeQuick + // ChecksumTypeExtended is EXTENDED. + ChecksumTypeExtended +) + +// ChecksumTableStmt is a CHECKSUM TABLE statement: +// CHECKSUM TABLE tbl_name [, tbl_name] ... [QUICK | EXTENDED]. +type ChecksumTableStmt struct { + stmtNode + + Tables []*TableName + Type ChecksumType +} + +// Restore implements Node interface. +func (n *ChecksumTableStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CHECKSUM TABLE ") + 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 ChecksumTableStmt.Tables[%d]", i) + } + } + switch n.Type { + case ChecksumTypeQuick: + ctx.WriteKeyWord(" QUICK") + case ChecksumTypeExtended: + ctx.WriteKeyWord(" EXTENDED") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ChecksumTableStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ChecksumTableStmt) + for i, table := range n.Tables { + node, ok := table.Accept(v) + if !ok { + return n, false + } + n.Tables[i] = node.(*TableName) + } + return v.Leave(n) +} + +// RepairTablesStmt is a REPAIR TABLE statement: +// REPAIR [NO_WRITE_TO_BINLOG | LOCAL] TABLE tbl_name [, tbl_name] ... +// [QUICK] [EXTENDED] [USE_FRM]. +// (RepairTableStmt is the unrelated TiDB ADMIN REPAIR TABLE statement.) +type RepairTablesStmt struct { + stmtNode + + NoWriteToBinLog bool + Tables []*TableName + Quick bool + Extended bool + UseFrm bool +} + +// Restore implements Node interface. +func (n *RepairTablesStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("REPAIR ") + if n.NoWriteToBinLog { + ctx.WriteKeyWord("NO_WRITE_TO_BINLOG ") + } + ctx.WriteKeyWord("TABLE ") + 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 RepairTablesStmt.Tables[%d]", i) + } + } + if n.Quick { + ctx.WriteKeyWord(" QUICK") + } + if n.Extended { + ctx.WriteKeyWord(" EXTENDED") + } + if n.UseFrm { + ctx.WriteKeyWord(" USE_FRM") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *RepairTablesStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*RepairTablesStmt) + for i, table := range n.Tables { + node, ok := table.Accept(v) + if !ok { + return n, false + } + n.Tables[i] = node.(*TableName) + } + return v.Leave(n) +} + +// LoadableFunctionReturnType is the RETURNS type of a loadable function. +type LoadableFunctionReturnType int + +const ( + // LoadableFunctionReturnString is RETURNS STRING. + LoadableFunctionReturnString LoadableFunctionReturnType = iota + // LoadableFunctionReturnInteger is RETURNS INTEGER (or INT). + LoadableFunctionReturnInteger + // LoadableFunctionReturnReal is RETURNS REAL. + LoadableFunctionReturnReal + // LoadableFunctionReturnDecimal is RETURNS DECIMAL. + LoadableFunctionReturnDecimal +) + +// String implements fmt.Stringer interface. +func (n LoadableFunctionReturnType) String() string { + switch n { + case LoadableFunctionReturnString: + return "STRING" + case LoadableFunctionReturnInteger: + return "INTEGER" + case LoadableFunctionReturnReal: + return "REAL" + case LoadableFunctionReturnDecimal: + return "DECIMAL" + } + return "" +} + +// CreateLoadableFunctionStmt is a CREATE FUNCTION statement for loadable +// functions: +// CREATE [AGGREGATE] FUNCTION [IF NOT EXISTS] function_name +// RETURNS {STRING | INTEGER | REAL | DECIMAL} SONAME shared_library_name. +type CreateLoadableFunctionStmt struct { + stmtNode + + IfNotExists bool + Aggregate bool + FunctionName CIStr + ReturnType LoadableFunctionReturnType + SoName string +} + +// Restore implements Node interface. +func (n *CreateLoadableFunctionStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CREATE ") + if n.Aggregate { + ctx.WriteKeyWord("AGGREGATE ") + } + ctx.WriteKeyWord("FUNCTION ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + ctx.WriteName(n.FunctionName.O) + ctx.WriteKeyWord(" RETURNS ") + ctx.WriteKeyWord(n.ReturnType.String()) + ctx.WriteKeyWord(" SONAME ") + ctx.WriteString(n.SoName) + return nil +} + +// Accept implements Node Accept interface. +func (n *CreateLoadableFunctionStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CreateLoadableFunctionStmt) + return v.Leave(n) +} + +// InstallComponentStmt is an INSTALL COMPONENT statement: +// INSTALL COMPONENT component_name [, component_name] ... +// [SET variable = expr [, variable = expr] ...]. +type InstallComponentStmt struct { + stmtNode + + Components []string + Variables []*VariableAssignment +} + +// Restore implements Node interface. +func (n *InstallComponentStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("INSTALL COMPONENT ") + for i, component := range n.Components { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteString(component) + } + for i, v := range n.Variables { + if i == 0 { + ctx.WriteKeyWord(" SET ") + } else { + ctx.WritePlain(", ") + } + if err := v.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore InstallComponentStmt.Variables[%d]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *InstallComponentStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*InstallComponentStmt) + for i, v2 := range n.Variables { + node, ok := v2.Accept(v) + if !ok { + return n, false + } + n.Variables[i] = node.(*VariableAssignment) + } + return v.Leave(n) +} + +// UninstallComponentStmt is an UNINSTALL COMPONENT statement: +// UNINSTALL COMPONENT component_name [, component_name] ... +type UninstallComponentStmt struct { + stmtNode + + Components []string +} + +// Restore implements Node interface. +func (n *UninstallComponentStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("UNINSTALL COMPONENT ") + for i, component := range n.Components { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteString(component) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *UninstallComponentStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*UninstallComponentStmt) + return v.Leave(n) +} + +// InstallPluginStmt is an INSTALL PLUGIN statement: +// INSTALL PLUGIN plugin_name SONAME shared_library_name. +type InstallPluginStmt struct { + stmtNode + + PluginName CIStr + SoName string +} + +// Restore implements Node interface. +func (n *InstallPluginStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("INSTALL PLUGIN ") + ctx.WriteName(n.PluginName.O) + ctx.WriteKeyWord(" SONAME ") + ctx.WriteString(n.SoName) + return nil +} + +// Accept implements Node Accept interface. +func (n *InstallPluginStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*InstallPluginStmt) + return v.Leave(n) +} + +// UninstallPluginStmt is an UNINSTALL PLUGIN statement: +// UNINSTALL PLUGIN plugin_name. +type UninstallPluginStmt struct { + stmtNode + + PluginName CIStr +} + +// Restore implements Node interface. +func (n *UninstallPluginStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("UNINSTALL PLUGIN ") + ctx.WriteName(n.PluginName.O) + return nil +} + +// Accept implements Node Accept interface. +func (n *UninstallPluginStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*UninstallPluginStmt) + return v.Leave(n) +} + +// CloneSSLType is the [REQUIRE [NO] SSL] clause of CLONE INSTANCE. +type CloneSSLType int + +const ( + // CloneSSLDefault omits the clause. + CloneSSLDefault CloneSSLType = iota + // CloneSSLRequire is REQUIRE SSL. + CloneSSLRequire + // CloneSSLRequireNo is REQUIRE NO SSL. + CloneSSLRequireNo +) + +// CloneStmt is a CLONE statement: +// +// CLONE LOCAL DATA DIRECTORY [=] 'clone_dir' +// CLONE INSTANCE FROM 'user'@'host':port IDENTIFIED BY 'password' +// [DATA DIRECTORY [=] 'clone_dir'] [REQUIRE [NO] SSL] +// +// Local selects the first form; the remote form sets User, Port, and +// Password. DataDirectory is empty when the optional clause is absent +// from the remote form. +type CloneStmt struct { + stmtNode + + Local bool + DataDirectory string + User *auth.UserIdentity + Port uint64 + Password string + SSL CloneSSLType +} + +// Restore implements Node interface. +func (n *CloneStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CLONE ") + if n.Local { + ctx.WriteKeyWord("LOCAL DATA DIRECTORY ") + ctx.WritePlain("= ") + ctx.WriteString(n.DataDirectory) + return nil + } + ctx.WriteKeyWord("INSTANCE FROM ") + if err := n.User.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CloneStmt.User") + } + ctx.WritePlainf(":%d", n.Port) + ctx.WriteKeyWord(" IDENTIFIED BY ") + ctx.WriteString(n.Password) + if n.DataDirectory != "" { + ctx.WriteKeyWord(" DATA DIRECTORY ") + ctx.WritePlain("= ") + ctx.WriteString(n.DataDirectory) + } + switch n.SSL { + case CloneSSLRequire: + ctx.WriteKeyWord(" REQUIRE SSL") + case CloneSSLRequireNo: + ctx.WriteKeyWord(" REQUIRE NO SSL") + } + return nil +} + +// SecureText implements SensitiveStatement interface. +func (n *CloneStmt) SecureText() string { + masked := *n + if !masked.Local { + masked.Password = "xxxxxx" + } + var sb strings.Builder + _ = masked.Restore(format.NewRestoreCtx(format.DefaultRestoreFlags, &sb)) + return sb.String() +} + +// Accept implements Node Accept interface. +func (n *CloneStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CloneStmt) + return v.Leave(n) +} + +// CacheTableIndex is one table specification of a CACHE INDEX or LOAD +// INDEX INTO CACHE statement: +// tbl_name [PARTITION (partition_list)] [{INDEX | KEY} (index_name, ...)] +// [IGNORE LEAVES]. AllPartitions is PARTITION (ALL); IgnoreLeaves is only +// produced by LOAD INDEX INTO CACHE. +type CacheTableIndex struct { + node + + Table *TableName + Partitions []CIStr + AllPartitions bool + Indexes []CIStr + IgnoreLeaves bool +} + +// Restore implements Node interface. +func (n *CacheTableIndex) Restore(ctx *format.RestoreCtx) error { + if err := n.Table.Restore(ctx); err != nil { + return annotate(err, "An error occurred while restore CacheTableIndex.Table") + } + if n.AllPartitions { + ctx.WriteKeyWord(" PARTITION ") + ctx.WritePlain("(") + ctx.WriteKeyWord("ALL") + ctx.WritePlain(")") + } else if len(n.Partitions) > 0 { + ctx.WriteKeyWord(" PARTITION ") + ctx.WritePlain("(") + for i, partition := range n.Partitions { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(partition.O) + } + ctx.WritePlain(")") + } + if len(n.Indexes) > 0 { + ctx.WriteKeyWord(" INDEX ") + ctx.WritePlain("(") + for i, idx := range n.Indexes { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(idx.O) + } + ctx.WritePlain(")") + } + if n.IgnoreLeaves { + ctx.WriteKeyWord(" IGNORE LEAVES") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *CacheTableIndex) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CacheTableIndex) + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + return v.Leave(n) +} + +// CacheIndexStmt is a CACHE INDEX statement: +// +// CACHE INDEX {tbl_index_list [, tbl_index_list] ... +// | tbl_name PARTITION (partition_list)} IN key_cache_name +type CacheIndexStmt struct { + stmtNode + + TableIndexes []*CacheTableIndex + KeyCacheName CIStr +} + +// Restore implements Node interface. +func (n *CacheIndexStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CACHE INDEX ") + for i, ti := range n.TableIndexes { + if i != 0 { + ctx.WritePlain(", ") + } + if err := ti.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore CacheIndexStmt.TableIndexes[%d]", i) + } + } + ctx.WriteKeyWord(" IN ") + ctx.WriteName(n.KeyCacheName.O) + return nil +} + +// Accept implements Node Accept interface. +func (n *CacheIndexStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*CacheIndexStmt) + for i, ti := range n.TableIndexes { + node, ok := ti.Accept(v) + if !ok { + return n, false + } + n.TableIndexes[i] = node.(*CacheTableIndex) + } + return v.Leave(n) +} + +// LoadIndexStmt is a LOAD INDEX INTO CACHE statement: +// +// LOAD INDEX INTO CACHE +// tbl_index_or_partition [, tbl_index_or_partition] ... +type LoadIndexStmt struct { + stmtNode + + TableIndexes []*CacheTableIndex +} + +// Restore implements Node interface. +func (n *LoadIndexStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("LOAD INDEX INTO CACHE ") + for i, ti := range n.TableIndexes { + if i != 0 { + ctx.WritePlain(", ") + } + if err := ti.Restore(ctx); err != nil { + return annotatef(err, "An error occurred while restore LoadIndexStmt.TableIndexes[%d]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *LoadIndexStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*LoadIndexStmt) + for i, ti := range n.TableIndexes { + node, ok := ti.Accept(v) + if !ok { + return n, false + } + n.TableIndexes[i] = node.(*CacheTableIndex) + } + return v.Leave(n) +} + +// ResetPersistStmt is a RESET PERSIST statement: +// RESET PERSIST [[IF EXISTS] system_var_name]. +// Variable is empty when every persisted variable is removed; IfExists +// requires Variable. +type ResetPersistStmt struct { + stmtNode + + IfExists bool + Variable string +} + +// Restore implements Node interface. +func (n *ResetPersistStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("RESET PERSIST") + if n.Variable != "" { + ctx.WritePlain(" ") + if n.IfExists { + ctx.WriteKeyWord("IF EXISTS ") + } + ctx.WriteName(n.Variable) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ResetPersistStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ResetPersistStmt) + return v.Leave(n) +} diff --git a/ast/ddl.go b/ast/ddl.go index c0d4dd7..aa87de3 100644 --- a/ast/ddl.go +++ b/ast/ddl.go @@ -2475,9 +2475,18 @@ func (n *PlacementOption) Restore(ctx *format.RestoreCtx) error { // ResourceGroupOption is used for parsing resource group option. type ResourceGroupOption struct { - Tp ResourceUnitType - StrValue string - UintValue uint64 + Tp ResourceUnitType + StrValue string + UintValue uint64 + // IntValue holds the ResourceGroupThreadPriority value, which may be + // negative (MySQL allows -20..19). + IntValue int64 + // BoolValue distinguishes ResourceGroupEnable's ENABLE (true) from + // DISABLE (false). + BoolValue bool + // Force is the optional FORCE modifier of DISABLE (MySQL syntax + // ALTER RESOURCE GROUP ... DISABLE FORCE). + Force bool Burstable BurstableType RunawayOptionList []*ResourceGroupRunawayOption BackgroundOptions []*ResourceGroupBackgroundOption @@ -2500,6 +2509,13 @@ const ( ResourceUnlimitedOption ResourceGroupRunaway ResourceGroupBackground + + // MySQL-syntax resource group options (CREATE/ALTER RESOURCE GROUP + // per the MySQL reference manual rather than the TiDB dialect). + ResourceGroupType + ResourceGroupVCPU + ResourceGroupThreadPriority + ResourceGroupEnable ) type BurstableType int @@ -2564,6 +2580,27 @@ func (n *ResourceGroupOption) Restore(ctx *format.RestoreCtx) error { } else { ctx.WritePlain("NULL") } + case ResourceGroupType: + ctx.WriteKeyWord("TYPE ") + ctx.WritePlain("= ") + ctx.WriteKeyWord(n.StrValue) + case ResourceGroupVCPU: + ctx.WriteKeyWord("VCPU ") + ctx.WritePlain("= ") + ctx.WritePlain(n.StrValue) + case ResourceGroupThreadPriority: + ctx.WriteKeyWord("THREAD_PRIORITY ") + ctx.WritePlain("= ") + ctx.WritePlainf("%d", n.IntValue) + case ResourceGroupEnable: + if n.BoolValue { + ctx.WriteKeyWord("ENABLE") + } else { + ctx.WriteKeyWord("DISABLE") + if n.Force { + ctx.WriteKeyWord(" FORCE") + } + } case ResourceGroupBackground: ctx.WritePlain("BACKGROUND ") ctx.WritePlain("= ") diff --git a/ast/sem.go b/ast/sem.go index c202b52..e63589c 100644 --- a/ast/sem.go +++ b/ast/sem.go @@ -474,6 +474,30 @@ const ( ResignalCommand = "RESIGNAL" // GetDiagnosticsCommand represents GET DIAGNOSTICS statement GetDiagnosticsCommand = "GET DIAGNOSTICS" + // CheckTableCommand represents CHECK TABLE statement + CheckTableCommand = "CHECK TABLE" + // ChecksumTableCommand represents CHECKSUM TABLE statement + ChecksumTableCommand = "CHECKSUM TABLE" + // RepairTablesCommand represents REPAIR TABLE statement + RepairTablesCommand = "REPAIR TABLE" + // CreateLoadableFunctionCommand represents CREATE FUNCTION statement for loadable functions + CreateLoadableFunctionCommand = "CREATE FUNCTION" + // InstallComponentCommand represents INSTALL COMPONENT statement + InstallComponentCommand = "INSTALL COMPONENT" + // UninstallComponentCommand represents UNINSTALL COMPONENT statement + UninstallComponentCommand = "UNINSTALL COMPONENT" + // InstallPluginCommand represents INSTALL PLUGIN statement + InstallPluginCommand = "INSTALL PLUGIN" + // UninstallPluginCommand represents UNINSTALL PLUGIN statement + UninstallPluginCommand = "UNINSTALL PLUGIN" + // CloneCommand represents CLONE statement + CloneCommand = "CLONE" + // CacheIndexCommand represents CACHE INDEX statement + CacheIndexCommand = "CACHE INDEX" + // LoadIndexCommand represents LOAD INDEX INTO CACHE statement + LoadIndexCommand = "LOAD INDEX INTO CACHE" + // ResetPersistCommand represents RESET PERSIST statement + ResetPersistCommand = "RESET PERSIST" // UnknownCommand represents unknown statements UnknownCommand = "UNKNOWN" // SetOprCommand represents UNION/INTERSECT/EXCEPT statement @@ -1362,3 +1386,63 @@ func (n *ResignalStmt) SEMCommand() string { func (n *GetDiagnosticsStmt) SEMCommand() string { return GetDiagnosticsCommand } + +// SEMCommand returns the command string for the statement. +func (n *CheckTableStmt) SEMCommand() string { + return CheckTableCommand +} + +// SEMCommand returns the command string for the statement. +func (n *ChecksumTableStmt) SEMCommand() string { + return ChecksumTableCommand +} + +// SEMCommand returns the command string for the statement. +func (n *RepairTablesStmt) SEMCommand() string { + return RepairTablesCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CreateLoadableFunctionStmt) SEMCommand() string { + return CreateLoadableFunctionCommand +} + +// SEMCommand returns the command string for the statement. +func (n *InstallComponentStmt) SEMCommand() string { + return InstallComponentCommand +} + +// SEMCommand returns the command string for the statement. +func (n *UninstallComponentStmt) SEMCommand() string { + return UninstallComponentCommand +} + +// SEMCommand returns the command string for the statement. +func (n *InstallPluginStmt) SEMCommand() string { + return InstallPluginCommand +} + +// SEMCommand returns the command string for the statement. +func (n *UninstallPluginStmt) SEMCommand() string { + return UninstallPluginCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CloneStmt) SEMCommand() string { + return CloneCommand +} + +// SEMCommand returns the command string for the statement. +func (n *CacheIndexStmt) SEMCommand() string { + return CacheIndexCommand +} + +// SEMCommand returns the command string for the statement. +func (n *LoadIndexStmt) SEMCommand() string { + return LoadIndexCommand +} + +// SEMCommand returns the command string for the statement. +func (n *ResetPersistStmt) SEMCommand() string { + return ResetPersistCommand +} diff --git a/parser/keyword_classes.go b/parser/keyword_classes.go index f8afbcd..3bc6438 100644 --- a/parser/keyword_classes.go +++ b/parser/keyword_classes.go @@ -413,6 +413,23 @@ var unReservedKeywordNames = []string{ "IETF_QUOTES", "DIAGNOSTICS", "STACKED", + "AGGREGATE", + "CHANGED", + "CLONE", + "COMPONENT", + "FAST", + "INSTALL", + "LEAVES", + "PERSIST", + "PLUGIN", + "RETURNS", + "SONAME", + "STRING", + "THREAD_PRIORITY", + "UNINSTALL", + "UPGRADE", + "USE_FRM", + "VCPU", } // notKeywordTokenNames lists the NotKeywordToken production alternatives of parser.y. diff --git a/parser/keywords.go b/parser/keywords.go index b3f321d..11f8a60 100644 --- a/parser/keywords.go +++ b/parser/keywords.go @@ -272,6 +272,7 @@ var Keywords = []KeywordsType{ {"AFFINITY", false, "unreserved"}, {"AFTER", false, "unreserved"}, {"AGAINST", false, "unreserved"}, + {"AGGREGATE", false, "unreserved"}, {"AGO", false, "unreserved"}, {"ALGORITHM", false, "unreserved"}, {"ALWAYS", false, "unreserved"}, @@ -309,6 +310,7 @@ var Keywords = []KeywordsType{ {"CASCADED", false, "unreserved"}, {"CAUSAL", false, "unreserved"}, {"CHAIN", false, "unreserved"}, + {"CHANGED", false, "unreserved"}, {"CHANNEL", false, "unreserved"}, {"CHARSET", false, "unreserved"}, {"CHECKPOINT", false, "unreserved"}, @@ -318,6 +320,7 @@ var Keywords = []KeywordsType{ {"CLEANUP", false, "unreserved"}, {"CLIENT", false, "unreserved"}, {"CLIENT_ERRORS_SUMMARY", false, "unreserved"}, + {"CLONE", false, "unreserved"}, {"CLOSE", false, "unreserved"}, {"CLUSTER", false, "unreserved"}, {"CLUSTERED", false, "unreserved"}, @@ -330,6 +333,7 @@ var Keywords = []KeywordsType{ {"COMMIT", false, "unreserved"}, {"COMMITTED", false, "unreserved"}, {"COMPACT", false, "unreserved"}, + {"COMPONENT", false, "unreserved"}, {"COMPRESSED", false, "unreserved"}, {"COMPRESSION", false, "unreserved"}, {"COMPRESSION_LEVEL", false, "unreserved"}, @@ -393,6 +397,7 @@ var Keywords = []KeywordsType{ {"EXPLORE", false, "unreserved"}, {"EXTENDED", false, "unreserved"}, {"FAILED_LOGIN_ATTEMPTS", false, "unreserved"}, + {"FAST", false, "unreserved"}, {"FAULTS", false, "unreserved"}, {"FIELDS", false, "unreserved"}, {"FILE", false, "unreserved"}, @@ -424,6 +429,7 @@ var Keywords = []KeywordsType{ {"INCREMENTAL", false, "unreserved"}, {"INDEXES", false, "unreserved"}, {"INSERT_METHOD", false, "unreserved"}, + {"INSTALL", false, "unreserved"}, {"INSTANCE", false, "unreserved"}, {"INVISIBLE", false, "unreserved"}, {"INVOKER", false, "unreserved"}, @@ -438,6 +444,7 @@ var Keywords = []KeywordsType{ {"LAST", false, "unreserved"}, {"LASTVAL", false, "unreserved"}, {"LAST_BACKUP", false, "unreserved"}, + {"LEAVES", false, "unreserved"}, {"LESS", false, "unreserved"}, {"LEVEL", false, "unreserved"}, {"LIST", false, "unreserved"}, @@ -508,8 +515,10 @@ var Keywords = []KeywordsType{ {"PASSWORD_LOCK_TIME", false, "unreserved"}, {"PAUSE", false, "unreserved"}, {"PERCENT", false, "unreserved"}, + {"PERSIST", false, "unreserved"}, {"PER_DB", false, "unreserved"}, {"PER_TABLE", false, "unreserved"}, + {"PLUGIN", false, "unreserved"}, {"PLUGINS", false, "unreserved"}, {"POINT", false, "unreserved"}, {"POLICY", false, "unreserved"}, @@ -549,6 +558,7 @@ var Keywords = []KeywordsType{ {"RESTORE", false, "unreserved"}, {"RESTORES", false, "unreserved"}, {"RESUME", false, "unreserved"}, + {"RETURNS", false, "unreserved"}, {"REUSE", false, "unreserved"}, {"REVERSE", false, "unreserved"}, {"ROLE", false, "unreserved"}, @@ -587,6 +597,7 @@ var Keywords = []KeywordsType{ {"SLOW", false, "unreserved"}, {"SNAPSHOT", false, "unreserved"}, {"SOME", false, "unreserved"}, + {"SONAME", false, "unreserved"}, {"SOURCE", false, "unreserved"}, {"SQL_BUFFER_RESULT", false, "unreserved"}, {"SQL_CACHE", false, "unreserved"}, @@ -611,6 +622,7 @@ var Keywords = []KeywordsType{ {"STATUS", false, "unreserved"}, {"STORAGE", false, "unreserved"}, {"STRICT_FORMAT", false, "unreserved"}, + {"STRING", false, "unreserved"}, {"SUBJECT", false, "unreserved"}, {"SUBPARTITION", false, "unreserved"}, {"SUBPARTITIONS", false, "unreserved"}, @@ -626,6 +638,7 @@ var Keywords = []KeywordsType{ {"TEMPTABLE", false, "unreserved"}, {"TEXT", false, "unreserved"}, {"THAN", false, "unreserved"}, + {"THREAD_PRIORITY", false, "unreserved"}, {"TIKV_IMPORTER", false, "unreserved"}, {"TIME", false, "unreserved"}, {"TIMEOUT", false, "unreserved"}, @@ -648,12 +661,16 @@ var Keywords = []KeywordsType{ {"UNCOMMITTED", false, "unreserved"}, {"UNDEFINED", false, "unreserved"}, {"UNICODE", false, "unreserved"}, + {"UNINSTALL", false, "unreserved"}, {"UNKNOWN", false, "unreserved"}, {"UNSET", false, "unreserved"}, + {"UPGRADE", false, "unreserved"}, {"USER", false, "unreserved"}, + {"USE_FRM", false, "unreserved"}, {"VALIDATION", false, "unreserved"}, {"VALUE", false, "unreserved"}, {"VARIABLES", false, "unreserved"}, + {"VCPU", false, "unreserved"}, {"VECTOR", false, "unreserved"}, {"VIEW", false, "unreserved"}, {"VISIBLE", false, "unreserved"}, diff --git a/parser/keywords_test.go b/parser/keywords_test.go index 2909de6..7c4b1ed 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(690, len(parser.Keywords)) { - t.Fatalf("got %v, want %v", len(parser.Keywords), 690) + if !reflect.DeepEqual(707, len(parser.Keywords)) { + t.Fatalf("got %v, want %v", len(parser.Keywords), 707) } reservedNr := 0 diff --git a/parser/misc.go b/parser/misc.go index 71b8c8f..421b6c6 100644 --- a/parser/misc.go +++ b/parser/misc.go @@ -166,6 +166,7 @@ var tokenMap = map[string]int{ "AFFINITY": affinity, "AFTER": after, "AGAINST": against, + "AGGREGATE": aggregate, "AGO": ago, "ALGORITHM": algorithm, "ALL": all, @@ -240,6 +241,7 @@ var tokenMap = map[string]int{ "CAUSAL": causal, "CHAIN": chain, "CHANGE": change, + "CHANGED": changed, "CHANNEL": channel, "CHAR": charType, "CHARACTER": character, @@ -252,6 +254,7 @@ var tokenMap = map[string]int{ "CLIENT": client, "CLIENT_ERRORS_SUMMARY": clientErrorsSummary, "CLOSE": close, + "CLONE": clone, "CLUSTER": cluster, "CLUSTERED": clustered, "CMSKETCH": cmSketch, @@ -267,6 +270,7 @@ var tokenMap = map[string]int{ "COMMIT": commit, "COMMITTED": committed, "COMPACT": compact, + "COMPONENT": component, "COMPRESS": compress, "COMPRESSED": compressed, "COMPRESSION": compression, @@ -402,6 +406,7 @@ var tokenMap = map[string]int{ "EXTERNAL": external, "EXTRACT": extract, "FALSE": falseKwd, + "FAST": fast, "FAULTS": faultsSym, "FETCH": fetch, "FIELDS": fields, @@ -467,6 +472,7 @@ var tokenMap = map[string]int{ "INOUT": inout, "INPLACE": inplace, "INSERT_METHOD": insertMethod, + "INSTALL": install, "INSERT": insert, "INSTANCE": instance, "INSTANT": instant, @@ -521,6 +527,7 @@ var tokenMap = map[string]int{ "LEARNER_CONSTRAINTS": learnerConstraints, "LEARNERS": learners, "LEAVE": leave, + "LEAVES": leaves, "LEFT": left, "LESS": less, "LEVEL": level, @@ -642,10 +649,12 @@ var tokenMap = map[string]int{ "PERCENT": percent, "PER_DB": per_db, "PER_TABLE": per_table, + "PERSIST": persist, "PESSIMISTIC": pessimistic, "PLACEMENT": placement, "PLAN": plan, "PLAN_CACHE": planCache, + "PLUGIN": plugin, "PLUGINS": plugins, "POINT": point, "POLICIES": policies, @@ -733,6 +742,7 @@ var tokenMap = map[string]int{ "RTREE": rtree, "HYPO": hypo, "RESUME": resume, + "RETURNS": returns, "RUN": run, "RUNNING": running, "S3": s3, @@ -777,6 +787,7 @@ var tokenMap = map[string]int{ "SMALLINT": smallIntType, "SNAPSHOT": snapshot, "SOME": some, + "SONAME": soname, "SOURCE": source, "SPATIAL": spatial, "SPEED": speed, @@ -831,6 +842,7 @@ var tokenMap = map[string]int{ "STRAIGHT_JOIN": straightJoin, "STRICT": strict, "STRICT_FORMAT": strictFormat, + "STRING": stringKwd, "STRONG": strong, "SUBDATE": subDate, "SUBJECT": subject, @@ -859,6 +871,7 @@ var tokenMap = map[string]int{ "TEXT": textType, "THAN": than, "THEN": then, + "THREAD_PRIORITY": threadPriority, "TIDB": tidb, "TIDB_CURRENT_TSO": tidbCurrentTSO, "TIDB_JSON": tidbJson, @@ -908,6 +921,7 @@ var tokenMap = map[string]int{ "UNCOMMITTED": uncommitted, "UNDEFINED": undefined, "UNICODE": unicodeSym, + "UNINSTALL": uninstall, "UNION": union, "UNIQUE": unique, "UNKNOWN": unknown, @@ -919,9 +933,11 @@ var tokenMap = map[string]int{ "UNTIL": until, "UNTIL_TS": untilTS, "UPDATE": update, + "UPGRADE": upgrade, "USAGE": usage, "USE": use, "USER": user, + "USE_FRM": useFrm, "USING": using, "UTC_DATE": utcDate, "UTC_TIME": utcTime, @@ -938,6 +954,7 @@ var tokenMap = map[string]int{ "VARIABLES": variables, "VARIANCE": varPop, "VARYING": varying, + "VCPU": vcpu, "VECTOR": vectorType, "VERBOSE": verboseType, "VOTER": voter, diff --git a/parser/parse_create_misc.go b/parser/parse_create_misc.go index 1085032..69bc145 100644 --- a/parser/parse_create_misc.go +++ b/parser/parse_create_misc.go @@ -22,6 +22,7 @@ package parser // the masking-policy clauses, ...) live in parse_alter.go. import ( + "strconv" "strings" "time" @@ -652,6 +653,9 @@ func (r *rdParser) isDirectResourceGroupOptionStart() bool { switch r.tok() { case ruRate, priority, burstable, queryLimit, background: return true + case tp, vcpu, threadPriority, enable, disable: + // The MySQL-syntax resource group options. + return true } return false } @@ -748,11 +752,70 @@ func (r *rdParser) parseDirectResourceGroupOption() *ast.ResourceGroupOption { list := r.parseResourceGroupBackgroundOptionList() r.expect(int(')')) return &ast.ResourceGroupOption{Tp: ast.ResourceGroupBackground, BackgroundOptions: list} + case tp: + // "TYPE" EqOpt ("SYSTEM" | "USER") — MySQL syntax, as are the + // remaining options (CREATE/ALTER RESOURCE GROUP per the MySQL + // 26.7 reference manual, §15.7.2). + r.advance() + r.parseEqOpt() + switch r.tok() { + case system: + r.advance() + return &ast.ResourceGroupOption{Tp: ast.ResourceGroupType, StrValue: "SYSTEM"} + case user: + r.advance() + return &ast.ResourceGroupOption{Tp: ast.ResourceGroupType, StrValue: "USER"} + } + r.syntaxError() + case vcpu: + // "VCPU" EqOpt VCPUSpecList + r.advance() + r.parseEqOpt() + return &ast.ResourceGroupOption{Tp: ast.ResourceGroupVCPU, StrValue: r.parseVCPUSpecList()} + case threadPriority: + // "THREAD_PRIORITY" EqOpt ['-'] NUM + r.advance() + r.parseEqOpt() + negative := r.accept(int('-')) + value := int64(getUint64FromNUM(r.expect(intLit).item)) + if negative { + value = -value + } + return &ast.ResourceGroupOption{Tp: ast.ResourceGroupThreadPriority, IntValue: value} + case enable: + // "ENABLE" + r.advance() + return &ast.ResourceGroupOption{Tp: ast.ResourceGroupEnable, BoolValue: true} + case disable: + // "DISABLE" ["FORCE"] + r.advance() + return &ast.ResourceGroupOption{Tp: ast.ResourceGroupEnable, Force: r.accept(force)} } r.syntaxError() return nil } +// parseVCPUSpecList implements VCPUSpecList, the value of the VCPU +// resource group option: NUM | NUM '-' NUM, comma-separated. The list is +// returned in canonical spelling (e.g. "0,2-3"). A comma continues the +// list only when a number follows, since a comma may also separate +// resource group options. +func (r *rdParser) parseVCPUSpecList() string { + var sb strings.Builder + for { + sb.WriteString(strconv.FormatUint(getUint64FromNUM(r.expect(intLit).item), 10)) + if r.accept(int('-')) { + sb.WriteByte('-') + sb.WriteString(strconv.FormatUint(getUint64FromNUM(r.expect(intLit).item), 10)) + } + if r.tok() != int(',') || r.la(1) != intLit { + return sb.String() + } + r.advance() + sb.WriteByte(',') + } +} + // parseResourceGroupRunawayOptionList implements // ResourceGroupRunawayOptionList, including its duplicate check. func (r *rdParser) parseResourceGroupRunawayOptionList() []*ast.ResourceGroupRunawayOption { diff --git a/parser/parse_create_table.go b/parser/parse_create_table.go index fe5ddc4..3b8364a 100644 --- a/parser/parse_create_table.go +++ b/parser/parse_create_table.go @@ -84,6 +84,10 @@ func (r *rdParser) parseCreateStmtFamily() ast.StmtNode { return r.parseCreateBindingStmt() 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() default: // No production continues here; the automaton shifts CREATE and // errors at the lookahead, so advance before reporting. diff --git a/parser/parse_mysql_admin.go b/parser/parse_mysql_admin.go new file mode 100644 index 0000000..4956678 --- /dev/null +++ b/parser/parse_mysql_admin.go @@ -0,0 +1,379 @@ +// 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 database administration statements (MySQL 26.7 §15.7): +// table maintenance (CHECK/CHECKSUM/REPAIR TABLE), INSTALL/UNINSTALL +// COMPONENT and PLUGIN, CREATE FUNCTION for loadable functions, CLONE, +// CACHE INDEX, LOAD INDEX INTO CACHE, and RESET PERSIST. 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 MySQL-syntax +// resource group options live with the other resource group productions +// in parse_create_misc.go.) + +import ( + "github.com/sqlc-dev/marino/ast" +) + +func init() { + rdRegister(check, (*rdParser).parseCheckTableStmt) + rdRegister(checksum, (*rdParser).parseChecksumTableStmt) + rdRegister(repair, (*rdParser).parseRepairTablesStmt) + rdRegister(install, (*rdParser).parseInstallStmt) + rdRegister(uninstall, (*rdParser).parseUninstallStmt) + rdRegister(clone, (*rdParser).parseCloneStmt) + rdRegister(cache, (*rdParser).parseCacheIndexStmt) + rdRegister(reset, (*rdParser).parseResetStmt) +} + +// parseCheckTableStmt implements CheckTableStmt: +// +// "CHECK" "TABLE" TableNameList CheckTableOptionListOpt +// CheckTableOption: "FOR" "UPGRADE" | "QUICK" | "FAST" | "MEDIUM" +// | "EXTENDED" | "CHANGED" +func (r *rdParser) parseCheckTableStmt() ast.StmtNode { + r.expect(check) + r.expect(tableKwd) + stmt := &ast.CheckTableStmt{Tables: r.parseTableNameList()} + for { + switch r.tok() { + case forKwd: + r.advance() + r.expect(upgrade) + stmt.Options = append(stmt.Options, ast.CheckTableForUpgrade) + case quick: + r.advance() + stmt.Options = append(stmt.Options, ast.CheckTableQuick) + case fast: + r.advance() + stmt.Options = append(stmt.Options, ast.CheckTableFast) + case medium: + r.advance() + stmt.Options = append(stmt.Options, ast.CheckTableMedium) + case extended: + r.advance() + stmt.Options = append(stmt.Options, ast.CheckTableExtended) + case changed: + r.advance() + stmt.Options = append(stmt.Options, ast.CheckTableChanged) + default: + return stmt + } + } +} + +// parseChecksumTableStmt implements ChecksumTableStmt: +// "CHECKSUM" "TABLE" TableNameList ["QUICK" | "EXTENDED"]. +func (r *rdParser) parseChecksumTableStmt() ast.StmtNode { + r.expect(checksum) + r.expect(tableKwd) + stmt := &ast.ChecksumTableStmt{Tables: r.parseTableNameList()} + switch r.tok() { + case quick: + r.advance() + stmt.Type = ast.ChecksumTypeQuick + case extended: + r.advance() + stmt.Type = ast.ChecksumTypeExtended + } + return stmt +} + +// parseRepairTablesStmt implements RepairTablesStmt: +// +// "REPAIR" NoWriteToBinLogAliasOpt "TABLE" TableNameList +// RepairTableOptionListOpt +// RepairTableOption: "QUICK" | "EXTENDED" | "USE_FRM" +// +// The options may come in any order and repeat, as in MySQL. +func (r *rdParser) parseRepairTablesStmt() ast.StmtNode { + r.expect(repair) + noWrite := false + if r.tok() == noWriteToBinLog || r.tok() == local { + noWrite = true + r.advance() + } + r.expect(tableKwd) + stmt := &ast.RepairTablesStmt{ + NoWriteToBinLog: noWrite, + Tables: r.parseTableNameList(), + } + for { + switch r.tok() { + case quick: + r.advance() + stmt.Quick = true + case extended: + r.advance() + stmt.Extended = true + case useFrm: + r.advance() + stmt.UseFrm = true + default: + return stmt + } + } +} + +// parseCreateLoadableFunctionStmt implements CreateLoadableFunctionStmt +// (the CREATE FUNCTION statement for loadable functions; stored +// functions do not parse): +// +// "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() + r.expect(returns) + var returnType ast.LoadableFunctionReturnType + switch r.tok() { + case stringKwd: + returnType = ast.LoadableFunctionReturnString + case intType, integerType: + returnType = ast.LoadableFunctionReturnInteger + case realType: + returnType = ast.LoadableFunctionReturnReal + case decimalType: + returnType = ast.LoadableFunctionReturnDecimal + default: + r.syntaxError() + } + r.advance() + r.expect(soname) + return &ast.CreateLoadableFunctionStmt{ + IfNotExists: ifNotExists, + Aggregate: aggregateOpt, + FunctionName: ast.NewCIStr(name), + ReturnType: returnType, + SoName: r.expect(stringLit).lit, + } +} + +// parseInstallStmt implements InstallComponentStmt and InstallPluginStmt: +// +// "INSTALL" "COMPONENT" ComponentNameList ["SET" VariableAssignmentList] +// "INSTALL" "PLUGIN" Identifier "SONAME" stringLit +func (r *rdParser) parseInstallStmt() ast.StmtNode { + r.expect(install) + switch r.tok() { + case component: + r.advance() + stmt := &ast.InstallComponentStmt{Components: r.parseComponentNameList()} + if r.accept(set) { + stmt.Variables = r.parseVariableAssignmentList() + } + return stmt + case plugin: + r.advance() + name := r.parseIdentifier() + r.expect(soname) + return &ast.InstallPluginStmt{ + PluginName: ast.NewCIStr(name), + SoName: r.expect(stringLit).lit, + } + } + r.syntaxError() + return nil +} + +// parseUninstallStmt implements UninstallComponentStmt and +// UninstallPluginStmt: +// +// "UNINSTALL" "COMPONENT" ComponentNameList +// "UNINSTALL" "PLUGIN" Identifier +func (r *rdParser) parseUninstallStmt() ast.StmtNode { + r.expect(uninstall) + switch r.tok() { + case component: + r.advance() + return &ast.UninstallComponentStmt{Components: r.parseComponentNameList()} + case plugin: + r.advance() + return &ast.UninstallPluginStmt{PluginName: ast.NewCIStr(r.parseIdentifier())} + } + r.syntaxError() + return nil +} + +// parseComponentNameList implements ComponentNameList: +// stringLit | ComponentNameList ',' stringLit. +func (r *rdParser) parseComponentNameList() []string { + list := []string{r.expect(stringLit).lit} + for r.accept(int(',')) { + list = append(list, r.expect(stringLit).lit) + } + return list +} + +// parseCloneStmt implements CloneStmt: +// +// "CLONE" "LOCAL" "DATA" "DIRECTORY" EqOpt stringLit +// "CLONE" "INSTANCE" "FROM" Username ':' NUM "IDENTIFIED" "BY" stringLit +// ["DATA" "DIRECTORY" EqOpt stringLit] ["REQUIRE" ["NO"] "SSL"] +func (r *rdParser) parseCloneStmt() ast.StmtNode { + r.expect(clone) + switch r.tok() { + case local: + r.advance() + r.expect(data) + r.expect(directory) + r.parseEqOpt() + return &ast.CloneStmt{ + Local: true, + DataDirectory: r.expect(stringLit).lit, + } + case instance: + r.advance() + r.expect(from) + stmt := &ast.CloneStmt{User: r.parseUsername()} + r.expect(int(':')) + stmt.Port = getUint64FromNUM(r.expect(intLit).item) + r.expect(identified) + r.expect(by) + stmt.Password = r.expect(stringLit).lit + if r.accept(data) { + r.expect(directory) + r.parseEqOpt() + stmt.DataDirectory = r.expect(stringLit).lit + } + if r.accept(require) { + if r.accept(no) { + stmt.SSL = ast.CloneSSLRequireNo + } else { + stmt.SSL = ast.CloneSSLRequire + } + r.expect(ssl) + } + return stmt + } + r.syntaxError() + return nil +} + +// parseCacheIndexStmt implements CacheIndexStmt: +// "CACHE" "INDEX" CacheTableIndexList "IN" KeyCacheName. +func (r *rdParser) parseCacheIndexStmt() ast.StmtNode { + r.expect(cache) + r.expect(index) + stmt := &ast.CacheIndexStmt{ + TableIndexes: r.parseCacheTableIndexList(false), + } + r.expect(in) + // KeyCacheName: Identifier | "DEFAULT" (the default key cache). + if r.tok() == defaultKwd { + stmt.KeyCacheName = ast.NewCIStr(r.cur().lit) + r.advance() + } else { + stmt.KeyCacheName = ast.NewCIStr(r.parseIdentifier()) + } + return stmt +} + +// parseLoadIndexStmt implements LoadIndexStmt: +// "LOAD" "INDEX" "INTO" "CACHE" CacheTableIndexList. +func (r *rdParser) parseLoadIndexStmt() ast.StmtNode { + r.expect(load) + r.expect(index) + r.expect(into) + r.expect(cache) + return &ast.LoadIndexStmt{TableIndexes: r.parseCacheTableIndexList(true)} +} + +// parseCacheTableIndexList implements CacheTableIndexList: +// CacheTableIndex | CacheTableIndexList ',' CacheTableIndex. +func (r *rdParser) parseCacheTableIndexList(allowIgnoreLeaves bool) []*ast.CacheTableIndex { + list := []*ast.CacheTableIndex{r.parseCacheTableIndex(allowIgnoreLeaves)} + for r.accept(int(',')) { + list = append(list, r.parseCacheTableIndex(allowIgnoreLeaves)) + } + return list +} + +// parseCacheTableIndex implements CacheTableIndex: +// +// TableName ["PARTITION" '(' ("ALL" | PartitionNameList) ')'] +// [("INDEX" | "KEY") '(' IndexNameList ')'] ["IGNORE" "LEAVES"] +// +// IGNORE LEAVES belongs to LOAD INDEX INTO CACHE only. An index name may +// also be "PRIMARY", naming the primary key. +func (r *rdParser) parseCacheTableIndex(allowIgnoreLeaves bool) *ast.CacheTableIndex { + ti := &ast.CacheTableIndex{Table: r.parseTableName()} + if r.tok() == partition { + r.advance() + r.expect(int('(')) + if r.tok() == all { + r.advance() + ti.AllPartitions = true + } else { + ti.Partitions = []ast.CIStr{ast.NewCIStr(r.parseIdentifier())} + for r.accept(int(',')) { + ti.Partitions = append(ti.Partitions, ast.NewCIStr(r.parseIdentifier())) + } + } + r.expect(int(')')) + } + if r.tok() == index || r.tok() == key { + r.advance() + r.expect(int('(')) + ti.Indexes = []ast.CIStr{r.parseCacheIndexName()} + for r.accept(int(',')) { + ti.Indexes = append(ti.Indexes, r.parseCacheIndexName()) + } + r.expect(int(')')) + } + if allowIgnoreLeaves && r.tok() == ignore { + r.advance() + r.expect(leaves) + ti.IgnoreLeaves = true + } + return ti +} + +// parseCacheIndexName implements the key_usage_element production: +// Identifier | "PRIMARY". +func (r *rdParser) parseCacheIndexName() ast.CIStr { + if r.tok() == primary { + name := ast.NewCIStr(r.cur().lit) + r.advance() + return name + } + return ast.NewCIStr(r.parseIdentifier()) +} + +// parseResetStmt implements the RESET statement family, of which only +// ResetPersistStmt is supported: +// "RESET" "PERSIST" [["IF" "EXISTS"] Identifier]. +func (r *rdParser) parseResetStmt() ast.StmtNode { + if r.la(1) != persist { + // The other RESET forms do not parse; fail at RESET the way an + // unregistered statement head does. + r.syntaxError() + } + r.expect(reset) + r.expect(persist) + stmt := &ast.ResetPersistStmt{} + switch { + case r.accept(ifKwd): + r.expect(exists) + stmt.IfExists = true + stmt.Variable = r.parseIdentifier() + case isIdentifierTok(r.tok()): + stmt.Variable = r.parseIdentifier() + } + return stmt +} diff --git a/parser/rd_parser.go b/parser/rd_parser.go index 27dc214..a983e1a 100644 --- a/parser/rd_parser.go +++ b/parser/rd_parser.go @@ -381,6 +381,8 @@ func (r *rdParser) parseStatement() ast.StmtNode { return r.parseLoadDataStmt() case stats: return r.parseLoadStatsStmt() + case index: + return r.parseLoadIndexStmt() } r.unsupported("LOAD statement") return nil diff --git a/parser/testdata/parser/mysql_admin/input.sql b/parser/testdata/parser/mysql_admin/input.sql new file mode 100644 index 0000000..7d5651a --- /dev/null +++ b/parser/testdata/parser/mysql_admin/input.sql @@ -0,0 +1,75 @@ +CREATE RESOURCE GROUP rg1 TYPE = USER VCPU = 0-3 +-- case +CREATE RESOURCE GROUP rg2 TYPE SYSTEM VCPU 0,2-3,7 THREAD_PRIORITY = -19 ENABLE +-- case +ALTER RESOURCE GROUP rg1 VCPU = 0-3 +-- case +ALTER RESOURCE GROUP rg1 THREAD_PRIORITY 5 DISABLE FORCE +-- case +CHECK TABLE t +-- case +CHECK TABLE t1, t2 FOR UPGRADE QUICK FAST MEDIUM EXTENDED CHANGED +-- case +CHECKSUM TABLE t +-- case +CHECKSUM TABLE t1, t2 QUICK +-- case +CHECKSUM TABLE t EXTENDED +-- case +REPAIR TABLE t +-- case +REPAIR NO_WRITE_TO_BINLOG TABLE t QUICK EXTENDED USE_FRM +-- case +REPAIR LOCAL TABLE t1, t2 QUICK +-- case +CREATE FUNCTION metaphon RETURNS STRING SONAME 'udf.so' +-- case +CREATE AGGREGATE FUNCTION IF NOT EXISTS myfunc RETURNS INTEGER SONAME 'udf.so' +-- case +CREATE FUNCTION f1 RETURNS INT SONAME 'lib.so' +-- case +CREATE FUNCTION f2 RETURNS REAL SONAME 'lib.so' +-- case +CREATE FUNCTION f3 RETURNS DECIMAL SONAME 'lib.so' +-- case +INSTALL COMPONENT 'file://component_validate_password' +-- case +INSTALL COMPONENT 'file://c1', 'file://c2' SET GLOBAL log_error_verbosity = 3, @a = 1 +-- case +INSTALL PLUGIN myplugin SONAME 'plugin.so' +-- case +UNINSTALL COMPONENT 'file://component_validate_password' +-- case +UNINSTALL COMPONENT 'file://c1', 'file://c2' +-- case +UNINSTALL PLUGIN myplugin +-- case +CLONE LOCAL DATA DIRECTORY = '/tmp/clone' +-- case +CLONE LOCAL DATA DIRECTORY '/tmp/clone' +-- case +CLONE INSTANCE FROM 'user'@'host':3306 IDENTIFIED BY 'password' +-- case +CLONE INSTANCE FROM 'u'@'h':3306 IDENTIFIED BY 'p' DATA DIRECTORY = '/d' REQUIRE SSL +-- case +CLONE INSTANCE FROM 'u'@'h':3306 IDENTIFIED BY 'p' REQUIRE NO SSL +-- case +CACHE INDEX t IN hot_cache +-- case +CACHE INDEX t1 INDEX (i1, i2), t2 KEY (i3) IN cold_cache +-- case +CACHE INDEX t PARTITION (p0, p1) IN default +-- case +CACHE INDEX t PARTITION (ALL) KEY (i1) IN c1 +-- case +LOAD INDEX INTO CACHE t +-- case +LOAD INDEX INTO CACHE t1 INDEX (i1) IGNORE LEAVES, t2 +-- case +LOAD INDEX INTO CACHE t PARTITION (p0) KEY (i1, PRIMARY) +-- case +RESET PERSIST +-- case +RESET PERSIST var1 +-- case +RESET PERSIST IF EXISTS var1 diff --git a/parser/testdata/parser/mysql_admin/output.sql b/parser/testdata/parser/mysql_admin/output.sql new file mode 100644 index 0000000..5cb7d6b --- /dev/null +++ b/parser/testdata/parser/mysql_admin/output.sql @@ -0,0 +1,75 @@ +CREATE RESOURCE GROUP `rg1` TYPE = USER, VCPU = 0-3 +-- case +CREATE RESOURCE GROUP `rg2` TYPE = SYSTEM, VCPU = 0,2-3,7, THREAD_PRIORITY = -19, ENABLE +-- case +ALTER RESOURCE GROUP `rg1` VCPU = 0-3 +-- case +ALTER RESOURCE GROUP `rg1` THREAD_PRIORITY = 5, DISABLE FORCE +-- case +CHECK TABLE `t` +-- case +CHECK TABLE `t1`, `t2` FOR UPGRADE QUICK FAST MEDIUM EXTENDED CHANGED +-- case +CHECKSUM TABLE `t` +-- case +CHECKSUM TABLE `t1`, `t2` QUICK +-- case +CHECKSUM TABLE `t` EXTENDED +-- case +REPAIR TABLE `t` +-- case +REPAIR NO_WRITE_TO_BINLOG TABLE `t` QUICK EXTENDED USE_FRM +-- case +REPAIR NO_WRITE_TO_BINLOG TABLE `t1`, `t2` QUICK +-- case +CREATE FUNCTION `metaphon` RETURNS STRING SONAME 'udf.so' +-- case +CREATE AGGREGATE FUNCTION IF NOT EXISTS `myfunc` RETURNS INTEGER SONAME 'udf.so' +-- case +CREATE FUNCTION `f1` RETURNS INTEGER SONAME 'lib.so' +-- case +CREATE FUNCTION `f2` RETURNS REAL SONAME 'lib.so' +-- case +CREATE FUNCTION `f3` RETURNS DECIMAL SONAME 'lib.so' +-- case +INSTALL COMPONENT 'file://component_validate_password' +-- case +INSTALL COMPONENT 'file://c1', 'file://c2' SET @@GLOBAL.`log_error_verbosity`=3, @`a`=1 +-- case +INSTALL PLUGIN `myplugin` SONAME 'plugin.so' +-- case +UNINSTALL COMPONENT 'file://component_validate_password' +-- case +UNINSTALL COMPONENT 'file://c1', 'file://c2' +-- case +UNINSTALL PLUGIN `myplugin` +-- case +CLONE LOCAL DATA DIRECTORY = '/tmp/clone' +-- case +CLONE LOCAL DATA DIRECTORY = '/tmp/clone' +-- case +CLONE INSTANCE FROM `user`@`host`:3306 IDENTIFIED BY 'password' +-- case +CLONE INSTANCE FROM `u`@`h`:3306 IDENTIFIED BY 'p' DATA DIRECTORY = '/d' REQUIRE SSL +-- case +CLONE INSTANCE FROM `u`@`h`:3306 IDENTIFIED BY 'p' REQUIRE NO SSL +-- case +CACHE INDEX `t` IN `hot_cache` +-- case +CACHE INDEX `t1` INDEX (`i1`, `i2`), `t2` INDEX (`i3`) IN `cold_cache` +-- case +CACHE INDEX `t` PARTITION (`p0`, `p1`) IN `default` +-- case +CACHE INDEX `t` PARTITION (ALL) INDEX (`i1`) IN `c1` +-- case +LOAD INDEX INTO CACHE `t` +-- case +LOAD INDEX INTO CACHE `t1` INDEX (`i1`) IGNORE LEAVES, `t2` +-- case +LOAD INDEX INTO CACHE `t` PARTITION (`p0`) INDEX (`i1`, `PRIMARY`) +-- case +RESET PERSIST +-- case +RESET PERSIST `var1` +-- case +RESET PERSIST IF EXISTS `var1` diff --git a/parser/testdata/parser/mysql_unsupported_admin/input.sql b/parser/testdata/parser/mysql_unsupported_admin/input.sql deleted file mode 100644 index 175e967..0000000 --- a/parser/testdata/parser/mysql_unsupported_admin/input.sql +++ /dev/null @@ -1,29 +0,0 @@ -CREATE RESOURCE GROUP rg1 TYPE = USER VCPU = 0-3 --- case -ALTER RESOURCE GROUP rg1 VCPU = 0-3 --- case -CHECK TABLE t --- case -CHECKSUM TABLE t --- case -REPAIR TABLE t --- case -CREATE FUNCTION metaphon RETURNS STRING SONAME 'udf.so' --- case -INSTALL COMPONENT 'file://component_validate_password' --- case -INSTALL PLUGIN myplugin SONAME 'plugin.so' --- case -UNINSTALL COMPONENT 'file://component_validate_password' --- case -UNINSTALL PLUGIN myplugin --- case -CLONE LOCAL DATA DIRECTORY = '/tmp/clone' --- case -CLONE INSTANCE FROM 'user'@'host':3306 IDENTIFIED BY 'password' --- case -CACHE INDEX t IN hot_cache --- case -LOAD INDEX INTO CACHE t --- case -RESET PERSIST diff --git a/parser/testdata/parser/mysql_unsupported_admin/output.sql b/parser/testdata/parser/mysql_unsupported_admin/output.sql deleted file mode 100644 index b22145e..0000000 --- a/parser/testdata/parser/mysql_unsupported_admin/output.sql +++ /dev/null @@ -1,29 +0,0 @@ --- error: line 1 column 30 near "TYPE = USER VCPU = 0-3" --- case --- error: line 1 column 29 near "VCPU = 0-3" --- case --- error: line 1 column 5 near "CHECK TABLE t" --- case --- error: line 1 column 8 near "CHECKSUM TABLE t" --- case --- error: line 1 column 6 near "REPAIR TABLE t" --- case --- error: line 1 column 15 near "FUNCTION metaphon RETURNS STRING SONAME 'udf.so'" --- case --- error: line 1 column 7 near "INSTALL COMPONENT 'file://component_validate_password'" --- case --- error: line 1 column 7 near "INSTALL PLUGIN myplugin SONAME 'plugin.so'" --- case --- error: line 1 column 9 near "UNINSTALL COMPONENT 'file://component_validate_password'" --- case --- error: line 1 column 9 near "UNINSTALL PLUGIN myplugin" --- case --- error: line 1 column 5 near "CLONE LOCAL DATA DIRECTORY = '/tmp/clone'" --- case --- error: line 1 column 5 near "CLONE INSTANCE FROM 'user'@'host':3306 IDENTIFIED BY 'password'" --- case --- error: line 1 column 5 near "CACHE INDEX t IN hot_cache" --- case --- error: line 1 column 4 near "LOAD INDEX INTO CACHE t" --- case --- error: line 1 column 5 near "RESET PERSIST" diff --git a/parser/testdata/parser/mysql_unsupported_ddl/output.sql b/parser/testdata/parser/mysql_unsupported_ddl/output.sql index ffab5ab..c4a8ce5 100644 --- a/parser/testdata/parser/mysql_unsupported_ddl/output.sql +++ b/parser/testdata/parser/mysql_unsupported_ddl/output.sql @@ -20,7 +20,7 @@ -- case -- error: line 1 column 12 near "EVENT e ON SCHEDULE AT CURRENT_TIMESTAMP DO SELECT 1" -- case --- error: line 1 column 15 near "FUNCTION f(x INT) RETURNS INT DETERMINISTIC RETURN x + 1" +-- 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 diff --git a/parser/token_kinds.go b/parser/token_kinds.go index 741dd10..0579d4a 100644 --- a/parser/token_kinds.go +++ b/parser/token_kinds.go @@ -63,6 +63,7 @@ const ( affinity = 57600 after = 57601 against = 57602 + aggregate = 58258 ago = 57603 algorithm = 57604 all = 57364 @@ -165,6 +166,7 @@ const ( causal = 57638 chain = 57639 change = 57380 + changed = 58259 channel = 58252 charType = 57381 character = 57382 @@ -178,6 +180,7 @@ const ( client = 57646 clientErrorsSummary = 57647 close = 57648 + clone = 58260 cluster = 57649 clustered = 57650 cmSketch = 58158 @@ -193,6 +196,7 @@ const ( commit = 57657 committed = 57658 compact = 57659 + component = 58261 compress = 58007 compressed = 57660 compression = 57661 @@ -328,6 +332,7 @@ const ( extract = 58023 failedLoginAttempts = 57721 falseKwd = 57425 + fast = 58262 faultsSym = 57722 fetch = 57426 fields = 57723 @@ -409,6 +414,7 @@ const ( insert = 57453 insertMethod = 57752 insertValues = 58228 + install = 58263 instance = 57753 instant = 58034 int1Type = 57455 @@ -465,6 +471,7 @@ const ( learnerConstraints = 58045 learners = 58046 leave = 57475 + leaves = 58264 left = 57476 less = 57767 level = 57768 @@ -626,12 +633,14 @@ const ( per_table = 57837 percent = 57835 percentRank = 57517 + persist = 58265 pessimistic = 58176 pipes = 57359 pipesAsOr = 57838 placement = 58057 plan = 58059 planCache = 58058 + plugin = 58266 plugins = 57839 point = 57840 policies = 58177 @@ -705,6 +714,7 @@ const ( restores = 57876 restrict = 57533 resume = 57877 + returns = 58267 reuse = 57878 reverse = 57879 revoke = 57534 @@ -767,6 +777,7 @@ const ( smallIntType = 57544 snapshot = 57914 some = 57915 + soname = 58268 source = 57916 spatial = 57545 speed = 58077 @@ -824,6 +835,7 @@ const ( straightJoin = 57556 strict = 58086 strictFormat = 57938 + stringKwd = 58269 stringLit = 57353 strong = 58087 subDate = 58088 @@ -853,6 +865,7 @@ const ( textType = 57952 than = 57953 then = 57560 + threadPriority = 58270 tiFlash = 58198 tidb = 58197 tidbCurrentTSO = 57561 @@ -907,6 +920,7 @@ const ( undefined = 57974 underscoreCS = 57352 unicodeSym = 57975 + uninstall = 58271 union = 57569 unique = 57570 unknown = 57976 @@ -917,8 +931,10 @@ const ( until = 57573 untilTS = 58115 update = 57574 + upgrade = 58272 usage = 57575 use = 57576 + useFrm = 58273 user = 57978 using = 57577 utcDate = 57578 @@ -936,6 +952,7 @@ const ( variables = 57981 variance = 58117 varying = 57585 + vcpu = 58274 vectorType = 57982 verboseType = 58120 view = 57983