diff --git a/parser/ast/dml.go b/parser/ast/dml.go new file mode 100644 index 0000000000000..1da250553bac0 --- /dev/null +++ b/parser/ast/dml.go @@ -0,0 +1,3516 @@ +// Copyright 2015 PingCAP, Inc. +// +// 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/pingcap/errors" + "github.com/pingcap/tidb/parser/auth" + "github.com/pingcap/tidb/parser/format" + "github.com/pingcap/tidb/parser/model" + "github.com/pingcap/tidb/parser/mysql" +) + +var ( + _ DMLNode = &DeleteStmt{} + _ DMLNode = &InsertStmt{} + _ DMLNode = &SetOprStmt{} + _ DMLNode = &UpdateStmt{} + _ DMLNode = &SelectStmt{} + _ DMLNode = &CallStmt{} + _ DMLNode = &ShowStmt{} + _ DMLNode = &LoadDataStmt{} + _ DMLNode = &SplitRegionStmt{} + + _ Node = &Assignment{} + _ Node = &ByItem{} + _ Node = &FieldList{} + _ Node = &GroupByClause{} + _ Node = &HavingClause{} + _ Node = &AsOfClause{} + _ Node = &Join{} + _ Node = &Limit{} + _ Node = &OnCondition{} + _ Node = &OrderByClause{} + _ Node = &SelectField{} + _ Node = &TableName{} + _ Node = &TableRefsClause{} + _ Node = &TableSource{} + _ Node = &SetOprSelectList{} + _ Node = &WildCardField{} + _ Node = &WindowSpec{} + _ Node = &PartitionByClause{} + _ Node = &FrameClause{} + _ Node = &FrameBound{} +) + +// JoinType is join type, including cross/left/right/full. +type JoinType int + +const ( + // CrossJoin is cross join type. + CrossJoin JoinType = iota + 1 + // LeftJoin is left Join type. + LeftJoin + // RightJoin is right Join type. + RightJoin +) + +// Join represents table join. +type Join struct { + node + + // Left table can be TableSource or JoinNode. + Left ResultSetNode + // Right table can be TableSource or JoinNode or nil. + Right ResultSetNode + // Tp represents join type. + Tp JoinType + // On represents join on condition. + On *OnCondition + // Using represents join using clause. + Using []*ColumnName + // NaturalJoin represents join is natural join. + NaturalJoin bool + // StraightJoin represents a straight join. + StraightJoin bool + ExplicitParens bool +} + +func (*Join) resultSet() {} + +// NewCrossJoin builds a cross join without `on` or `using` clause. +// If the right child is a join tree, we need to handle it differently to make the precedence get right. +// Here is the example: t1 join t2 join t3 +// JOIN ON t2.a = t3.a +// t1 join / \ +// t2 t3 +// (left) (right) +// +// We can not build it directly to: +// JOIN +// / \ +// t1 JOIN ON t2.a = t3.a +// / \ +// t2 t3 +// The precedence would be t1 join (t2 join t3 on t2.a=t3.a), not (t1 join t2) join t3 on t2.a=t3.a +// We need to find the left-most child of the right child, and build a cross join of the left-hand side +// of the left child(t1), and the right hand side with the original left-most child of the right child(t2). +// JOIN t2.a = t3.a +// / \ +// JOIN t3 +// / \ +// t1 t2 +// Besides, if the right handle side join tree's join type is right join and has explicit parentheses, we need to rewrite it to left join. +// So t1 join t2 right join t3 would be rewrite to t1 join t3 left join t2. +// If not, t1 join (t2 right join t3) would be (t1 join t2) right join t3. After rewrite the right join to left join. +// We get (t1 join t3) left join t2, the semantics is correct. +func NewCrossJoin(left, right ResultSetNode) (n *Join) { + rj, ok := right.(*Join) + // don't break the explicit parents name scope constraints. + // this kind of join re-order can be done in logical-phase after the name resolution. + if !ok || rj.Right == nil || rj.ExplicitParens { + return &Join{Left: left, Right: right, Tp: CrossJoin} + } + + var leftMostLeafFatherOfRight = rj + // Walk down the right hand side. + for { + if leftMostLeafFatherOfRight.Tp == RightJoin && leftMostLeafFatherOfRight.ExplicitParens { + // Rewrite right join to left join. + tmpChild := leftMostLeafFatherOfRight.Right + leftMostLeafFatherOfRight.Right = leftMostLeafFatherOfRight.Left + leftMostLeafFatherOfRight.Left = tmpChild + leftMostLeafFatherOfRight.Tp = LeftJoin + } + leftChild := leftMostLeafFatherOfRight.Left + if join, ok := leftChild.(*Join); ok && join.Right != nil { + leftMostLeafFatherOfRight = join + } else { + break + } + } + + newCrossJoin := &Join{Left: left, Right: leftMostLeafFatherOfRight.Left, Tp: CrossJoin} + leftMostLeafFatherOfRight.Left = newCrossJoin + return rj +} + +// Restore implements Node interface. +func (n *Join) Restore(ctx *format.RestoreCtx) error { + useCommaJoin := false + _, leftIsJoin := n.Left.(*Join) + + if leftIsJoin && n.Left.(*Join).Right == nil { + if ts, ok := n.Left.(*Join).Left.(*TableSource); ok { + switch ts.Source.(type) { + case *SelectStmt, *SetOprStmt: + useCommaJoin = true + } + } + } + + if leftIsJoin && !useCommaJoin { + ctx.WritePlain("(") + } + if err := n.Left.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Join.Left") + } + if leftIsJoin && !useCommaJoin { + ctx.WritePlain(")") + } + if n.Right == nil { + return nil + } + if n.NaturalJoin { + ctx.WriteKeyWord(" NATURAL") + } + switch n.Tp { + case LeftJoin: + ctx.WriteKeyWord(" LEFT") + case RightJoin: + ctx.WriteKeyWord(" RIGHT") + } + if n.StraightJoin { + ctx.WriteKeyWord(" STRAIGHT_JOIN ") + } else { + if useCommaJoin { + ctx.WritePlain(", ") + } else { + ctx.WriteKeyWord(" JOIN ") + } + } + _, rightIsJoin := n.Right.(*Join) + if rightIsJoin { + ctx.WritePlain("(") + } + if err := n.Right.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Join.Right") + } + if rightIsJoin { + ctx.WritePlain(")") + } + + if n.On != nil { + ctx.WritePlain(" ") + if err := n.On.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Join.On") + } + } + if len(n.Using) != 0 { + ctx.WriteKeyWord(" USING ") + ctx.WritePlain("(") + for i, v := range n.Using { + if i != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Join.Using") + } + } + ctx.WritePlain(")") + } + + return nil +} + +// Accept implements Node Accept interface. +func (n *Join) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*Join) + node, ok := n.Left.Accept(v) + if !ok { + return n, false + } + n.Left = node.(ResultSetNode) + if n.Right != nil { + node, ok = n.Right.Accept(v) + if !ok { + return n, false + } + n.Right = node.(ResultSetNode) + } + if n.On != nil { + node, ok = n.On.Accept(v) + if !ok { + return n, false + } + n.On = node.(*OnCondition) + } + for i, col := range n.Using { + node, ok = col.Accept(v) + if !ok { + return n, false + } + n.Using[i] = node.(*ColumnName) + } + return v.Leave(n) +} + +// TableName represents a table name. +type TableName struct { + node + + Schema model.CIStr + Name model.CIStr + + DBInfo *model.DBInfo + TableInfo *model.TableInfo + + IndexHints []*IndexHint + PartitionNames []model.CIStr + TableSample *TableSample + // AS OF is used to see the data as it was at a specific point in time. + AsOf *AsOfClause +} + +func (*TableName) resultSet() {} + +// Restore implements Node interface. +func (n *TableName) restoreName(ctx *format.RestoreCtx) { + if n.Schema.String() != "" { + ctx.WriteName(n.Schema.String()) + ctx.WritePlain(".") + } else if ctx.DefaultDB != "" { + // Try CTE, for a CTE table name, we shouldn't write the database name. + ok := false + for _, name := range ctx.CTENames { + if strings.EqualFold(name, n.Name.String()) { + ok = true + break + } + } + if !ok { + ctx.WriteName(ctx.DefaultDB) + ctx.WritePlain(".") + } + } + ctx.WriteName(n.Name.String()) +} + +func (n *TableName) restorePartitions(ctx *format.RestoreCtx) { + if len(n.PartitionNames) > 0 { + ctx.WriteKeyWord(" PARTITION") + ctx.WritePlain("(") + for i, v := range n.PartitionNames { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(v.String()) + } + ctx.WritePlain(")") + } +} + +func (n *TableName) restoreIndexHints(ctx *format.RestoreCtx) error { + for _, value := range n.IndexHints { + ctx.WritePlain(" ") + if err := value.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while splicing IndexHints") + } + } + return nil +} + +func (n *TableName) Restore(ctx *format.RestoreCtx) error { + n.restoreName(ctx) + n.restorePartitions(ctx) + if err := n.restoreIndexHints(ctx); err != nil { + return err + } + if n.AsOf != nil { + ctx.WritePlain(" ") + if err := n.AsOf.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while splicing TableName.Asof") + } + } + if n.TableSample != nil { + ctx.WritePlain(" ") + if err := n.TableSample.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while splicing TableName.TableSample") + } + } + return nil +} + +// IndexHintType is the type for index hint use, ignore or force. +type IndexHintType int + +// IndexHintUseType values. +const ( + HintUse IndexHintType = iota + 1 + HintIgnore + HintForce +) + +// IndexHintScope is the type for index hint for join, order by or group by. +type IndexHintScope int + +// Index hint scopes. +const ( + HintForScan IndexHintScope = iota + 1 + HintForJoin + HintForOrderBy + HintForGroupBy +) + +// IndexHint represents a hint for optimizer to use/ignore/force for join/order by/group by. +type IndexHint struct { + IndexNames []model.CIStr + HintType IndexHintType + HintScope IndexHintScope +} + +// IndexHint Restore (The const field uses switch to facilitate understanding) +func (n *IndexHint) Restore(ctx *format.RestoreCtx) error { + indexHintType := "" + switch n.HintType { + case HintUse: + indexHintType = "USE INDEX" + case HintIgnore: + indexHintType = "IGNORE INDEX" + case HintForce: + indexHintType = "FORCE INDEX" + default: // Prevent accidents + return errors.New("IndexHintType has an error while matching") + } + + indexHintScope := "" + switch n.HintScope { + case HintForScan: + indexHintScope = "" + case HintForJoin: + indexHintScope = " FOR JOIN" + case HintForOrderBy: + indexHintScope = " FOR ORDER BY" + case HintForGroupBy: + indexHintScope = " FOR GROUP BY" + default: // Prevent accidents + return errors.New("IndexHintScope has an error while matching") + } + ctx.WriteKeyWord(indexHintType) + ctx.WriteKeyWord(indexHintScope) + ctx.WritePlain(" (") + for i, value := range n.IndexNames { + if i > 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(value.O) + } + ctx.WritePlain(")") + + return nil +} + +// Accept implements Node Accept interface. +func (n *TableName) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*TableName) + if n.TableSample != nil { + newTs, ok := n.TableSample.Accept(v) + if !ok { + return n, false + } + n.TableSample = newTs.(*TableSample) + } + if n.AsOf != nil { + newNode, skipChildren := n.AsOf.Accept(v) + if skipChildren { + return v.Leave(n) + } + n.AsOf = newNode.(*AsOfClause) + } + return v.Leave(n) +} + +// DeleteTableList is the tablelist used in delete statement multi-table mode. +type DeleteTableList struct { + node + Tables []*TableName +} + +// Restore implements Node interface. +func (n *DeleteTableList) Restore(ctx *format.RestoreCtx) error { + for i, t := range n.Tables { + if i != 0 { + ctx.WritePlain(",") + } + if err := t.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore DeleteTableList.Tables[%v]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *DeleteTableList) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*DeleteTableList) + if n != nil { + for i, t := range n.Tables { + node, ok := t.Accept(v) + if !ok { + return n, false + } + n.Tables[i] = node.(*TableName) + } + } + return v.Leave(n) +} + +// OnCondition represents JOIN on condition. +type OnCondition struct { + node + + Expr ExprNode +} + +// Restore implements Node interface. +func (n *OnCondition) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ON ") + if err := n.Expr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore OnCondition.Expr") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *OnCondition) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*OnCondition) + node, ok := n.Expr.Accept(v) + if !ok { + return n, false + } + n.Expr = node.(ExprNode) + return v.Leave(n) +} + +// TableSource represents table source with a name. +type TableSource struct { + node + + // Source is the source of the data, can be a TableName, + // a SelectStmt, a SetOprStmt, or a JoinNode. + Source ResultSetNode + + // AsName is the alias name of the table source. + AsName model.CIStr +} + +func (*TableSource) resultSet() {} + +// Restore implements Node interface. +func (n *TableSource) Restore(ctx *format.RestoreCtx) error { + needParen := false + switch n.Source.(type) { + case *SelectStmt, *SetOprStmt: + needParen = true + } + + if tn, tnCase := n.Source.(*TableName); tnCase { + if needParen { + ctx.WritePlain("(") + } + + tn.restoreName(ctx) + tn.restorePartitions(ctx) + + if asName := n.AsName.String(); asName != "" { + ctx.WriteKeyWord(" AS ") + ctx.WriteName(asName) + } + + if tn.AsOf != nil { + ctx.WritePlain(" ") + if err := tn.AsOf.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore TableSource.AsOf") + } + + } + if err := tn.restoreIndexHints(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore TableSource.Source.(*TableName).IndexHints") + } + if tn.TableSample != nil { + ctx.WritePlain(" ") + if err := tn.TableSample.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while splicing TableName.TableSample") + } + } + + if needParen { + ctx.WritePlain(")") + } + } else { + if needParen { + ctx.WritePlain("(") + } + if err := n.Source.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore TableSource.Source") + } + if needParen { + ctx.WritePlain(")") + } + if asName := n.AsName.String(); asName != "" { + ctx.WriteKeyWord(" AS ") + ctx.WriteName(asName) + } + } + + return nil +} + +// Accept implements Node Accept interface. +func (n *TableSource) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*TableSource) + node, ok := n.Source.Accept(v) + if !ok { + return n, false + } + n.Source = node.(ResultSetNode) + return v.Leave(n) +} + +// SelectLockType is the lock type for SelectStmt. +type SelectLockType int + +// Select lock types. +const ( + SelectLockNone SelectLockType = iota + SelectLockForUpdate + SelectLockForShare + SelectLockForUpdateNoWait + SelectLockForUpdateWaitN + SelectLockForShareNoWait + SelectLockForUpdateSkipLocked + SelectLockForShareSkipLocked +) + +type SelectLockInfo struct { + LockType SelectLockType + WaitSec uint64 + Tables []*TableName +} + +// String implements fmt.Stringer. +func (n SelectLockType) String() string { + switch n { + case SelectLockNone: + return "none" + case SelectLockForUpdate: + return "for update" + case SelectLockForShare: + return "for share" + case SelectLockForUpdateNoWait: + return "for update nowait" + case SelectLockForUpdateWaitN: + return "for update wait" + case SelectLockForShareNoWait: + return "for share nowait" + case SelectLockForUpdateSkipLocked: + return "for update skip locked" + case SelectLockForShareSkipLocked: + return "for share skip locked" + } + return "unsupported select lock type" +} + +// WildCardField is a special type of select field content. +type WildCardField struct { + node + + Table model.CIStr + Schema model.CIStr +} + +// Restore implements Node interface. +func (n *WildCardField) Restore(ctx *format.RestoreCtx) error { + if schema := n.Schema.String(); schema != "" { + ctx.WriteName(schema) + ctx.WritePlain(".") + } + if table := n.Table.String(); table != "" { + ctx.WriteName(table) + ctx.WritePlain(".") + } + ctx.WritePlain("*") + return nil +} + +// Accept implements Node Accept interface. +func (n *WildCardField) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*WildCardField) + return v.Leave(n) +} + +// SelectField represents fields in select statement. +// There are two type of select field: wildcard +// and expression with optional alias name. +type SelectField struct { + node + + // Offset is used to get original text. + Offset int + // WildCard is not nil, Expr will be nil. + WildCard *WildCardField + // Expr is not nil, WildCard will be nil. + Expr ExprNode + // AsName is alias name for Expr. + AsName model.CIStr + // Auxiliary stands for if this field is auxiliary. + // When we add a Field into SelectField list which is used for having/orderby clause but the field is not in select clause, + // we should set its Auxiliary to true. Then the TrimExec will trim the field. + Auxiliary bool + AuxiliaryColInAgg bool + AuxiliaryColInOrderBy bool +} + +// Restore implements Node interface. +func (n *SelectField) Restore(ctx *format.RestoreCtx) error { + if n.WildCard != nil { + if err := n.WildCard.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectField.WildCard") + } + } + if n.Expr != nil { + if err := n.Expr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectField.Expr") + } + } + if asName := n.AsName.String(); asName != "" { + ctx.WriteKeyWord(" AS ") + ctx.WriteName(asName) + } + return nil +} + +// Accept implements Node Accept interface. +func (n *SelectField) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*SelectField) + if n.Expr != nil { + node, ok := n.Expr.Accept(v) + if !ok { + return n, false + } + n.Expr = node.(ExprNode) + } + return v.Leave(n) +} + +// FieldList represents field list in select statement. +type FieldList struct { + node + + Fields []*SelectField +} + +// Restore implements Node interface. +func (n *FieldList) Restore(ctx *format.RestoreCtx) error { + for i, v := range n.Fields { + if i != 0 { + ctx.WritePlain(", ") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore FieldList.Fields[%d]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *FieldList) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*FieldList) + for i, val := range n.Fields { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Fields[i] = node.(*SelectField) + } + return v.Leave(n) +} + +// TableRefsClause represents table references clause in dml statement. +type TableRefsClause struct { + node + + TableRefs *Join +} + +// Restore implements Node interface. +func (n *TableRefsClause) Restore(ctx *format.RestoreCtx) error { + if err := n.TableRefs.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore TableRefsClause.TableRefs") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *TableRefsClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*TableRefsClause) + node, ok := n.TableRefs.Accept(v) + if !ok { + return n, false + } + n.TableRefs = node.(*Join) + return v.Leave(n) +} + +// ByItem represents an item in order by or group by. +type ByItem struct { + node + + Expr ExprNode + Desc bool + NullOrder bool +} + +// Restore implements Node interface. +func (n *ByItem) Restore(ctx *format.RestoreCtx) error { + if err := n.Expr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ByItem.Expr") + } + if n.Desc { + ctx.WriteKeyWord(" DESC") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ByItem) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ByItem) + node, ok := n.Expr.Accept(v) + if !ok { + return n, false + } + n.Expr = node.(ExprNode) + return v.Leave(n) +} + +// GroupByClause represents group by clause. +type GroupByClause struct { + node + Items []*ByItem +} + +// Restore implements Node interface. +func (n *GroupByClause) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("GROUP BY ") + for i, v := range n.Items { + if i != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore GroupByClause.Items[%d]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *GroupByClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*GroupByClause) + for i, val := range n.Items { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Items[i] = node.(*ByItem) + } + return v.Leave(n) +} + +// HavingClause represents having clause. +type HavingClause struct { + node + Expr ExprNode +} + +// Restore implements Node interface. +func (n *HavingClause) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("HAVING ") + if err := n.Expr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore HavingClause.Expr") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *HavingClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*HavingClause) + node, ok := n.Expr.Accept(v) + if !ok { + return n, false + } + n.Expr = node.(ExprNode) + return v.Leave(n) +} + +// OrderByClause represents order by clause. +type OrderByClause struct { + node + Items []*ByItem + ForUnion bool +} + +// Restore implements Node interface. +func (n *OrderByClause) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("ORDER BY ") + for i, item := range n.Items { + if i != 0 { + ctx.WritePlain(",") + } + if err := item.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore OrderByClause.Items[%d]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *OrderByClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*OrderByClause) + for i, val := range n.Items { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Items[i] = node.(*ByItem) + } + return v.Leave(n) +} + +type SampleMethodType int8 + +const ( + SampleMethodTypeNone SampleMethodType = iota + SampleMethodTypeSystem + SampleMethodTypeBernoulli + SampleMethodTypeTiDBRegion +) + +type SampleClauseUnitType int8 + +const ( + SampleClauseUnitTypeDefault SampleClauseUnitType = iota + SampleClauseUnitTypeRow + SampleClauseUnitTypePercent +) + +type TableSample struct { + node + SampleMethod SampleMethodType + Expr ExprNode + SampleClauseUnit SampleClauseUnitType + RepeatableSeed ExprNode +} + +func (s *TableSample) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("TABLESAMPLE ") + switch s.SampleMethod { + case SampleMethodTypeBernoulli: + ctx.WriteKeyWord("BERNOULLI ") + case SampleMethodTypeSystem: + ctx.WriteKeyWord("SYSTEM ") + case SampleMethodTypeTiDBRegion: + ctx.WriteKeyWord("REGION ") + } + ctx.WritePlain("(") + if s.Expr != nil { + if err := s.Expr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore TableSample.Expr") + } + } + switch s.SampleClauseUnit { + case SampleClauseUnitTypeDefault: + case SampleClauseUnitTypePercent: + ctx.WriteKeyWord(" PERCENT") + case SampleClauseUnitTypeRow: + ctx.WriteKeyWord(" ROWS") + + } + ctx.WritePlain(")") + if s.RepeatableSeed != nil { + ctx.WriteKeyWord(" REPEATABLE") + ctx.WritePlain("(") + if err := s.RepeatableSeed.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore TableSample.Expr") + } + ctx.WritePlain(")") + } + return nil +} + +func (s *TableSample) Accept(v Visitor) (node Node, ok bool) { + newNode, skipChildren := v.Enter(s) + if skipChildren { + return v.Leave(newNode) + } + s = newNode.(*TableSample) + if s.Expr != nil { + node, ok = s.Expr.Accept(v) + if !ok { + return s, false + } + s.Expr = node.(ExprNode) + } + if s.RepeatableSeed != nil { + node, ok = s.RepeatableSeed.Accept(v) + if !ok { + return s, false + } + s.RepeatableSeed = node.(ExprNode) + } + return v.Leave(s) +} + +type SelectStmtKind uint8 + +const ( + SelectStmtKindSelect SelectStmtKind = iota + SelectStmtKindTable + SelectStmtKindValues +) + +func (s *SelectStmtKind) String() string { + switch *s { + case SelectStmtKindSelect: + return "SELECT" + case SelectStmtKindTable: + return "TABLE" + case SelectStmtKindValues: + return "VALUES" + } + return "" +} + +type CommonTableExpression struct { + node + + Name model.CIStr + Query *SubqueryExpr + ColNameList []model.CIStr +} + +type WithClause struct { + node + + IsRecursive bool + CTEs []*CommonTableExpression +} + +// SelectStmt represents the select query node. +// See https://dev.mysql.com/doc/refman/5.7/en/select.html +type SelectStmt struct { + dmlNode + + // SelectStmtOpts wraps around select hints and switches. + *SelectStmtOpts + // Distinct represents whether the select has distinct option. + Distinct bool + // From is the from clause of the query. + From *TableRefsClause + // Where is the where clause in select statement. + Where ExprNode + // Fields is the select expression list. + Fields *FieldList + // GroupBy is the group by expression list. + GroupBy *GroupByClause + // Having is the having condition. + Having *HavingClause + // WindowSpecs is the window specification list. + WindowSpecs []WindowSpec + // OrderBy is the ordering expression list. + OrderBy *OrderByClause + // Limit is the limit clause. + Limit *Limit + // LockInfo is the lock type + LockInfo *SelectLockInfo + // TableHints represents the table level Optimizer Hint for join type + TableHints []*TableOptimizerHint + // IsInBraces indicates whether it's a stmt in brace. + IsInBraces bool + // WithBeforeBraces indicates whether stmt's with clause is before the brace. + // It's used to distinguish (with xxx select xxx) and with xxx (select xxx) + WithBeforeBraces bool + // QueryBlockOffset indicates the order of this SelectStmt if counted from left to right in the sql text. + QueryBlockOffset int + // SelectIntoOpt is the select-into option. + SelectIntoOpt *SelectIntoOption + // AfterSetOperator indicates the SelectStmt after which type of set operator + AfterSetOperator *SetOprType + // Kind refer to three kind of statement: SelectStmt, TableStmt and ValuesStmt + Kind SelectStmtKind + // Lists is filled only when Kind == SelectStmtKindValues + Lists []*RowExpr + With *WithClause + // AsViewSchema indicates if this stmt provides the schema for the view. It is only used when creating the view + AsViewSchema bool +} + +func (*SelectStmt) resultSet() {} + +func (n *WithClause) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("WITH ") + if n.IsRecursive { + ctx.WriteKeyWord("RECURSIVE ") + } + for i, cte := range n.CTEs { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(cte.Name.String()) + if n.IsRecursive { + // If the CTE is recursive, we should make it visible for the CTE's query. + // Otherwise, we should put it to stack after building the CTE's query. + ctx.CTENames = append(ctx.CTENames, cte.Name.L) + } + if len(cte.ColNameList) > 0 { + ctx.WritePlain(" (") + for j, name := range cte.ColNameList { + if j != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(name.String()) + } + ctx.WritePlain(")") + } + ctx.WriteKeyWord(" AS ") + err := cte.Query.Restore(ctx) + if err != nil { + return err + } + if !n.IsRecursive { + ctx.CTENames = append(ctx.CTENames, cte.Name.L) + } + } + ctx.WritePlain(" ") + return nil +} + +func (n *WithClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + + for _, cte := range n.CTEs { + node, ok := cte.Query.Accept(v) + if !ok { + return n, false + } + cte.Query = node.(*SubqueryExpr) + } + return v.Leave(n) +} + +// Restore implements Node interface. +func (n *SelectStmt) Restore(ctx *format.RestoreCtx) error { + if n.WithBeforeBraces { + l := len(ctx.CTENames) + defer func() { + ctx.CTENames = ctx.CTENames[:l] + }() + err := n.With.Restore(ctx) + if err != nil { + return err + } + } + if n.IsInBraces { + ctx.WritePlain("(") + defer func() { + ctx.WritePlain(")") + }() + } + if !n.WithBeforeBraces && n.With != nil { + err := n.With.Restore(ctx) + if err != nil { + return err + } + } + + ctx.WriteKeyWord(n.Kind.String()) + ctx.WritePlain(" ") + switch n.Kind { + case SelectStmtKindSelect: + if n.SelectStmtOpts.Priority > 0 { + ctx.WriteKeyWord(mysql.Priority2Str[n.SelectStmtOpts.Priority]) + ctx.WritePlain(" ") + } + + if n.SelectStmtOpts.SQLSmallResult { + ctx.WriteKeyWord("SQL_SMALL_RESULT ") + } + + if n.SelectStmtOpts.SQLBigResult { + ctx.WriteKeyWord("SQL_BIG_RESULT ") + } + + if n.SelectStmtOpts.SQLBufferResult { + ctx.WriteKeyWord("SQL_BUFFER_RESULT ") + } + + if !n.SelectStmtOpts.SQLCache { + ctx.WriteKeyWord("SQL_NO_CACHE ") + } + + if n.SelectStmtOpts.CalcFoundRows { + ctx.WriteKeyWord("SQL_CALC_FOUND_ROWS ") + } + + if n.TableHints != nil && len(n.TableHints) != 0 { + ctx.WritePlain("/*+ ") + for i, tableHint := range n.TableHints { + if i != 0 { + ctx.WritePlain(" ") + } + if err := tableHint.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore SelectStmt.TableHints[%d]", i) + } + } + ctx.WritePlain("*/ ") + } + + if n.Distinct { + ctx.WriteKeyWord("DISTINCT ") + } else if n.SelectStmtOpts.ExplicitAll { + ctx.WriteKeyWord("ALL ") + } + if n.SelectStmtOpts.StraightJoin { + ctx.WriteKeyWord("STRAIGHT_JOIN ") + } + if n.Fields != nil { + for i, field := range n.Fields.Fields { + if i != 0 { + ctx.WritePlain(",") + } + if err := field.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore SelectStmt.Fields[%d]", i) + } + } + } + + if n.From != nil { + ctx.WriteKeyWord(" FROM ") + if err := n.From.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.From") + } + } + + if n.From == nil && n.Where != nil { + ctx.WriteKeyWord(" FROM DUAL") + } + + if n.Where != nil { + ctx.WriteKeyWord(" WHERE ") + if err := n.Where.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.Where") + } + } + + if n.GroupBy != nil { + ctx.WritePlain(" ") + if err := n.GroupBy.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.GroupBy") + } + } + + if n.Having != nil { + ctx.WritePlain(" ") + if err := n.Having.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.Having") + } + } + + if n.WindowSpecs != nil { + ctx.WriteKeyWord(" WINDOW ") + for i, windowsSpec := range n.WindowSpecs { + if i != 0 { + ctx.WritePlain(",") + } + if err := windowsSpec.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore SelectStmt.WindowSpec[%d]", i) + } + } + } + case SelectStmtKindTable: + if err := n.From.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.From") + } + case SelectStmtKindValues: + for i, v := range n.Lists { + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore SelectStmt.Lists[%d]", i) + } + if i != len(n.Lists)-1 { + ctx.WritePlain(", ") + } + } + } + + if n.OrderBy != nil { + ctx.WritePlain(" ") + if err := n.OrderBy.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.OrderBy") + } + } + + if n.Limit != nil { + ctx.WritePlain(" ") + if err := n.Limit.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.Limit") + } + } + + if n.LockInfo != nil { + ctx.WritePlain(" ") + switch n.LockInfo.LockType { + case SelectLockNone: + case SelectLockForUpdateNoWait: + ctx.WriteKeyWord("for update") + if len(n.LockInfo.Tables) != 0 { + ctx.WriteKeyWord(" OF ") + restoreTables(ctx, n.LockInfo.Tables) + } + ctx.WriteKeyWord(" nowait") + case SelectLockForUpdateWaitN: + ctx.WriteKeyWord("for update") + if len(n.LockInfo.Tables) != 0 { + ctx.WriteKeyWord(" OF ") + restoreTables(ctx, n.LockInfo.Tables) + } + ctx.WriteKeyWord(" wait") + ctx.WritePlainf(" %d", n.LockInfo.WaitSec) + case SelectLockForShareNoWait: + ctx.WriteKeyWord("for share") + if len(n.LockInfo.Tables) != 0 { + ctx.WriteKeyWord(" OF ") + restoreTables(ctx, n.LockInfo.Tables) + } + ctx.WriteKeyWord(" nowait") + case SelectLockForUpdateSkipLocked: + ctx.WriteKeyWord("for update") + if len(n.LockInfo.Tables) != 0 { + ctx.WriteKeyWord(" OF ") + restoreTables(ctx, n.LockInfo.Tables) + } + ctx.WriteKeyWord(" skip locked") + case SelectLockForShareSkipLocked: + ctx.WriteKeyWord("for share") + if len(n.LockInfo.Tables) != 0 { + ctx.WriteKeyWord(" OF ") + restoreTables(ctx, n.LockInfo.Tables) + } + ctx.WriteKeyWord(" skip locked") + default: + ctx.WriteKeyWord(n.LockInfo.LockType.String()) + if len(n.LockInfo.Tables) != 0 { + ctx.WriteKeyWord(" OF ") + restoreTables(ctx, n.LockInfo.Tables) + } + } + } + + if n.SelectIntoOpt != nil { + ctx.WritePlain(" ") + if err := n.SelectIntoOpt.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.SelectIntoOpt") + } + } + return nil +} + +func restoreTables(ctx *format.RestoreCtx, ts []*TableName) error { + for i, v := range ts { + if err := v.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectStmt.LockInfo") + } + if i != len(ts)-1 { + ctx.WritePlain(", ") + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *SelectStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + + n = newNode.(*SelectStmt) + + if n.With != nil { + node, ok := n.With.Accept(v) + if !ok { + return n, false + } + n.With = node.(*WithClause) + } + + if n.TableHints != nil && len(n.TableHints) != 0 { + newHints := make([]*TableOptimizerHint, len(n.TableHints)) + for i, hint := range n.TableHints { + node, ok := hint.Accept(v) + if !ok { + return n, false + } + newHints[i] = node.(*TableOptimizerHint) + } + n.TableHints = newHints + } + + if n.Fields != nil { + node, ok := n.Fields.Accept(v) + if !ok { + return n, false + } + n.Fields = node.(*FieldList) + } + + if n.From != nil { + node, ok := n.From.Accept(v) + if !ok { + return n, false + } + n.From = node.(*TableRefsClause) + } + + if n.Where != nil { + node, ok := n.Where.Accept(v) + if !ok { + return n, false + } + n.Where = node.(ExprNode) + } + + if n.GroupBy != nil { + node, ok := n.GroupBy.Accept(v) + if !ok { + return n, false + } + n.GroupBy = node.(*GroupByClause) + } + + if n.Having != nil { + node, ok := n.Having.Accept(v) + if !ok { + return n, false + } + n.Having = node.(*HavingClause) + } + + for i, list := range n.Lists { + node, ok := list.Accept(v) + if !ok { + return n, false + } + n.Lists[i] = node.(*RowExpr) + } + + for i, spec := range n.WindowSpecs { + node, ok := spec.Accept(v) + if !ok { + return n, false + } + n.WindowSpecs[i] = *node.(*WindowSpec) + } + + if n.OrderBy != nil { + node, ok := n.OrderBy.Accept(v) + if !ok { + return n, false + } + n.OrderBy = node.(*OrderByClause) + } + + if n.Limit != nil { + node, ok := n.Limit.Accept(v) + if !ok { + return n, false + } + n.Limit = node.(*Limit) + } + + if n.LockInfo != nil { + for i, t := range n.LockInfo.Tables { + node, ok := t.Accept(v) + if !ok { + return n, false + } + n.LockInfo.Tables[i] = node.(*TableName) + } + } + + return v.Leave(n) +} + +// SetOprSelectList represents the SelectStmt/TableStmt/ValuesStmt list in a union statement. +type SetOprSelectList struct { + node + + With *WithClause + AfterSetOperator *SetOprType + Selects []Node +} + +// Restore implements Node interface. +func (n *SetOprSelectList) Restore(ctx *format.RestoreCtx) error { + if n.With != nil { + l := len(ctx.CTENames) + defer func() { + ctx.CTENames = ctx.CTENames[:l] + }() + if err := n.With.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SetOprSelectList.With") + } + } + for i, stmt := range n.Selects { + switch selectStmt := stmt.(type) { + case *SelectStmt: + if i != 0 { + ctx.WriteKeyWord(" " + selectStmt.AfterSetOperator.String() + " ") + } + if err := selectStmt.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SetOprSelectList.SelectStmt") + } + case *SetOprSelectList: + if i != 0 { + ctx.WriteKeyWord(" " + selectStmt.AfterSetOperator.String() + " ") + } + ctx.WritePlain("(") + err := selectStmt.Restore(ctx) + if err != nil { + return err + } + ctx.WritePlain(")") + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *SetOprSelectList) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*SetOprSelectList) + if n.With != nil { + node, ok := n.With.Accept(v) + if !ok { + return n, false + } + n.With = node.(*WithClause) + } + for i, sel := range n.Selects { + node, ok := sel.Accept(v) + if !ok { + return n, false + } + n.Selects[i] = node + } + return v.Leave(n) +} + +type SetOprType uint8 + +const ( + Union SetOprType = iota + UnionAll + Except + ExceptAll + Intersect + IntersectAll +) + +func (s *SetOprType) String() string { + switch *s { + case Union: + return "UNION" + case UnionAll: + return "UNION ALL" + case Except: + return "EXCEPT" + case ExceptAll: + return "EXCEPT ALL" + case Intersect: + return "INTERSECT" + case IntersectAll: + return "INTERSECT ALL" + } + return "" +} + +// SetOprStmt represents "union/except/intersect statement" +// See https://dev.mysql.com/doc/refman/5.7/en/union.html +// See https://mariadb.com/kb/en/intersect/ +// See https://mariadb.com/kb/en/except/ +type SetOprStmt struct { + dmlNode + + IsInBraces bool + SelectList *SetOprSelectList + OrderBy *OrderByClause + Limit *Limit + With *WithClause +} + +func (*SetOprStmt) resultSet() {} + +// Restore implements Node interface. +func (n *SetOprStmt) Restore(ctx *format.RestoreCtx) error { + if n.With != nil { + l := len(ctx.CTENames) + defer func() { + ctx.CTENames = ctx.CTENames[:l] + }() + if err := n.With.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore UnionStmt.With") + } + } + if n.IsInBraces { + ctx.WritePlain("(") + defer func() { + ctx.WritePlain(")") + }() + } + + if err := n.SelectList.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SetOprStmt.SelectList") + } + + if n.OrderBy != nil { + ctx.WritePlain(" ") + if err := n.OrderBy.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SetOprStmt.OrderBy") + } + } + + if n.Limit != nil { + ctx.WritePlain(" ") + if err := n.Limit.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SetOprStmt.Limit") + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *SetOprStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + if n.With != nil { + node, ok := n.With.Accept(v) + if !ok { + return n, false + } + n.With = node.(*WithClause) + } + if n.SelectList != nil { + node, ok := n.SelectList.Accept(v) + if !ok { + return n, false + } + n.SelectList = node.(*SetOprSelectList) + } + if n.OrderBy != nil { + node, ok := n.OrderBy.Accept(v) + if !ok { + return n, false + } + n.OrderBy = node.(*OrderByClause) + } + if n.Limit != nil { + node, ok := n.Limit.Accept(v) + if !ok { + return n, false + } + n.Limit = node.(*Limit) + } + return v.Leave(n) +} + +// Assignment is the expression for assignment, like a = 1. +type Assignment struct { + node + // Column is the column name to be assigned. + Column *ColumnName + // Expr is the expression assigning to ColName. + Expr ExprNode +} + +// Restore implements Node interface. +func (n *Assignment) Restore(ctx *format.RestoreCtx) error { + if err := n.Column.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Assignment.Column") + } + ctx.WritePlain("=") + if err := n.Expr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Assignment.Expr") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *Assignment) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*Assignment) + node, ok := n.Column.Accept(v) + if !ok { + return n, false + } + n.Column = node.(*ColumnName) + node, ok = n.Expr.Accept(v) + if !ok { + return n, false + } + n.Expr = node.(ExprNode) + return v.Leave(n) +} + +type ColumnNameOrUserVar struct { + node + ColumnName *ColumnName + UserVar *VariableExpr +} + +func (n *ColumnNameOrUserVar) Restore(ctx *format.RestoreCtx) error { + if n.ColumnName != nil { + if err := n.ColumnName.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ColumnNameOrUserVar.ColumnName") + } + } + if n.UserVar != nil { + if err := n.UserVar.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ColumnNameOrUserVar.UserVar") + } + } + return nil +} + +func (n *ColumnNameOrUserVar) Accept(v Visitor) (node Node, ok bool) { + newNode, skipChild := v.Enter(n) + if skipChild { + return v.Leave(newNode) + } + n = newNode.(*ColumnNameOrUserVar) + if n.ColumnName != nil { + node, ok = n.ColumnName.Accept(v) + if !ok { + return node, false + } + n.ColumnName = node.(*ColumnName) + } + if n.UserVar != nil { + node, ok = n.UserVar.Accept(v) + if !ok { + return node, false + } + n.UserVar = node.(*VariableExpr) + } + return v.Leave(n) +} + +// LoadDataStmt is a statement to load data from a specified file, then insert this rows into an existing table. +// See https://dev.mysql.com/doc/refman/5.7/en/load-data.html +type LoadDataStmt struct { + dmlNode + + IsLocal bool + Path string + OnDuplicate OnDuplicateKeyHandlingType + Table *TableName + Columns []*ColumnName + FieldsInfo *FieldsClause + LinesInfo *LinesClause + IgnoreLines uint64 + ColumnAssignments []*Assignment + + ColumnsAndUserVars []*ColumnNameOrUserVar +} + +// Restore implements Node interface. +func (n *LoadDataStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("LOAD DATA ") + if n.IsLocal { + 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 errors.Annotate(err, "An error occurred while restore LoadDataStmt.Table") + } + n.FieldsInfo.Restore(ctx) + n.LinesInfo.Restore(ctx) + if n.IgnoreLines != 0 { + ctx.WriteKeyWord(" IGNORE ") + ctx.WritePlainf("%d", n.IgnoreLines) + ctx.WriteKeyWord(" LINES") + } + 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 errors.Annotate(err, "An error occurred while restore LoadDataStmt.ColumnsAndUserVars") + } + } + ctx.WritePlain(")") + } + + if n.ColumnAssignments != nil { + ctx.WriteKeyWord(" SET") + for i, assign := range n.ColumnAssignments { + if i != 0 { + ctx.WritePlain(",") + } + ctx.WritePlain(" ") + if err := assign.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore LoadDataStmt.ColumnAssignments") + } + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *LoadDataStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*LoadDataStmt) + if n.Table != nil { + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + } + for i, val := range n.Columns { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Columns[i] = node.(*ColumnName) + } + + for i, assignment := range n.ColumnAssignments { + node, ok := assignment.Accept(v) + if !ok { + return n, false + } + n.ColumnAssignments[i] = node.(*Assignment) + } + for i, cuVars := range n.ColumnsAndUserVars { + node, ok := cuVars.Accept(v) + if !ok { + return n, false + } + n.ColumnsAndUserVars[i] = node.(*ColumnNameOrUserVar) + } + return v.Leave(n) +} + +const ( + Terminated = iota + Enclosed + Escaped +) + +type FieldItem struct { + Type int + Value string + OptEnclosed bool +} + +// FieldsClause represents fields references clause in load data statement. +type FieldsClause struct { + Terminated string + Enclosed byte + Escaped byte + OptEnclosed bool +} + +// Restore for FieldsClause +func (n *FieldsClause) Restore(ctx *format.RestoreCtx) error { + if n.Terminated != "\t" || n.Escaped != '\\' { + ctx.WriteKeyWord(" FIELDS") + if n.Terminated != "\t" { + ctx.WriteKeyWord(" TERMINATED BY ") + ctx.WriteString(n.Terminated) + } + if n.Enclosed != 0 { + if n.OptEnclosed { + ctx.WriteKeyWord(" OPTIONALLY") + } + ctx.WriteKeyWord(" ENCLOSED BY ") + ctx.WriteString(string(n.Enclosed)) + } + if n.Escaped != '\\' { + ctx.WriteKeyWord(" ESCAPED BY ") + if n.Escaped == 0 { + ctx.WritePlain("''") + } else { + ctx.WriteString(string(n.Escaped)) + } + } + } + return nil +} + +// LinesClause represents lines references clause in load data statement. +type LinesClause struct { + Starting string + Terminated string +} + +// Restore for LinesClause +func (n *LinesClause) Restore(ctx *format.RestoreCtx) error { + if n.Starting != "" || n.Terminated != "\n" { + ctx.WriteKeyWord(" LINES") + if n.Starting != "" { + ctx.WriteKeyWord(" STARTING BY ") + ctx.WriteString(n.Starting) + } + if n.Terminated != "\n" { + ctx.WriteKeyWord(" TERMINATED BY ") + ctx.WriteString(n.Terminated) + } + } + return nil +} + +// CallStmt represents a call procedure query node. +// See https://dev.mysql.com/doc/refman/5.7/en/call.html +type CallStmt struct { + dmlNode + + Procedure *FuncCallExpr +} + +// Restore implements Node interface. +func (n *CallStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("CALL ") + + if err := n.Procedure.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore CallStmt.Procedure") + } + + return nil +} + +// Accept implements Node Accept interface. +func (n *CallStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + + n = newNode.(*CallStmt) + + if n.Procedure != nil { + node, ok := n.Procedure.Accept(v) + if !ok { + return n, false + } + + n.Procedure = node.(*FuncCallExpr) + } + + return v.Leave(n) +} + +// InsertStmt is a statement to insert new rows into an existing table. +// See https://dev.mysql.com/doc/refman/5.7/en/insert.html +type InsertStmt struct { + dmlNode + + IsReplace bool + IgnoreErr bool + Table *TableRefsClause + Columns []*ColumnName + Lists [][]ExprNode + Setlist []*Assignment + Priority mysql.PriorityEnum + OnDuplicate []*Assignment + Select ResultSetNode + // TableHints represents the table level Optimizer Hint for join type. + TableHints []*TableOptimizerHint + PartitionNames []model.CIStr +} + +// Restore implements Node interface. +func (n *InsertStmt) Restore(ctx *format.RestoreCtx) error { + if n.IsReplace { + ctx.WriteKeyWord("REPLACE ") + } else { + ctx.WriteKeyWord("INSERT ") + } + + if n.TableHints != nil && len(n.TableHints) != 0 { + ctx.WritePlain("/*+ ") + for i, tableHint := range n.TableHints { + if i != 0 { + ctx.WritePlain(" ") + } + if err := tableHint.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore InsertStmt.TableHints[%d]", i) + } + } + ctx.WritePlain("*/ ") + } + + if err := n.Priority.Restore(ctx); err != nil { + return errors.Trace(err) + } + if n.Priority != mysql.NoPriority { + ctx.WritePlain(" ") + } + if n.IgnoreErr { + ctx.WriteKeyWord("IGNORE ") + } + ctx.WriteKeyWord("INTO ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore InsertStmt.Table") + } + if len(n.PartitionNames) != 0 { + ctx.WriteKeyWord(" PARTITION") + ctx.WritePlain("(") + for i := 0; i < len(n.PartitionNames); i++ { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(n.PartitionNames[i].String()) + } + ctx.WritePlain(")") + } + if n.Columns != nil { + ctx.WritePlain(" (") + for i, v := range n.Columns { + if i != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore InsertStmt.Columns[%d]", i) + } + } + ctx.WritePlain(")") + } + if n.Lists != nil { + ctx.WriteKeyWord(" VALUES ") + for i, row := range n.Lists { + if i != 0 { + ctx.WritePlain(",") + } + ctx.WritePlain("(") + for j, v := range row { + if j != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore InsertStmt.Lists[%d][%d]", i, j) + } + } + ctx.WritePlain(")") + } + } + if n.Select != nil { + ctx.WritePlain(" ") + switch v := n.Select.(type) { + case *SelectStmt, *SetOprStmt: + if err := v.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore InsertStmt.Select") + } + default: + return errors.Errorf("Incorrect type for InsertStmt.Select: %T", v) + } + } + if n.Setlist != nil { + ctx.WriteKeyWord(" SET ") + for i, v := range n.Setlist { + if i != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore InsertStmt.Setlist[%d]", i) + } + } + } + if n.OnDuplicate != nil { + ctx.WriteKeyWord(" ON DUPLICATE KEY UPDATE ") + for i, v := range n.OnDuplicate { + if i != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore InsertStmt.OnDuplicate[%d]", i) + } + } + } + + return nil +} + +// Accept implements Node Accept interface. +func (n *InsertStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + + n = newNode.(*InsertStmt) + if n.Select != nil { + node, ok := n.Select.Accept(v) + if !ok { + return n, false + } + n.Select = node.(ResultSetNode) + } + + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableRefsClause) + + for i, val := range n.Columns { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Columns[i] = node.(*ColumnName) + } + for i, list := range n.Lists { + for j, val := range list { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Lists[i][j] = node.(ExprNode) + } + } + for i, val := range n.Setlist { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Setlist[i] = node.(*Assignment) + } + for i, val := range n.OnDuplicate { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.OnDuplicate[i] = node.(*Assignment) + } + return v.Leave(n) +} + +// DeleteStmt is a statement to delete rows from table. +// See https://dev.mysql.com/doc/refman/5.7/en/delete.html +type DeleteStmt struct { + dmlNode + + // TableRefs is used in both single table and multiple table delete statement. + TableRefs *TableRefsClause + // Tables is only used in multiple table delete statement. + Tables *DeleteTableList + Where ExprNode + Order *OrderByClause + Limit *Limit + Priority mysql.PriorityEnum + IgnoreErr bool + Quick bool + IsMultiTable bool + BeforeFrom bool + // TableHints represents the table level Optimizer Hint for join type. + TableHints []*TableOptimizerHint + With *WithClause +} + +// Restore implements Node interface. +func (n *DeleteStmt) Restore(ctx *format.RestoreCtx) error { + if n.With != nil { + l := len(ctx.CTENames) + defer func() { + ctx.CTENames = ctx.CTENames[:l] + }() + err := n.With.Restore(ctx) + if err != nil { + return err + } + } + + ctx.WriteKeyWord("DELETE ") + + if n.TableHints != nil && len(n.TableHints) != 0 { + ctx.WritePlain("/*+ ") + for i, tableHint := range n.TableHints { + if i != 0 { + ctx.WritePlain(" ") + } + if err := tableHint.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore UpdateStmt.TableHints[%d]", i) + } + } + ctx.WritePlain("*/ ") + } + + if err := n.Priority.Restore(ctx); err != nil { + return errors.Trace(err) + } + if n.Priority != mysql.NoPriority { + ctx.WritePlain(" ") + } + if n.Quick { + ctx.WriteKeyWord("QUICK ") + } + if n.IgnoreErr { + ctx.WriteKeyWord("IGNORE ") + } + + if n.IsMultiTable { // Multiple-Table Syntax + if n.BeforeFrom { + if err := n.Tables.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.Tables") + } + + ctx.WriteKeyWord(" FROM ") + if err := n.TableRefs.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.TableRefs") + } + } else { + ctx.WriteKeyWord("FROM ") + if err := n.Tables.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.Tables") + } + + ctx.WriteKeyWord(" USING ") + if err := n.TableRefs.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.TableRefs") + } + } + } else { // Single-Table Syntax + ctx.WriteKeyWord("FROM ") + + if err := n.TableRefs.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.TableRefs") + } + } + + if n.Where != nil { + ctx.WriteKeyWord(" WHERE ") + if err := n.Where.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.Where") + } + } + + if n.Order != nil { + ctx.WritePlain(" ") + if err := n.Order.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.Order") + } + } + + if n.Limit != nil { + ctx.WritePlain(" ") + if err := n.Limit.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore DeleteStmt.Limit") + } + } + + return nil +} + +// Accept implements Node Accept interface. +func (n *DeleteStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + + n = newNode.(*DeleteStmt) + if n.With != nil { + node, ok := n.With.Accept(v) + if !ok { + return n, false + } + n.With = node.(*WithClause) + } + node, ok := n.TableRefs.Accept(v) + if !ok { + return n, false + } + n.TableRefs = node.(*TableRefsClause) + + if n.Tables != nil { + node, ok = n.Tables.Accept(v) + if !ok { + return n, false + } + n.Tables = node.(*DeleteTableList) + } + + if n.Where != nil { + node, ok = n.Where.Accept(v) + if !ok { + return n, false + } + n.Where = node.(ExprNode) + } + if n.Order != nil { + node, ok = n.Order.Accept(v) + if !ok { + return n, false + } + n.Order = node.(*OrderByClause) + } + if n.Limit != nil { + node, ok = n.Limit.Accept(v) + if !ok { + return n, false + } + n.Limit = node.(*Limit) + } + return v.Leave(n) +} + +// UpdateStmt is a statement to update columns of existing rows in tables with new values. +// See https://dev.mysql.com/doc/refman/5.7/en/update.html +type UpdateStmt struct { + dmlNode + + TableRefs *TableRefsClause + List []*Assignment + Where ExprNode + Order *OrderByClause + Limit *Limit + Priority mysql.PriorityEnum + IgnoreErr bool + MultipleTable bool + TableHints []*TableOptimizerHint + With *WithClause +} + +// Restore implements Node interface. +func (n *UpdateStmt) Restore(ctx *format.RestoreCtx) error { + if n.With != nil { + l := len(ctx.CTENames) + defer func() { + ctx.CTENames = ctx.CTENames[:l] + }() + err := n.With.Restore(ctx) + if err != nil { + return err + } + } + + ctx.WriteKeyWord("UPDATE ") + + if n.TableHints != nil && len(n.TableHints) != 0 { + ctx.WritePlain("/*+ ") + for i, tableHint := range n.TableHints { + if i != 0 { + ctx.WritePlain(" ") + } + if err := tableHint.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore UpdateStmt.TableHints[%d]", i) + } + } + ctx.WritePlain("*/ ") + } + + if err := n.Priority.Restore(ctx); err != nil { + return errors.Trace(err) + } + if n.Priority != mysql.NoPriority { + ctx.WritePlain(" ") + } + if n.IgnoreErr { + ctx.WriteKeyWord("IGNORE ") + } + + if err := n.TableRefs.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occur while restore UpdateStmt.TableRefs") + } + + ctx.WriteKeyWord(" SET ") + for i, assignment := range n.List { + if i != 0 { + ctx.WritePlain(", ") + } + + if err := assignment.Column.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occur while restore UpdateStmt.List[%d].Column", i) + } + + ctx.WritePlain("=") + + if err := assignment.Expr.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occur while restore UpdateStmt.List[%d].Expr", i) + } + } + + if n.Where != nil { + ctx.WriteKeyWord(" WHERE ") + if err := n.Where.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occur while restore UpdateStmt.Where") + } + } + + if n.Order != nil { + ctx.WritePlain(" ") + if err := n.Order.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occur while restore UpdateStmt.Order") + } + } + + if n.Limit != nil { + ctx.WritePlain(" ") + if err := n.Limit.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occur while restore UpdateStmt.Limit") + } + } + + return nil +} + +// Accept implements Node Accept interface. +func (n *UpdateStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*UpdateStmt) + if n.With != nil { + node, ok := n.With.Accept(v) + if !ok { + return n, false + } + n.With = node.(*WithClause) + } + node, ok := n.TableRefs.Accept(v) + if !ok { + return n, false + } + n.TableRefs = node.(*TableRefsClause) + for i, val := range n.List { + node, ok = val.Accept(v) + if !ok { + return n, false + } + n.List[i] = node.(*Assignment) + } + if n.Where != nil { + node, ok = n.Where.Accept(v) + if !ok { + return n, false + } + n.Where = node.(ExprNode) + } + if n.Order != nil { + node, ok = n.Order.Accept(v) + if !ok { + return n, false + } + n.Order = node.(*OrderByClause) + } + if n.Limit != nil { + node, ok = n.Limit.Accept(v) + if !ok { + return n, false + } + n.Limit = node.(*Limit) + } + return v.Leave(n) +} + +// Limit is the limit clause. +type Limit struct { + node + + Count ExprNode + Offset ExprNode +} + +// Restore implements Node interface. +func (n *Limit) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("LIMIT ") + if n.Offset != nil { + if err := n.Offset.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Limit.Offset") + } + ctx.WritePlain(",") + } + if err := n.Count.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore Limit.Count") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *Limit) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + if n.Count != nil { + node, ok := n.Count.Accept(v) + if !ok { + return n, false + } + n.Count = node.(ExprNode) + } + if n.Offset != nil { + node, ok := n.Offset.Accept(v) + if !ok { + return n, false + } + n.Offset = node.(ExprNode) + } + + n = newNode.(*Limit) + return v.Leave(n) +} + +// ShowStmtType is the type for SHOW statement. +type ShowStmtType int + +// Show statement types. +const ( + ShowNone = iota + ShowEngines + ShowDatabases + ShowTables + ShowTableStatus + ShowColumns + ShowWarnings + ShowCharset + ShowVariables + ShowStatus + ShowCollation + ShowCreateTable + ShowCreateView + ShowCreateUser + ShowCreateSequence + ShowCreatePlacementPolicy + ShowGrants + ShowTriggers + ShowProcedureStatus + ShowIndex + ShowProcessList + ShowCreateDatabase + ShowConfig + ShowEvents + ShowStatsExtended + ShowStatsMeta + ShowStatsHistograms + ShowStatsTopN + ShowStatsBuckets + ShowStatsHealthy + ShowHistogramsInFlight + ShowColumnStatsUsage + ShowPlugins + ShowProfile + ShowProfiles + ShowMasterStatus + ShowPrivileges + ShowErrors + ShowBindings + ShowBindingCacheStatus + ShowPumpStatus + ShowDrainerStatus + ShowOpenTables + ShowAnalyzeStatus + ShowRegions + ShowBuiltins + ShowTableNextRowId + ShowBackups + ShowRestores + ShowImports + ShowCreateImport + ShowPlacement + ShowPlacementForDatabase + ShowPlacementForTable + ShowPlacementForPartition + ShowPlacementLabels +) + +const ( + ProfileTypeInvalid = iota + ProfileTypeCPU + ProfileTypeMemory + ProfileTypeBlockIo + ProfileTypeContextSwitch + ProfileTypePageFaults + ProfileTypeIpc + ProfileTypeSwaps + ProfileTypeSource + ProfileTypeAll +) + +// ShowStmt is a statement to provide information about databases, tables, columns and so on. +// See https://dev.mysql.com/doc/refman/5.7/en/show.html +type ShowStmt struct { + dmlNode + + Tp ShowStmtType // Databases/Tables/Columns/.... + DBName string + Table *TableName // Used for showing columns. + Partition model.CIStr // Used for showing partition. + Column *ColumnName // Used for `desc table column`. + IndexName model.CIStr + Flag int // Some flag parsed from sql, such as FULL. + Full bool + User *auth.UserIdentity // Used for show grants/create user. + Roles []*auth.RoleIdentity // Used for show grants .. using + IfNotExists bool // Used for `show create database if not exists` + Extended bool // Used for `show extended columns from ...` + + // GlobalScope is used by `show variables` and `show bindings` + GlobalScope bool + Pattern *PatternLikeExpr + Where ExprNode + + ShowProfileTypes []int // Used for `SHOW PROFILE` syntax + ShowProfileArgs *int64 // Used for `SHOW PROFILE` syntax + ShowProfileLimit *Limit // Used for `SHOW PROFILE` syntax +} + +// Restore implements Node interface. +func (n *ShowStmt) Restore(ctx *format.RestoreCtx) error { + restoreOptFull := func() { + if n.Full { + ctx.WriteKeyWord("FULL ") + } + } + restoreShowDatabaseNameOpt := func() { + if n.DBName != "" { + // FROM OR IN + ctx.WriteKeyWord(" IN ") + ctx.WriteName(n.DBName) + } + } + restoreGlobalScope := func() { + if n.GlobalScope { + ctx.WriteKeyWord("GLOBAL ") + } else { + ctx.WriteKeyWord("SESSION ") + } + } + restoreShowLikeOrWhereOpt := func() error { + if n.Pattern != nil && n.Pattern.Pattern != nil { + ctx.WriteKeyWord(" LIKE ") + if err := n.Pattern.Pattern.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Pattern") + } + } else if n.Where != nil { + ctx.WriteKeyWord(" WHERE ") + if err := n.Where.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Where") + } + } + return nil + } + + ctx.WriteKeyWord("SHOW ") + switch n.Tp { + case ShowCreateTable: + ctx.WriteKeyWord("CREATE TABLE ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Table") + } + case ShowCreateView: + ctx.WriteKeyWord("CREATE VIEW ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.VIEW") + } + case ShowCreateDatabase: + ctx.WriteKeyWord("CREATE DATABASE ") + if n.IfNotExists { + ctx.WriteKeyWord("IF NOT EXISTS ") + } + ctx.WriteName(n.DBName) + case ShowCreateSequence: + ctx.WriteKeyWord("CREATE SEQUENCE ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.SEQUENCE") + } + case ShowCreatePlacementPolicy: + ctx.WriteKeyWord("CREATE PLACEMENT POLICY ") + ctx.WriteName(n.DBName) + case ShowCreateUser: + ctx.WriteKeyWord("CREATE USER ") + if err := n.User.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.User") + } + case ShowGrants: + ctx.WriteKeyWord("GRANTS") + if n.User != nil { + ctx.WriteKeyWord(" FOR ") + if err := n.User.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.User") + } + } + if n.Roles != nil { + ctx.WriteKeyWord(" USING ") + for i, r := range n.Roles { + if err := r.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.User") + } + if i != len(n.Roles)-1 { + ctx.WritePlain(", ") + } + } + } + case ShowMasterStatus: + ctx.WriteKeyWord("MASTER STATUS") + case ShowProcessList: + restoreOptFull() + ctx.WriteKeyWord("PROCESSLIST") + case ShowStatsExtended: + ctx.WriteKeyWord("STATS_EXTENDED") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowStatsMeta: + ctx.WriteKeyWord("STATS_META") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowStatsHistograms: + ctx.WriteKeyWord("STATS_HISTOGRAMS") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowStatsTopN: + ctx.WriteKeyWord("STATS_TOPN") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowStatsBuckets: + ctx.WriteKeyWord("STATS_BUCKETS") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowStatsHealthy: + ctx.WriteKeyWord("STATS_HEALTHY") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowHistogramsInFlight: + ctx.WriteKeyWord("HISTOGRAMS_IN_FLIGHT") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowColumnStatsUsage: + ctx.WriteKeyWord("COLUMN_STATS_USAGE") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + case ShowProfiles: + ctx.WriteKeyWord("PROFILES") + case ShowProfile: + ctx.WriteKeyWord("PROFILE") + if len(n.ShowProfileTypes) > 0 { + for i, tp := range n.ShowProfileTypes { + if i != 0 { + ctx.WritePlain(",") + } + ctx.WritePlain(" ") + switch tp { + case ProfileTypeCPU: + ctx.WriteKeyWord("CPU") + case ProfileTypeMemory: + ctx.WriteKeyWord("MEMORY") + case ProfileTypeBlockIo: + ctx.WriteKeyWord("BLOCK IO") + case ProfileTypeContextSwitch: + ctx.WriteKeyWord("CONTEXT SWITCHES") + case ProfileTypeIpc: + ctx.WriteKeyWord("IPC") + case ProfileTypePageFaults: + ctx.WriteKeyWord("PAGE FAULTS") + case ProfileTypeSource: + ctx.WriteKeyWord("SOURCE") + case ProfileTypeSwaps: + ctx.WriteKeyWord("SWAPS") + case ProfileTypeAll: + ctx.WriteKeyWord("ALL") + } + } + } + if n.ShowProfileArgs != nil { + ctx.WriteKeyWord(" FOR QUERY ") + ctx.WritePlainf("%d", *n.ShowProfileArgs) + } + if n.ShowProfileLimit != nil { + ctx.WritePlain(" ") + if err := n.ShowProfileLimit.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.WritePlain") + } + } + + case ShowPrivileges: + ctx.WriteKeyWord("PRIVILEGES") + case ShowBuiltins: + ctx.WriteKeyWord("BUILTINS") + case ShowCreateImport: + ctx.WriteKeyWord("CREATE IMPORT ") + ctx.WriteName(n.DBName) + case ShowPlacementForDatabase: + ctx.WriteKeyWord("PLACEMENT FOR DATABASE ") + ctx.WriteName(n.DBName) + case ShowPlacementForTable: + ctx.WriteKeyWord("PLACEMENT FOR TABLE ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Table") + } + case ShowPlacementForPartition: + ctx.WriteKeyWord("PLACEMENT FOR TABLE ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Table") + } + ctx.WriteKeyWord(" PARTITION ") + ctx.WriteName(n.Partition.String()) + // ShowTargetFilterable + default: + switch n.Tp { + case ShowEngines: + ctx.WriteKeyWord("ENGINES") + case ShowConfig: + ctx.WriteKeyWord("CONFIG") + case ShowDatabases: + ctx.WriteKeyWord("DATABASES") + case ShowCharset: + ctx.WriteKeyWord("CHARSET") + case ShowTables: + restoreOptFull() + ctx.WriteKeyWord("TABLES") + restoreShowDatabaseNameOpt() + case ShowOpenTables: + ctx.WriteKeyWord("OPEN TABLES") + restoreShowDatabaseNameOpt() + case ShowTableStatus: + ctx.WriteKeyWord("TABLE STATUS") + restoreShowDatabaseNameOpt() + case ShowIndex: + // here can be INDEX INDEXES KEYS + // FROM or IN + ctx.WriteKeyWord("INDEX IN ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Table") + } // TODO: remember to check this case + case ShowColumns: // equivalent to SHOW FIELDS + if n.Extended { + ctx.WriteKeyWord("EXTENDED ") + } + restoreOptFull() + ctx.WriteKeyWord("COLUMNS") + if n.Table != nil { + // FROM or IN + ctx.WriteKeyWord(" IN ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Table") + } + } + restoreShowDatabaseNameOpt() + case ShowWarnings: + ctx.WriteKeyWord("WARNINGS") + case ShowErrors: + ctx.WriteKeyWord("ERRORS") + case ShowVariables: + restoreGlobalScope() + ctx.WriteKeyWord("VARIABLES") + case ShowStatus: + restoreGlobalScope() + ctx.WriteKeyWord("STATUS") + case ShowCollation: + ctx.WriteKeyWord("COLLATION") + case ShowTriggers: + ctx.WriteKeyWord("TRIGGERS") + restoreShowDatabaseNameOpt() + case ShowProcedureStatus: + ctx.WriteKeyWord("PROCEDURE STATUS") + case ShowEvents: + ctx.WriteKeyWord("EVENTS") + restoreShowDatabaseNameOpt() + case ShowPlugins: + ctx.WriteKeyWord("PLUGINS") + case ShowBindings: + if n.GlobalScope { + ctx.WriteKeyWord("GLOBAL ") + } else { + ctx.WriteKeyWord("SESSION ") + } + ctx.WriteKeyWord("BINDINGS") + case ShowBindingCacheStatus: + ctx.WriteKeyWord("BINDING_CACHE STATUS") + case ShowPumpStatus: + ctx.WriteKeyWord("PUMP STATUS") + case ShowDrainerStatus: + ctx.WriteKeyWord("DRAINER STATUS") + case ShowAnalyzeStatus: + ctx.WriteKeyWord("ANALYZE STATUS") + case ShowRegions: + ctx.WriteKeyWord("TABLE ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Table") + } + if len(n.IndexName.L) > 0 { + ctx.WriteKeyWord(" INDEX ") + ctx.WriteName(n.IndexName.String()) + } + ctx.WriteKeyWord(" REGIONS") + if err := restoreShowLikeOrWhereOpt(); err != nil { + return err + } + return nil + case ShowTableNextRowId: + ctx.WriteKeyWord("TABLE ") + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore ShowStmt.Table") + } + ctx.WriteKeyWord(" NEXT_ROW_ID") + return nil + case ShowBackups: + ctx.WriteKeyWord("BACKUPS") + case ShowRestores: + ctx.WriteKeyWord("RESTORES") + case ShowImports: + ctx.WriteKeyWord("IMPORTS") + case ShowPlacement: + ctx.WriteKeyWord("PLACEMENT") + case ShowPlacementLabels: + ctx.WriteKeyWord("PLACEMENT LABELS") + default: + return errors.New("Unknown ShowStmt type") + } + restoreShowLikeOrWhereOpt() + } + return nil +} + +// Accept implements Node Accept interface. +func (n *ShowStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*ShowStmt) + if n.Table != nil { + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + } + if n.Column != nil { + node, ok := n.Column.Accept(v) + if !ok { + return n, false + } + n.Column = node.(*ColumnName) + } + if n.Pattern != nil { + node, ok := n.Pattern.Accept(v) + if !ok { + return n, false + } + n.Pattern = node.(*PatternLikeExpr) + } + + if n.Where != nil { + node, ok := n.Where.Accept(v) + if !ok { + return n, false + } + n.Where = node.(ExprNode) + } + return v.Leave(n) +} + +// WindowSpec is the specification of a window. +type WindowSpec struct { + node + + Name model.CIStr + // Ref is the reference window of this specification. For example, in `w2 as (w1 order by a)`, + // the definition of `w2` references `w1`. + Ref model.CIStr + + PartitionBy *PartitionByClause + OrderBy *OrderByClause + Frame *FrameClause + + // OnlyAlias will set to true of the first following case. + // To make compatible with MySQL, we need to distinguish `select func over w` from `select func over (w)`. + OnlyAlias bool +} + +// Restore implements Node interface. +func (n *WindowSpec) Restore(ctx *format.RestoreCtx) error { + if name := n.Name.String(); name != "" { + ctx.WriteName(name) + if n.OnlyAlias { + return nil + } + ctx.WriteKeyWord(" AS ") + } + ctx.WritePlain("(") + sep := "" + if refName := n.Ref.String(); refName != "" { + ctx.WriteName(refName) + sep = " " + } + if n.PartitionBy != nil { + ctx.WritePlain(sep) + if err := n.PartitionBy.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore WindowSpec.PartitionBy") + } + sep = " " + } + if n.OrderBy != nil { + ctx.WritePlain(sep) + if err := n.OrderBy.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore WindowSpec.OrderBy") + } + sep = " " + } + if n.Frame != nil { + ctx.WritePlain(sep) + if err := n.Frame.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore WindowSpec.Frame") + } + } + ctx.WritePlain(")") + + return nil +} + +// Accept implements Node Accept interface. +func (n *WindowSpec) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*WindowSpec) + if n.PartitionBy != nil { + node, ok := n.PartitionBy.Accept(v) + if !ok { + return n, false + } + n.PartitionBy = node.(*PartitionByClause) + } + if n.OrderBy != nil { + node, ok := n.OrderBy.Accept(v) + if !ok { + return n, false + } + n.OrderBy = node.(*OrderByClause) + } + if n.Frame != nil { + node, ok := n.Frame.Accept(v) + if !ok { + return n, false + } + n.Frame = node.(*FrameClause) + } + return v.Leave(n) +} + +type SelectIntoType int + +const ( + SelectIntoOutfile SelectIntoType = iota + 1 + SelectIntoDumpfile + SelectIntoVars +) + +type SelectIntoOption struct { + node + + Tp SelectIntoType + FileName string + FieldsInfo *FieldsClause + LinesInfo *LinesClause +} + +// 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 + return errors.New("Unsupported SelectionInto type") + } + + ctx.WriteKeyWord("INTO OUTFILE ") + ctx.WriteString(n.FileName) + if n.FieldsInfo != nil { + if err := n.FieldsInfo.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectInto.FieldsInfo") + } + } + if n.LinesInfo != nil { + if err := n.LinesInfo.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SelectInto.LinesInfo") + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *SelectIntoOption) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + return v.Leave(n) +} + +// PartitionByClause represents partition by clause. +type PartitionByClause struct { + node + + Items []*ByItem +} + +// Restore implements Node interface. +func (n *PartitionByClause) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("PARTITION BY ") + for i, v := range n.Items { + if i != 0 { + ctx.WritePlain(", ") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore PartitionByClause.Items[%d]", i) + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *PartitionByClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*PartitionByClause) + for i, val := range n.Items { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.Items[i] = node.(*ByItem) + } + return v.Leave(n) +} + +// FrameType is the type of window function frame. +type FrameType int + +// Window function frame types. +// MySQL only supports `ROWS` and `RANGES`. +const ( + Rows = iota + Ranges + Groups +) + +// FrameClause represents frame clause. +type FrameClause struct { + node + + Type FrameType + Extent FrameExtent +} + +// Restore implements Node interface. +func (n *FrameClause) Restore(ctx *format.RestoreCtx) error { + switch n.Type { + case Rows: + ctx.WriteKeyWord("ROWS") + case Ranges: + ctx.WriteKeyWord("RANGE") + default: + return errors.New("Unsupported window function frame type") + } + ctx.WriteKeyWord(" BETWEEN ") + if err := n.Extent.Start.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore FrameClause.Extent.Start") + } + ctx.WriteKeyWord(" AND ") + if err := n.Extent.End.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore FrameClause.Extent.End") + } + + return nil +} + +// Accept implements Node Accept interface. +func (n *FrameClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*FrameClause) + node, ok := n.Extent.Start.Accept(v) + if !ok { + return n, false + } + n.Extent.Start = *node.(*FrameBound) + node, ok = n.Extent.End.Accept(v) + if !ok { + return n, false + } + n.Extent.End = *node.(*FrameBound) + return v.Leave(n) +} + +// FrameExtent represents frame extent. +type FrameExtent struct { + Start FrameBound + End FrameBound +} + +// FrameType is the type of window function frame bound. +type BoundType int + +// Frame bound types. +const ( + Following = iota + Preceding + CurrentRow +) + +// FrameBound represents frame bound. +type FrameBound struct { + node + + Type BoundType + UnBounded bool + Expr ExprNode + // `Unit` is used to indicate the units in which the `Expr` should be interpreted. + // For example: '2:30' MINUTE_SECOND. + Unit TimeUnitType +} + +// Restore implements Node interface. +func (n *FrameBound) Restore(ctx *format.RestoreCtx) error { + if n.UnBounded { + ctx.WriteKeyWord("UNBOUNDED") + } + switch n.Type { + case CurrentRow: + ctx.WriteKeyWord("CURRENT ROW") + case Preceding, Following: + if n.Unit != TimeUnitInvalid { + ctx.WriteKeyWord("INTERVAL ") + } + if n.Expr != nil { + if err := n.Expr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore FrameBound.Expr") + } + } + if n.Unit != TimeUnitInvalid { + ctx.WritePlain(" ") + ctx.WriteKeyWord(n.Unit.String()) + } + if n.Type == Preceding { + ctx.WriteKeyWord(" PRECEDING") + } else { + ctx.WriteKeyWord(" FOLLOWING") + } + } + return nil +} + +// Accept implements Node Accept interface. +func (n *FrameBound) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*FrameBound) + if n.Expr != nil { + node, ok := n.Expr.Accept(v) + if !ok { + return n, false + } + n.Expr = node.(ExprNode) + } + return v.Leave(n) +} + +type SplitRegionStmt struct { + dmlNode + + Table *TableName + IndexName model.CIStr + PartitionNames []model.CIStr + + SplitSyntaxOpt *SplitSyntaxOption + + SplitOpt *SplitOption +} + +type SplitOption struct { + Lower []ExprNode + Upper []ExprNode + Num int64 + ValueLists [][]ExprNode +} + +type SplitSyntaxOption struct { + HasRegionFor bool + HasPartition bool +} + +func (n *SplitRegionStmt) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("SPLIT ") + if n.SplitSyntaxOpt != nil { + if n.SplitSyntaxOpt.HasRegionFor { + ctx.WriteKeyWord("REGION FOR ") + } + if n.SplitSyntaxOpt.HasPartition { + ctx.WriteKeyWord("PARTITION ") + + } + } + ctx.WriteKeyWord("TABLE ") + + if err := n.Table.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore SplitIndexRegionStmt.Table") + } + if len(n.PartitionNames) > 0 { + ctx.WriteKeyWord(" PARTITION") + ctx.WritePlain("(") + for i, v := range n.PartitionNames { + if i != 0 { + ctx.WritePlain(", ") + } + ctx.WriteName(v.String()) + } + ctx.WritePlain(")") + } + + if len(n.IndexName.L) > 0 { + ctx.WriteKeyWord(" INDEX ") + ctx.WriteName(n.IndexName.String()) + } + ctx.WritePlain(" ") + err := n.SplitOpt.Restore(ctx) + return err +} + +func (n *SplitRegionStmt) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + + n = newNode.(*SplitRegionStmt) + node, ok := n.Table.Accept(v) + if !ok { + return n, false + } + n.Table = node.(*TableName) + for i, val := range n.SplitOpt.Lower { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.SplitOpt.Lower[i] = node.(ExprNode) + } + for i, val := range n.SplitOpt.Upper { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.SplitOpt.Upper[i] = node.(ExprNode) + } + + for i, list := range n.SplitOpt.ValueLists { + for j, val := range list { + node, ok := val.Accept(v) + if !ok { + return n, false + } + n.SplitOpt.ValueLists[i][j] = node.(ExprNode) + } + } + return v.Leave(n) +} + +func (n *SplitOption) Restore(ctx *format.RestoreCtx) error { + if len(n.ValueLists) == 0 { + ctx.WriteKeyWord("BETWEEN ") + ctx.WritePlain("(") + for j, v := range n.Lower { + if j != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore SplitOption Lower") + } + } + ctx.WritePlain(")") + + ctx.WriteKeyWord(" AND ") + ctx.WritePlain("(") + for j, v := range n.Upper { + if j != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore SplitOption Upper") + } + } + ctx.WritePlain(")") + ctx.WriteKeyWord(" REGIONS") + ctx.WritePlainf(" %d", n.Num) + return nil + } + ctx.WriteKeyWord("BY ") + for i, row := range n.ValueLists { + if i != 0 { + ctx.WritePlain(",") + } + ctx.WritePlain("(") + for j, v := range row { + if j != 0 { + ctx.WritePlain(",") + } + if err := v.Restore(ctx); err != nil { + return errors.Annotatef(err, "An error occurred while restore SplitOption.ValueLists[%d][%d]", i, j) + } + } + ctx.WritePlain(")") + } + return nil +} + +type FulltextSearchModifier int + +const ( + FulltextSearchModifierNaturalLanguageMode = 0 + FulltextSearchModifierBooleanMode = 1 + FulltextSearchModifierModeMask = 0xF + FulltextSearchModifierWithQueryExpansion = 1 << 4 +) + +func (m FulltextSearchModifier) IsBooleanMode() bool { + return m&FulltextSearchModifierModeMask == FulltextSearchModifierBooleanMode +} + +func (m FulltextSearchModifier) IsNaturalLanguageMode() bool { + return m&FulltextSearchModifierModeMask == FulltextSearchModifierNaturalLanguageMode +} + +func (m FulltextSearchModifier) WithQueryExpansion() bool { + return m&FulltextSearchModifierWithQueryExpansion == FulltextSearchModifierWithQueryExpansion +} + +type AsOfClause struct { + node + TsExpr ExprNode +} + +// Restore implements Node interface. +func (n *AsOfClause) Restore(ctx *format.RestoreCtx) error { + ctx.WriteKeyWord("AS OF TIMESTAMP ") + if err := n.TsExpr.Restore(ctx); err != nil { + return errors.Annotate(err, "An error occurred while restore AsOfClause.Expr") + } + return nil +} + +// Accept implements Node Accept interface. +func (n *AsOfClause) Accept(v Visitor) (Node, bool) { + newNode, skipChildren := v.Enter(n) + if skipChildren { + return v.Leave(newNode) + } + n = newNode.(*AsOfClause) + node, ok := n.TsExpr.Accept(v) + if !ok { + return n, false + } + n.TsExpr = node.(ExprNode) + return v.Leave(n) +} diff --git a/parser/ast/dml_test.go b/parser/ast/dml_test.go new file mode 100644 index 0000000000000..f7bfd78e25901 --- /dev/null +++ b/parser/ast/dml_test.go @@ -0,0 +1,417 @@ +// Copyright 2017 PingCAP, Inc. +// +// 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_test + +import ( + "testing" + + . "github.com/pingcap/tidb/parser/ast" + "github.com/pingcap/tidb/parser/format" + "github.com/stretchr/testify/require" +) + +func TestDMLVisitorCover(t *testing.T) { + ce := &checkExpr{} + + tableRefsClause := &TableRefsClause{TableRefs: &Join{Left: &TableSource{Source: &TableName{}}, On: &OnCondition{Expr: ce}}} + + stmts := []struct { + node Node + expectedEnterCnt int + expectedLeaveCnt int + }{ + {&DeleteStmt{TableRefs: tableRefsClause, Tables: &DeleteTableList{}, Where: ce, + Order: &OrderByClause{}, Limit: &Limit{Count: ce, Offset: ce}}, 4, 4}, + {&ShowStmt{Table: &TableName{}, Column: &ColumnName{}, Pattern: &PatternLikeExpr{Expr: ce, Pattern: ce}, Where: ce}, 3, 3}, + {&LoadDataStmt{Table: &TableName{}, Columns: []*ColumnName{{}}, FieldsInfo: &FieldsClause{}, LinesInfo: &LinesClause{}}, 0, 0}, + {&Assignment{Column: &ColumnName{}, Expr: ce}, 1, 1}, + {&ByItem{Expr: ce}, 1, 1}, + {&GroupByClause{Items: []*ByItem{{Expr: ce}, {Expr: ce}}}, 2, 2}, + {&HavingClause{Expr: ce}, 1, 1}, + {&Join{Left: &TableSource{Source: &TableName{}}}, 0, 0}, + {&Limit{Count: ce, Offset: ce}, 2, 2}, + {&OnCondition{Expr: ce}, 1, 1}, + {&OrderByClause{Items: []*ByItem{{Expr: ce}, {Expr: ce}}}, 2, 2}, + {&SelectField{Expr: ce, WildCard: &WildCardField{}}, 1, 1}, + {&TableName{}, 0, 0}, + {tableRefsClause, 1, 1}, + {&TableSource{Source: &TableName{}}, 0, 0}, + {&WildCardField{}, 0, 0}, + + // TODO: cover childrens + {&InsertStmt{Table: tableRefsClause}, 1, 1}, + {&SetOprStmt{}, 0, 0}, + {&UpdateStmt{TableRefs: tableRefsClause}, 1, 1}, + {&SelectStmt{}, 0, 0}, + {&FieldList{}, 0, 0}, + {&SetOprSelectList{}, 0, 0}, + {&WindowSpec{}, 0, 0}, + {&PartitionByClause{}, 0, 0}, + {&FrameClause{}, 0, 0}, + {&FrameBound{}, 0, 0}, + } + + for _, v := range stmts { + ce.reset() + v.node.Accept(checkVisitor{}) + require.Equal(t, v.expectedEnterCnt, ce.enterCnt) + require.Equal(t, v.expectedLeaveCnt, ce.leaveCnt) + v.node.Accept(visitor1{}) + } +} + +func TestTableNameRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"dbb.`tbb1`", "`dbb`.`tbb1`"}, + {"`tbb2`", "`tbb2`"}, + {"tbb3", "`tbb3`"}, + {"dbb.`hello-world`", "`dbb`.`hello-world`"}, + {"`dbb`.`hello-world`", "`dbb`.`hello-world`"}, + {"`dbb.HelloWorld`", "`dbb.HelloWorld`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*CreateTableStmt).Table + } + runNodeRestoreTest(t, testCases, "CREATE TABLE %s (id VARCHAR(128) NOT NULL);", extractNodeFunc) +} + +func TestTableNameIndexHintsRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"t use index (hello)", "`t` USE INDEX (`hello`)"}, + {"t use index (hello, world)", "`t` USE INDEX (`hello`, `world`)"}, + {"t use index ()", "`t` USE INDEX ()"}, + {"t use key ()", "`t` USE INDEX ()"}, + {"t ignore key ()", "`t` IGNORE INDEX ()"}, + {"t force key ()", "`t` FORCE INDEX ()"}, + {"t use index for order by (idx1)", "`t` USE INDEX FOR ORDER BY (`idx1`)"}, + + {"t use index (hello, world, yes) force key (good)", "`t` USE INDEX (`hello`, `world`, `yes`) FORCE INDEX (`good`)"}, + {"t use index (hello, world, yes) use index for order by (good)", "`t` USE INDEX (`hello`, `world`, `yes`) USE INDEX FOR ORDER BY (`good`)"}, + {"t ignore key (hello, world, yes) force key (good)", "`t` IGNORE INDEX (`hello`, `world`, `yes`) FORCE INDEX (`good`)"}, + + {"t use index for group by (idx1) use index for order by (idx2)", "`t` USE INDEX FOR GROUP BY (`idx1`) USE INDEX FOR ORDER BY (`idx2`)"}, + {"t use index for group by (idx1) ignore key for order by (idx2)", "`t` USE INDEX FOR GROUP BY (`idx1`) IGNORE INDEX FOR ORDER BY (`idx2`)"}, + {"t use index for group by (idx1) ignore key for group by (idx2)", "`t` USE INDEX FOR GROUP BY (`idx1`) IGNORE INDEX FOR GROUP BY (`idx2`)"}, + {"t use index for order by (idx1) ignore key for group by (idx2)", "`t` USE INDEX FOR ORDER BY (`idx1`) IGNORE INDEX FOR GROUP BY (`idx2`)"}, + + {"t use index for order by (idx1) ignore key for group by (idx2) use index (idx3)", "`t` USE INDEX FOR ORDER BY (`idx1`) IGNORE INDEX FOR GROUP BY (`idx2`) USE INDEX (`idx3`)"}, + {"t use index for order by (idx1) ignore key for group by (idx2) use index (idx3)", "`t` USE INDEX FOR ORDER BY (`idx1`) IGNORE INDEX FOR GROUP BY (`idx2`) USE INDEX (`idx3`)"}, + + {"t use index (`foo``bar`) force index (`baz``1`, `xyz`)", "`t` USE INDEX (`foo``bar`) FORCE INDEX (`baz``1`, `xyz`)"}, + {"t force index (`foo``bar`) ignore index (`baz``1`, xyz)", "`t` FORCE INDEX (`foo``bar`) IGNORE INDEX (`baz``1`, `xyz`)"}, + {"t ignore index (`foo``bar`) force key (`baz``1`, xyz)", "`t` IGNORE INDEX (`foo``bar`) FORCE INDEX (`baz``1`, `xyz`)"}, + {"t ignore index (`foo``bar`) ignore key for group by (`baz``1`, xyz)", "`t` IGNORE INDEX (`foo``bar`) IGNORE INDEX FOR GROUP BY (`baz``1`, `xyz`)"}, + {"t ignore index (`foo``bar`) ignore key for order by (`baz``1`, xyz)", "`t` IGNORE INDEX (`foo``bar`) IGNORE INDEX FOR ORDER BY (`baz``1`, `xyz`)"}, + + {"t use index for group by (`foo``bar`) use index for order by (`baz``1`, `xyz`)", "`t` USE INDEX FOR GROUP BY (`foo``bar`) USE INDEX FOR ORDER BY (`baz``1`, `xyz`)"}, + {"t use index for group by (`foo``bar`) ignore key for order by (`baz``1`, `xyz`)", "`t` USE INDEX FOR GROUP BY (`foo``bar`) IGNORE INDEX FOR ORDER BY (`baz``1`, `xyz`)"}, + {"t use index for group by (`foo``bar`) ignore key for group by (`baz``1`, `xyz`)", "`t` USE INDEX FOR GROUP BY (`foo``bar`) IGNORE INDEX FOR GROUP BY (`baz``1`, `xyz`)"}, + {"t use index for order by (`foo``bar`) ignore key for group by (`baz``1`, `xyz`)", "`t` USE INDEX FOR ORDER BY (`foo``bar`) IGNORE INDEX FOR GROUP BY (`baz``1`, `xyz`)"}, + + {"t tt use index for order by (`foo``bar`) ignore key for group by (`baz``1`, `xyz`)", "`t` AS `tt` USE INDEX FOR ORDER BY (`foo``bar`) IGNORE INDEX FOR GROUP BY (`baz``1`, `xyz`)"}, + {"t as tt use index for order by (`foo``bar`) ignore key for group by (`baz``1`, `xyz`)", "`t` AS `tt` USE INDEX FOR ORDER BY (`foo``bar`) IGNORE INDEX FOR GROUP BY (`baz``1`, `xyz`)"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).From.TableRefs.Left + } + runNodeRestoreTest(t, testCases, "SELECT * FROM %s", extractNodeFunc) +} + +func TestLimitRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"limit 10", "LIMIT 10"}, + {"limit 10,20", "LIMIT 10,20"}, + {"limit 20 offset 10", "LIMIT 10,20"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).Limit + } + runNodeRestoreTest(t, testCases, "SELECT 1 %s", extractNodeFunc) +} + +func TestWildCardFieldRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"*", "*"}, + {"t.*", "`t`.*"}, + {"testdb.t.*", "`testdb`.`t`.*"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).Fields.Fields[0].WildCard + } + runNodeRestoreTest(t, testCases, "SELECT %s", extractNodeFunc) +} + +func TestSelectFieldRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"*", "*"}, + {"t.*", "`t`.*"}, + {"testdb.t.*", "`testdb`.`t`.*"}, + {"col as a", "`col` AS `a`"}, + {"col + 1 a", "`col`+1 AS `a`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).Fields.Fields[0] + } + runNodeRestoreTest(t, testCases, "SELECT %s", extractNodeFunc) +} + +func TestFieldListRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"*", "*"}, + {"t.*", "`t`.*"}, + {"testdb.t.*", "`testdb`.`t`.*"}, + {"col as a", "`col` AS `a`"}, + {"`t`.*, s.col as a", "`t`.*, `s`.`col` AS `a`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).Fields + } + runNodeRestoreTest(t, testCases, "SELECT %s", extractNodeFunc) +} + +func TestTableSourceRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"tbl", "`tbl`"}, + {"tbl as t", "`tbl` AS `t`"}, + {"(select * from tbl) as t", "(SELECT * FROM `tbl`) AS `t`"}, + {"(select * from a union select * from b) as t", "(SELECT * FROM `a` UNION SELECT * FROM `b`) AS `t`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).From.TableRefs.Left + } + runNodeRestoreTest(t, testCases, "select * from %s", extractNodeFunc) +} + +func TestOnConditionRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"on t1.a=t2.a", "ON `t1`.`a`=`t2`.`a`"}, + {"on t1.a=t2.a and t1.b=t2.b", "ON `t1`.`a`=`t2`.`a` AND `t1`.`b`=`t2`.`b`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).From.TableRefs.On + } + runNodeRestoreTest(t, testCases, "select * from t1 join t2 %s", extractNodeFunc) +} + +func TestJoinRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"t1 natural join t2", "`t1` NATURAL JOIN `t2`"}, + {"t1 natural left join t2", "`t1` NATURAL LEFT JOIN `t2`"}, + {"t1 natural right outer join t2", "`t1` NATURAL RIGHT JOIN `t2`"}, + {"t1 straight_join t2", "`t1` STRAIGHT_JOIN `t2`"}, + {"t1 straight_join t2 on t1.a>t2.a", "`t1` STRAIGHT_JOIN `t2` ON `t1`.`a`>`t2`.`a`"}, + {"t1 cross join t2", "`t1` JOIN `t2`"}, + {"t1 cross join t2 on t1.a>t2.a", "`t1` JOIN `t2` ON `t1`.`a`>`t2`.`a`"}, + {"t1 inner join t2 using (b)", "`t1` JOIN `t2` USING (`b`)"}, + {"t1 join t2 using (b,c) left join t3 on t1.a>t3.a", "(`t1` JOIN `t2` USING (`b`,`c`)) LEFT JOIN `t3` ON `t1`.`a`>`t3`.`a`"}, + {"t1 natural join t2 right outer join t3 using (b,c)", "(`t1` NATURAL JOIN `t2`) RIGHT JOIN `t3` USING (`b`,`c`)"}, + {"t1, t2", "(`t1`) JOIN `t2`"}, + {"t1, t2, t3", "((`t1`) JOIN `t2`) JOIN `t3`"}, + {"(select * from t) t1, (t2, t3)", "(SELECT * FROM `t`) AS `t1`, ((`t2`) JOIN `t3`)"}, + {"(select * from t) t1, t2", "(SELECT * FROM `t`) AS `t1`, `t2`"}, + {"(select * from (select a from t1) tb1) tb;", "(SELECT * FROM (SELECT `a` FROM `t1`) AS `tb1`) AS `tb`"}, + {"(select * from t) t1 cross join t2", "(SELECT * FROM `t`) AS `t1` JOIN `t2`"}, + {"(select * from t) t1 natural join t2", "(SELECT * FROM `t`) AS `t1` NATURAL JOIN `t2`"}, + {"(select * from t) t1 cross join t2 on t1.a>t2.a", "(SELECT * FROM `t`) AS `t1` JOIN `t2` ON `t1`.`a`>`t2`.`a`"}, + {"(select * from t union select * from t1) tb1, t2;", "(SELECT * FROM `t` UNION SELECT * FROM `t1`) AS `tb1`, `t2`"}, + //todo: uncomment this after https://github.com/pingcap/parser/issues/1127 fixed + //{"(select a from t) t1 join t t2, t3;", "((SELECT `a` FROM `t`) AS `t1` JOIN `t` AS `t2`) JOIN `t3`"}, + } + testChangedCases := []NodeRestoreTestCase{ + {"(a al left join b bl on al.a1 > bl.b1) join (a ar right join b br on ar.a1 > br.b1)", "(`a` AS `al` LEFT JOIN `b` AS `bl` ON `al`.`a1`>`bl`.`b1`) JOIN (`a` AS `ar` RIGHT JOIN `b` AS `br` ON `ar`.`a1`>`br`.`b1`)"}, + {"a al left join b bl on al.a1 > bl.b1, a ar right join b br on ar.a1 > br.b1", "(`a` AS `al` LEFT JOIN `b` AS `bl` ON `al`.`a1`>`bl`.`b1`) JOIN (`a` AS `ar` RIGHT JOIN `b` AS `br` ON `ar`.`a1`>`br`.`b1`)"}, + {"t1 join (t2 right join t3 on t2.a > t3.a join (t4 right join t5 on t4.a > t5.a))", "`t1` JOIN ((`t2` RIGHT JOIN `t3` ON `t2`.`a`>`t3`.`a`) JOIN (`t4` RIGHT JOIN `t5` ON `t4`.`a`>`t5`.`a`))"}, + {"t1 join t2 right join t3 on t2.a=t3.a", "(`t1` JOIN `t2`) RIGHT JOIN `t3` ON `t2`.`a`=`t3`.`a`"}, + {"t1 join (t2 right join t3 on t2.a=t3.a)", "`t1` JOIN (`t2` RIGHT JOIN `t3` ON `t2`.`a`=`t3`.`a`)"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).From.TableRefs + } + runNodeRestoreTest(t, testCases, "select * from %s", extractNodeFunc) + runNodeRestoreTestWithFlagsStmtChange(t, testChangedCases, "select * from %s", extractNodeFunc, format.DefaultRestoreFlags) +} + +func TestTableRefsClauseRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"t", "`t`"}, + {"t1 join t2", "`t1` JOIN `t2`"}, + {"t1, t2", "(`t1`) JOIN `t2`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).From + } + runNodeRestoreTest(t, testCases, "select * from %s", extractNodeFunc) +} + +func TestDeleteTableListRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"t1,t2", "`t1`,`t2`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*DeleteStmt).Tables + } + runNodeRestoreTest(t, testCases, "DELETE %s FROM t1, t2;", extractNodeFunc) + runNodeRestoreTest(t, testCases, "DELETE FROM %s USING t1, t2;", extractNodeFunc) +} + +func TestDeleteTableIndexHintRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"DELETE FROM t1 USE key (`fld1`) WHERE fld=1", + "DELETE FROM `t1` USE INDEX (`fld1`) WHERE `fld`=1"}, + {"DELETE FROM t1 as tbl USE key (`fld1`) WHERE tbl.fld=2", + "DELETE FROM `t1` AS `tbl` USE INDEX (`fld1`) WHERE `tbl`.`fld`=2"}, + } + + extractNodeFunc := func(node Node) Node { + return node.(*DeleteStmt) + } + runNodeRestoreTest(t, testCases, "%s", extractNodeFunc) +} + +func TestByItemRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"a", "`a`"}, + {"a desc", "`a` DESC"}, + {"NULL", "NULL"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).OrderBy.Items[0] + } + runNodeRestoreTest(t, testCases, "select * from t order by %s", extractNodeFunc) +} + +func TestGroupByClauseRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"GROUP BY a,b desc", "GROUP BY `a`,`b` DESC"}, + {"GROUP BY 1 desc,b", "GROUP BY 1 DESC,`b`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).GroupBy + } + runNodeRestoreTest(t, testCases, "select * from t %s", extractNodeFunc) +} + +func TestOrderByClauseRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"ORDER BY a", "ORDER BY `a`"}, + {"ORDER BY a,b", "ORDER BY `a`,`b`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).OrderBy + } + runNodeRestoreTest(t, testCases, "SELECT 1 FROM t1 %s", extractNodeFunc) + + extractNodeFromSetOprStmtFunc := func(node Node) Node { + return node.(*SetOprStmt).OrderBy + } + runNodeRestoreTest(t, testCases, "SELECT 1 FROM t1 UNION SELECT 2 FROM t2 %s", extractNodeFromSetOprStmtFunc) +} + +func TestAssignmentRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"a=1", "`a`=1"}, + {"b=1+2", "`b`=1+2"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*UpdateStmt).List[0] + } + runNodeRestoreTest(t, testCases, "UPDATE t1 SET %s", extractNodeFunc) +} + +func TestHavingClauseRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"HAVING a", "HAVING `a`"}, + {"HAVING NULL", "HAVING NULL"}, + {"HAVING a>b", "HAVING `a`>`b`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).Having + } + runNodeRestoreTest(t, testCases, "select 1 from t1 group by 1 %s", extractNodeFunc) +} + +func TestFrameBoundRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"CURRENT ROW", "CURRENT ROW"}, + {"UNBOUNDED PRECEDING", "UNBOUNDED PRECEDING"}, + {"1 PRECEDING", "1 PRECEDING"}, + {"? PRECEDING", "? PRECEDING"}, + {"INTERVAL 5 DAY PRECEDING", "INTERVAL 5 DAY PRECEDING"}, + {"UNBOUNDED FOLLOWING", "UNBOUNDED FOLLOWING"}, + {"1 FOLLOWING", "1 FOLLOWING"}, + {"? FOLLOWING", "? FOLLOWING"}, + {"INTERVAL '2:30' MINUTE_SECOND FOLLOWING", "INTERVAL _UTF8MB4'2:30' MINUTE_SECOND FOLLOWING"}, + } + extractNodeFunc := func(node Node) Node { + return &node.(*SelectStmt).Fields.Fields[0].Expr.(*WindowFuncExpr).Spec.Frame.Extent.Start + } + runNodeRestoreTest(t, testCases, "select avg(val) over (rows between %s and current row) from t", extractNodeFunc) +} + +func TestFrameClauseRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"ROWS CURRENT ROW", "ROWS BETWEEN CURRENT ROW AND CURRENT ROW"}, + {"ROWS UNBOUNDED PRECEDING", "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"}, + {"ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING", "ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"}, + {"RANGE BETWEEN ? PRECEDING AND ? FOLLOWING", "RANGE BETWEEN ? PRECEDING AND ? FOLLOWING"}, + {"RANGE BETWEEN INTERVAL 5 DAY PRECEDING AND INTERVAL '2:30' MINUTE_SECOND FOLLOWING", "RANGE BETWEEN INTERVAL 5 DAY PRECEDING AND INTERVAL _UTF8MB4'2:30' MINUTE_SECOND FOLLOWING"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).Fields.Fields[0].Expr.(*WindowFuncExpr).Spec.Frame + } + runNodeRestoreTest(t, testCases, "select avg(val) over (%s) from t", extractNodeFunc) +} + +func TestPartitionByClauseRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"PARTITION BY a", "PARTITION BY `a`"}, + {"PARTITION BY NULL", "PARTITION BY NULL"}, + {"PARTITION BY a, b", "PARTITION BY `a`, `b`"}, + } + extractNodeFunc := func(node Node) Node { + return node.(*SelectStmt).Fields.Fields[0].Expr.(*WindowFuncExpr).Spec.PartitionBy + } + runNodeRestoreTest(t, testCases, "select avg(val) over (%s rows current row) from t", extractNodeFunc) +} + +func TestWindowSpecRestore(t *testing.T) { + testCases := []NodeRestoreTestCase{ + {"w as ()", "`w` AS ()"}, + {"w as (w1)", "`w` AS (`w1`)"}, + {"w as (w1 order by country)", "`w` AS (`w1` ORDER BY `country`)"}, + {"w as (partition by a order by b rows current row)", "`w` AS (PARTITION BY `a` ORDER BY `b` ROWS BETWEEN CURRENT ROW AND CURRENT ROW)"}, + } + extractNodeFunc := func(node Node) Node { + return &node.(*SelectStmt).WindowSpecs[0] + } + runNodeRestoreTest(t, testCases, "select rank() over w from t window %s", extractNodeFunc) + + testCases = []NodeRestoreTestCase{ + {"w", "`w`"}, + {"()", "()"}, + {"(w)", "(`w`)"}, + {"(w PARTITION BY country)", "(`w` PARTITION BY `country`)"}, + {"(PARTITION BY a ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)", "(PARTITION BY `a` ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)"}, + } + extractNodeFunc = func(node Node) Node { + return &node.(*SelectStmt).Fields.Fields[0].Expr.(*WindowFuncExpr).Spec + } + runNodeRestoreTest(t, testCases, "select rank() over %s from t window w as (order by a)", extractNodeFunc) +} + +func TestFulltextSearchModifier(t *testing.T) { + require.False(t, FulltextSearchModifier(FulltextSearchModifierNaturalLanguageMode).IsBooleanMode()) + require.True(t, FulltextSearchModifier(FulltextSearchModifierNaturalLanguageMode).IsNaturalLanguageMode()) + require.False(t, FulltextSearchModifier(FulltextSearchModifierNaturalLanguageMode).WithQueryExpansion()) +} diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index bd7109019de3d..3ea1cb9abd286 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -127,8 +127,30 @@ func (s *testIntegrationSuite) TestShowSubquery(c *C) { )) } +<<<<<<< HEAD func (s *testIntegrationSuite) TestPpdWithSetVar(c *C) { tk := testkit.NewTestKit(c, s.store) +======= +func TestJoinOperatorRightAssociative(t *testing.T) { + store, clean := testkit.CreateMockStore(t) + defer clean() + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("drop table if exists t") + tk.MustExec("create table t(a int, b int)") + tk.MustExec("insert into t values(1,10),(2,20)") + // make sure this join won't rewrite as left-associative join like (t0 join t1) join t2 when explicit parent existed. + // mysql will detect the t0.a is out of it's join parent scope and errors like ERROR 1054 (42S22): Unknown column 't0.a' in 'on clause' + err := tk.ExecToErr("select t1.* from t t0 cross join (t t1 join t t2 on 100=t0.a);") + require.Error(t, err) + require.EqualError(t, err, "[planner:1054]Unknown column 't0.a' in 'on clause'") +} + +func TestPpdWithSetVar(t *testing.T) { + store, clean := testkit.CreateMockStore(t) + defer clean() + tk := testkit.NewTestKit(t, store) +>>>>>>> 59fac2aba... planner: fix the join scope with explicit parents (#33311) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec("create table t(c1 int, c2 varchar(255))")