/*
Copyright 2019 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqlparser

import (
	"fmt"
	"strings"

	"vitess.io/vitess/go/sqltypes"
)

/*
This is the Vitess AST. This file should only contain pure struct declarations,
or methods used to mark a struct as implementing an interface. All other methods
related to these structs live in ast_funcs.go
*/

// SQLNode defines the interface for all nodes
// generated by the parser.
type SQLNode interface {
	Format(buf *TrackedBuffer)
}

// Statements
type (
	// Statement represents a statement.
	Statement interface {
		iStatement()
		SQLNode
	}

	// SelectStatement any SELECT statement.
	SelectStatement interface {
		iSelectStatement()
		iStatement()
		iInsertRows()
		AddOrder(*Order)
		SetLimit(*Limit)
		SQLNode
	}

	// Select represents a SELECT statement.
	Select struct {
		Cache       string
		Comments    Comments
		Distinct    string
		Hints       string
		SelectExprs SelectExprs
		From        TableExprs
		Where       *Where
		GroupBy     GroupBy
		Having      *Where
		OrderBy     OrderBy
		Limit       *Limit
		Lock        string
	}

	// Union represents a UNION statement.
	Union struct {
		Type        string
		Left, Right SelectStatement
		OrderBy     OrderBy
		Limit       *Limit
		Lock        string
	}

	// Stream represents a SELECT statement.
	Stream struct {
		Comments   Comments
		SelectExpr SelectExpr
		Table      TableName
	}

	// Insert represents an INSERT or REPLACE statement.
	// Per the MySQL docs, http://dev.mysql.com/doc/refman/5.7/en/replace.html
	// Replace is the counterpart to `INSERT IGNORE`, and works exactly like a
	// normal INSERT except if the row exists. In that case it first deletes
	// the row and re-inserts with new values. For that reason we keep it as an Insert struct.
	// Replaces are currently disallowed in sharded schemas because
	// of the implications the deletion part may have on vindexes.
	// If you add fields here, consider adding them to calls to validateUnshardedRoute.
	Insert struct {
		Action     string
		Comments   Comments
		Ignore     string
		Table      TableName
		Partitions Partitions
		Columns    Columns
		Rows       InsertRows
		OnDup      OnDup
	}

	// Update represents an UPDATE statement.
	// If you add fields here, consider adding them to calls to validateUnshardedRoute.
	Update struct {
		Comments   Comments
		Ignore     string
		TableExprs TableExprs
		Exprs      UpdateExprs
		Where      *Where
		OrderBy    OrderBy
		Limit      *Limit
	}

	// Delete represents a DELETE statement.
	// If you add fields here, consider adding them to calls to validateUnshardedRoute.
	Delete struct {
		Comments   Comments
		Targets    TableNames
		TableExprs TableExprs
		Partitions Partitions
		Where      *Where
		OrderBy    OrderBy
		Limit      *Limit
	}

	// Set represents a SET statement.
	Set struct {
		Comments Comments
		Exprs    SetExprs
		Scope    string
	}

	// DBDDL represents a CREATE, DROP, or ALTER database statement.
	DBDDL struct {
		Action   string
		DBName   string
		IfExists bool
		Collate  string
		Charset  string
	}

	// DDL represents a CREATE, ALTER, DROP, RENAME, TRUNCATE or ANALYZE statement.
	DDL struct {
		Action string

		// FromTables is set if Action is RenameStr or DropStr.
		FromTables TableNames

		// ToTables is set if Action is RenameStr.
		ToTables TableNames

		// Table is set if Action is other than RenameStr or DropStr.
		Table TableName

		// The following fields are set if a DDL was fully analyzed.
		IfExists      bool
		TableSpec     *TableSpec
		OptLike       *OptLike
		PartitionSpec *PartitionSpec

		// VindexSpec is set for CreateVindexStr, DropVindexStr, AddColVindexStr, DropColVindexStr.
		VindexSpec *VindexSpec

		// VindexCols is set for AddColVindexStr.
		VindexCols []ColIdent

		// AutoIncSpec is set for AddAutoIncStr.
		AutoIncSpec *AutoIncSpec
	}

	// ParenSelect is a parenthesized SELECT statement.
	ParenSelect struct {
		Select SelectStatement
	}

	// Show represents a show statement.
	Show struct {
		Type                   string
		OnTable                TableName
		Table                  TableName
		ShowTablesOpt          *ShowTablesOpt
		Scope                  string
		ShowCollationFilterOpt *Expr // TODO: this should not be a pointer
	}

	// Use represents a use statement.
	Use struct {
		DBName TableIdent
	}

	// Begin represents a Begin statement.
	Begin struct{}

	// Commit represents a Commit statement.
	Commit struct{}

	// Rollback represents a Rollback statement.
	Rollback struct{}

	// OtherRead represents a DESCRIBE, or EXPLAIN statement.
	// It should be used only as an indicator. It does not contain
	// the full AST for the statement.
	OtherRead struct{}

	// OtherAdmin represents a misc statement that relies on ADMIN privileges,
	// such as REPAIR, OPTIMIZE, or TRUNCATE statement.
	// It should be used only as an indicator. It does not contain
	// the full AST for the statement.
	OtherAdmin struct{}
)

func (*Union) iStatement()             {}
func (*Select) iStatement()            {}
func (*Stream) iStatement()            {}
func (*Insert) iStatement()            {}
func (*Update) iStatement()            {}
func (*Delete) iStatement()            {}
func (*Set) iStatement()               {}
func (*DBDDL) iStatement()             {}
func (*DDL) iStatement()               {}
func (*Show) iStatement()              {}
func (*Use) iStatement()               {}
func (*Begin) iStatement()             {}
func (*Commit) iStatement()            {}
func (*Rollback) iStatement()          {}
func (*OtherRead) iStatement()         {}
func (*OtherAdmin) iStatement()        {}
func (*Select) iSelectStatement()      {}
func (*Union) iSelectStatement()       {}
func (*ParenSelect) iSelectStatement() {}

// ParenSelect can actually not be a top level statement,
// but we have to allow it because it's a requirement
// of SelectStatement.
func (*ParenSelect) iStatement() {}

// InsertRows represents the rows for an INSERT statement.
type InsertRows interface {
	iInsertRows()
	SQLNode
}

func (*Select) iInsertRows()      {}
func (*Union) iInsertRows()       {}
func (Values) iInsertRows()       {}
func (*ParenSelect) iInsertRows() {}

// OptLike works for create table xxx like xxx
type OptLike struct {
	LikeTable TableName
}

// PartitionSpec describe partition actions (for alter and create)
type PartitionSpec struct {
	Action      string
	Name        ColIdent
	Definitions []*PartitionDefinition
}

// PartitionDefinition describes a very minimal partition definition
type PartitionDefinition struct {
	Name     ColIdent
	Limit    Expr
	Maxvalue bool
}

// TableSpec describes the structure of a table from a CREATE TABLE statement
type TableSpec struct {
	Columns     []*ColumnDefinition
	Indexes     []*IndexDefinition
	Constraints []*ConstraintDefinition
	Options     string
}

// ColumnDefinition describes a column in a CREATE TABLE statement
type ColumnDefinition struct {
	Name ColIdent
	// TODO: Should this not be a reference?
	Type ColumnType
}

// ColumnType represents a sql type in a CREATE TABLE statement
// All optional fields are nil if not specified
type ColumnType struct {
	// The base type string
	Type string

	// Generic field options.
	NotNull       BoolVal
	Autoincrement BoolVal
	Default       Expr
	OnUpdate      Expr
	Comment       *SQLVal

	// Numeric field options
	Length   *SQLVal
	Unsigned BoolVal
	Zerofill BoolVal
	Scale    *SQLVal

	// Text field options
	Charset string
	Collate string

	// Enum values
	EnumValues []string

	// Key specification
	KeyOpt ColumnKeyOption
}

// IndexDefinition describes an index in a CREATE TABLE statement
type IndexDefinition struct {
	Info    *IndexInfo
	Columns []*IndexColumn
	Options []*IndexOption
}

// IndexInfo describes the name and type of an index in a CREATE TABLE statement
type IndexInfo struct {
	Type    string
	Name    ColIdent
	Primary bool
	Spatial bool
	Unique  bool
}

// VindexSpec defines a vindex for a CREATE VINDEX or DROP VINDEX statement
type VindexSpec struct {
	Name   ColIdent
	Type   ColIdent
	Params []VindexParam
}

// AutoIncSpec defines and autoincrement value for a ADD AUTO_INCREMENT statement
type AutoIncSpec struct {
	Column   ColIdent
	Sequence TableName
}

// VindexParam defines a key/value parameter for a CREATE VINDEX statement
type VindexParam struct {
	Key ColIdent
	Val string
}

// ConstraintDefinition describes a constraint in a CREATE TABLE statement
type ConstraintDefinition struct {
	Name    string
	Details ConstraintInfo
}

type (
	// ConstraintInfo details a constraint in a CREATE TABLE statement
	ConstraintInfo interface {
		SQLNode
		iConstraintInfo()
	}

	// ForeignKeyDefinition describes a foreign key in a CREATE TABLE statement
	ForeignKeyDefinition struct {
		Source            Columns
		ReferencedTable   TableName
		ReferencedColumns Columns
		OnDelete          ReferenceAction
		OnUpdate          ReferenceAction
	}
)

// ShowFilter is show tables filter
type ShowFilter struct {
	Like   string
	Filter Expr
}

// Comments represents a list of comments.
type Comments [][]byte

// SelectExprs represents SELECT expressions.
type SelectExprs []SelectExpr

type (
	// SelectExpr represents a SELECT expression.
	SelectExpr interface {
		iSelectExpr()
		SQLNode
	}

	// StarExpr defines a '*' or 'table.*' expression.
	StarExpr struct {
		TableName TableName
	}

	// AliasedExpr defines an aliased SELECT expression.
	AliasedExpr struct {
		Expr Expr
		As   ColIdent
	}

	// Nextval defines the NEXT VALUE expression.
	Nextval struct {
		Expr Expr
	}
)

func (*StarExpr) iSelectExpr()    {}
func (*AliasedExpr) iSelectExpr() {}
func (Nextval) iSelectExpr()      {}

// Columns represents an insert column list.
type Columns []ColIdent

// Partitions is a type alias for Columns so we can handle printing efficiently
type Partitions Columns

// TableExprs represents a list of table expressions.
type TableExprs []TableExpr

type (
	// TableExpr represents a table expression.
	TableExpr interface {
		iTableExpr()
		SQLNode
	}

	// AliasedTableExpr represents a table expression
	// coupled with an optional alias or index hint.
	// If As is empty, no alias was used.
	AliasedTableExpr struct {
		Expr       SimpleTableExpr
		Partitions Partitions
		As         TableIdent
		Hints      *IndexHints
	}

	// JoinTableExpr represents a TableExpr that's a JOIN operation.
	JoinTableExpr struct {
		LeftExpr  TableExpr
		Join      string
		RightExpr TableExpr
		Condition JoinCondition
	}

	// ParenTableExpr represents a parenthesized list of TableExpr.
	ParenTableExpr struct {
		Exprs TableExprs
	}
)

func (*AliasedTableExpr) iTableExpr() {}
func (*ParenTableExpr) iTableExpr()   {}
func (*JoinTableExpr) iTableExpr()    {}

type (
	// SimpleTableExpr represents a simple table expression.
	SimpleTableExpr interface {
		iSimpleTableExpr()
		SQLNode
	}

	// TableName represents a table  name.
	// Qualifier, if specified, represents a database or keyspace.
	// TableName is a value struct whose fields are case sensitive.
	// This means two TableName vars can be compared for equality
	// and a TableName can also be used as key in a map.
	TableName struct {
		Name, Qualifier TableIdent
	}

	// Subquery represents a subquery.
	Subquery struct {
		Select SelectStatement
	}
)

func (TableName) iSimpleTableExpr() {}
func (*Subquery) iSimpleTableExpr() {}

// TableNames is a list of TableName.
type TableNames []TableName

// JoinCondition represents the join conditions (either a ON or USING clause)
// of a JoinTableExpr.
type JoinCondition struct {
	On    Expr
	Using Columns
}

// IndexHints represents a list of index hints.
type IndexHints struct {
	Type    string
	Indexes []ColIdent
}

// Where represents a WHERE or HAVING clause.
type Where struct {
	Type string
	Expr Expr
}

// *********** Expressions
type (
	// Expr represents an expression.
	Expr interface {
		iExpr()
		SQLNode
	}

	// AndExpr represents an AND expression.
	AndExpr struct {
		Left, Right Expr
	}

	// OrExpr represents an OR expression.
	OrExpr struct {
		Left, Right Expr
	}

	// NotExpr represents a NOT expression.
	NotExpr struct {
		Expr Expr
	}

	// ParenExpr represents a parenthesized boolean expression.
	ParenExpr struct {
		Expr Expr
	}

	// ComparisonExpr represents a two-value comparison expression.
	ComparisonExpr struct {
		Operator    string
		Left, Right Expr
		Escape      Expr
	}

	// RangeCond represents a BETWEEN or a NOT BETWEEN expression.
	RangeCond struct {
		Operator string
		Left     Expr
		From, To Expr
	}

	// IsExpr represents an IS ... or an IS NOT ... expression.
	IsExpr struct {
		Operator string
		Expr     Expr
	}

	// ExistsExpr represents an EXISTS expression.
	ExistsExpr struct {
		Subquery *Subquery
	}

	// SQLVal represents a single value.
	SQLVal struct {
		Type ValType
		Val  []byte
	}

	// NullVal represents a NULL value.
	NullVal struct{}

	// BoolVal is true or false.
	BoolVal bool

	// ColName represents a column name.
	ColName struct {
		// Metadata is not populated by the parser.
		// It's a placeholder for analyzers to store
		// additional data, typically info about which
		// table or column this node references.
		Metadata  interface{}
		Name      ColIdent
		Qualifier TableName
	}

	// ColTuple represents a list of column values.
	// It can be ValTuple, Subquery, ListArg.
	ColTuple interface {
		iColTuple()
		Expr
	}

	// ListArg represents a named list argument.
	ListArg []byte

	// ValTuple represents a tuple of actual values.
	ValTuple Exprs

	// BinaryExpr represents a binary value expression.
	BinaryExpr struct {
		Operator    string
		Left, Right Expr
	}

	// UnaryExpr represents a unary value expression.
	UnaryExpr struct {
		Operator string
		Expr     Expr
	}

	// IntervalExpr represents a date-time INTERVAL expression.
	IntervalExpr struct {
		Expr Expr
		Unit string
	}

	// TimestampFuncExpr represents the function and arguments for TIMESTAMP{ADD,DIFF} functions.
	TimestampFuncExpr struct {
		Name  string
		Expr1 Expr
		Expr2 Expr
		Unit  string
	}

	// CollateExpr represents dynamic collate operator.
	CollateExpr struct {
		Expr    Expr
		Charset string
	}

	// FuncExpr represents a function call.
	FuncExpr struct {
		Qualifier TableIdent
		Name      ColIdent
		Distinct  bool
		Exprs     SelectExprs
	}

	// GroupConcatExpr represents a call to GROUP_CONCAT
	GroupConcatExpr struct {
		Distinct  string
		Exprs     SelectExprs
		OrderBy   OrderBy
		Separator string
		Limit     *Limit
	}

	// ValuesFuncExpr represents a function call.
	ValuesFuncExpr struct {
		Name *ColName
	}

	// SubstrExpr represents a call to SubstrExpr(column, value_expression) or SubstrExpr(column, value_expression,value_expression)
	// also supported syntax SubstrExpr(column from value_expression for value_expression).
	// Additionally to column names, SubstrExpr is also supported for string values, e.g.:
	// SubstrExpr('static string value', value_expression, value_expression)
	// In this case StrVal will be set instead of Name.
	SubstrExpr struct {
		Name   *ColName
		StrVal *SQLVal
		From   Expr
		To     Expr
	}

	// ConvertExpr represents a call to CONVERT(expr, type)
	// or it's equivalent CAST(expr AS type). Both are rewritten to the former.
	ConvertExpr struct {
		Expr Expr
		Type *ConvertType
	}

	// ConvertUsingExpr represents a call to CONVERT(expr USING charset).
	ConvertUsingExpr struct {
		Expr Expr
		Type string
	}

	// MatchExpr represents a call to the MATCH function
	MatchExpr struct {
		Columns SelectExprs
		Expr    Expr
		Option  string
	}

	// CaseExpr represents a CASE expression.
	CaseExpr struct {
		Expr  Expr
		Whens []*When
		Else  Expr
	}

	// Default represents a DEFAULT expression.
	Default struct {
		ColName string
	}

	// When represents a WHEN sub-expression.
	When struct {
		Cond Expr
		Val  Expr
	}

	// CurTimeFuncExpr represents the function and arguments for CURRENT DATE/TIME functions
	// supported functions are documented in the grammar
	CurTimeFuncExpr struct {
		Name ColIdent
		Fsp  Expr // fractional seconds precision, integer from 0 to 6
	}
)

// iExpr ensures that only expressions nodes can be assigned to a Expr
func (*AndExpr) iExpr()           {}
func (*OrExpr) iExpr()            {}
func (*NotExpr) iExpr()           {}
func (*ParenExpr) iExpr()         {}
func (*ComparisonExpr) iExpr()    {}
func (*RangeCond) iExpr()         {}
func (*IsExpr) iExpr()            {}
func (*ExistsExpr) iExpr()        {}
func (*SQLVal) iExpr()            {}
func (*NullVal) iExpr()           {}
func (BoolVal) iExpr()            {}
func (*ColName) iExpr()           {}
func (ValTuple) iExpr()           {}
func (*Subquery) iExpr()          {}
func (ListArg) iExpr()            {}
func (*BinaryExpr) iExpr()        {}
func (*UnaryExpr) iExpr()         {}
func (*IntervalExpr) iExpr()      {}
func (*CollateExpr) iExpr()       {}
func (*FuncExpr) iExpr()          {}
func (*TimestampFuncExpr) iExpr() {}
func (*CurTimeFuncExpr) iExpr()   {}
func (*CaseExpr) iExpr()          {}
func (*ValuesFuncExpr) iExpr()    {}
func (*ConvertExpr) iExpr()       {}
func (*SubstrExpr) iExpr()        {}
func (*ConvertUsingExpr) iExpr()  {}
func (*MatchExpr) iExpr()         {}
func (*GroupConcatExpr) iExpr()   {}
func (*Default) iExpr()           {}

// Exprs represents a list of value expressions.
// It's not a valid expression because it's not parenthesized.
type Exprs []Expr

func (ValTuple) iColTuple()  {}
func (*Subquery) iColTuple() {}
func (ListArg) iColTuple()   {}

// ConvertType represents the type in call to CONVERT(expr, type)
type ConvertType struct {
	Type     string
	Length   *SQLVal
	Scale    *SQLVal
	Operator string
	Charset  string
}

// GroupBy represents a GROUP BY clause.
type GroupBy []Expr

// OrderBy represents an ORDER By clause.
type OrderBy []*Order

// Order represents an ordering expression.
type Order struct {
	Expr      Expr
	Direction string
}

// Limit represents a LIMIT clause.
type Limit struct {
	Offset, Rowcount Expr
}

// Values represents a VALUES clause.
type Values []ValTuple

// UpdateExprs represents a list of update expressions.
type UpdateExprs []*UpdateExpr

// UpdateExpr represents an update expression.
type UpdateExpr struct {
	Name *ColName
	Expr Expr
}

// SetExprs represents a list of set expressions.
type SetExprs []*SetExpr

// SetExpr represents a set expression.
type SetExpr struct {
	Name ColIdent
	Expr Expr
}

// OnDup represents an ON DUPLICATE KEY clause.
type OnDup UpdateExprs

// ColIdent is a case insensitive SQL identifier. It will be escaped with
// backquotes if necessary.
type ColIdent struct {
	// This artifact prevents this struct from being compared
	// with itself. It consumes no space as long as it's not the
	// last field in the struct.
	_            [0]struct{ _ []byte }
	val, lowered string
	at           atCount
}

// TableIdent is a case sensitive SQL identifier. It will be escaped with
// backquotes if necessary.
type TableIdent struct {
	v string
}

// Here follow all the Format implementations for AST nodes

// Format formats the node.
func (node *Select) Format(buf *TrackedBuffer) {
	buf.Myprintf("select %v%s%s%s%v from %v%v%v%v%v%v%s",
		node.Comments, node.Cache, node.Distinct, node.Hints, node.SelectExprs,
		node.From, node.Where,
		node.GroupBy, node.Having, node.OrderBy,
		node.Limit, node.Lock)
}

// Format formats the node.
func (node *ParenSelect) Format(buf *TrackedBuffer) {
	buf.Myprintf("(%v)", node.Select)
}

// Format formats the node.
func (node *Union) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v %s %v%v%v%s", node.Left, node.Type, node.Right,
		node.OrderBy, node.Limit, node.Lock)
}

// Format formats the node.
func (node *Stream) Format(buf *TrackedBuffer) {
	buf.Myprintf("stream %v%v from %v",
		node.Comments, node.SelectExpr, node.Table)
}

// Format formats the node.
func (node *Insert) Format(buf *TrackedBuffer) {
	buf.Myprintf("%s %v%sinto %v%v%v %v%v",
		node.Action,
		node.Comments, node.Ignore,
		node.Table, node.Partitions, node.Columns, node.Rows, node.OnDup)
}

// Format formats the node.
func (node *Update) Format(buf *TrackedBuffer) {
	buf.Myprintf("update %v%s%v set %v%v%v%v",
		node.Comments, node.Ignore, node.TableExprs,
		node.Exprs, node.Where, node.OrderBy, node.Limit)
}

// Format formats the node.
func (node *Delete) Format(buf *TrackedBuffer) {
	buf.Myprintf("delete %v", node.Comments)
	if node.Targets != nil {
		buf.Myprintf("%v ", node.Targets)
	}
	buf.Myprintf("from %v%v%v%v%v", node.TableExprs, node.Partitions, node.Where, node.OrderBy, node.Limit)
}

// Format formats the node.
func (node *Set) Format(buf *TrackedBuffer) {
	if node.Scope == "" {
		buf.Myprintf("set %v%v", node.Comments, node.Exprs)
	} else {
		buf.Myprintf("set %v%s %v", node.Comments, node.Scope, node.Exprs)
	}
}

// Format formats the node.
func (node *DBDDL) Format(buf *TrackedBuffer) {
	switch node.Action {
	case CreateStr, AlterStr:
		buf.WriteString(fmt.Sprintf("%s database %s", node.Action, node.DBName))
	case DropStr:
		exists := ""
		if node.IfExists {
			exists = " if exists"
		}
		buf.WriteString(fmt.Sprintf("%s database%s %v", node.Action, exists, node.DBName))
	}
}

// Format formats the node.
func (node *DDL) Format(buf *TrackedBuffer) {
	switch node.Action {
	case CreateStr:
		if node.OptLike != nil {
			buf.Myprintf("%s table %v %v", node.Action, node.Table, node.OptLike)
		} else if node.TableSpec != nil {
			buf.Myprintf("%s table %v %v", node.Action, node.Table, node.TableSpec)
		} else {
			buf.Myprintf("%s table %v", node.Action, node.Table)
		}
	case DropStr:
		exists := ""
		if node.IfExists {
			exists = " if exists"
		}
		buf.Myprintf("%s table%s %v", node.Action, exists, node.FromTables)
	case RenameStr:
		buf.Myprintf("%s table %v to %v", node.Action, node.FromTables[0], node.ToTables[0])
		for i := 1; i < len(node.FromTables); i++ {
			buf.Myprintf(", %v to %v", node.FromTables[i], node.ToTables[i])
		}
	case AlterStr:
		if node.PartitionSpec != nil {
			buf.Myprintf("%s table %v %v", node.Action, node.Table, node.PartitionSpec)
		} else {
			buf.Myprintf("%s table %v", node.Action, node.Table)
		}
	case FlushStr:
		buf.Myprintf("%s", node.Action)
	case CreateVindexStr:
		buf.Myprintf("alter vschema create vindex %v %v", node.Table, node.VindexSpec)
	case DropVindexStr:
		buf.Myprintf("alter vschema drop vindex %v", node.Table)
	case AddVschemaTableStr:
		buf.Myprintf("alter vschema add table %v", node.Table)
	case DropVschemaTableStr:
		buf.Myprintf("alter vschema drop table %v", node.Table)
	case AddColVindexStr:
		buf.Myprintf("alter vschema on %v add vindex %v (", node.Table, node.VindexSpec.Name)
		for i, col := range node.VindexCols {
			if i != 0 {
				buf.Myprintf(", %v", col)
			} else {
				buf.Myprintf("%v", col)
			}
		}
		buf.Myprintf(")")
		if node.VindexSpec.Type.String() != "" {
			buf.Myprintf(" %v", node.VindexSpec)
		}
	case DropColVindexStr:
		buf.Myprintf("alter vschema on %v drop vindex %v", node.Table, node.VindexSpec.Name)
	case AddSequenceStr:
		buf.Myprintf("alter vschema add sequence %v", node.Table)
	case AddAutoIncStr:
		buf.Myprintf("alter vschema on %v add auto_increment %v", node.Table, node.AutoIncSpec)
	default:
		buf.Myprintf("%s table %v", node.Action, node.Table)
	}
}

// Format formats the node.
func (node *OptLike) Format(buf *TrackedBuffer) {
	buf.Myprintf("like %v", node.LikeTable)
}

// Format formats the node.
func (node *PartitionSpec) Format(buf *TrackedBuffer) {
	switch node.Action {
	case ReorganizeStr:
		buf.Myprintf("%s %v into (", node.Action, node.Name)
		var prefix string
		for _, pd := range node.Definitions {
			buf.Myprintf("%s%v", prefix, pd)
			prefix = ", "
		}
		buf.Myprintf(")")
	default:
		panic("unimplemented")
	}
}

// Format formats the node
func (node *PartitionDefinition) Format(buf *TrackedBuffer) {
	if !node.Maxvalue {
		buf.Myprintf("partition %v values less than (%v)", node.Name, node.Limit)
	} else {
		buf.Myprintf("partition %v values less than (maxvalue)", node.Name)
	}
}

// Format formats the node.
func (ts *TableSpec) Format(buf *TrackedBuffer) {
	buf.Myprintf("(\n")
	for i, col := range ts.Columns {
		if i == 0 {
			buf.Myprintf("\t%v", col)
		} else {
			buf.Myprintf(",\n\t%v", col)
		}
	}
	for _, idx := range ts.Indexes {
		buf.Myprintf(",\n\t%v", idx)
	}
	for _, c := range ts.Constraints {
		buf.Myprintf(",\n\t%v", c)
	}

	buf.Myprintf("\n)%s", strings.Replace(ts.Options, ", ", ",\n  ", -1))
}

// Format formats the node.
func (col *ColumnDefinition) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v %v", col.Name, &col.Type)
}

// Format returns a canonical string representation of the type and all relevant options
func (ct *ColumnType) Format(buf *TrackedBuffer) {
	buf.Myprintf("%s", ct.Type)

	if ct.Length != nil && ct.Scale != nil {
		buf.Myprintf("(%v,%v)", ct.Length, ct.Scale)

	} else if ct.Length != nil {
		buf.Myprintf("(%v)", ct.Length)
	}

	if ct.EnumValues != nil {
		buf.Myprintf("(%s)", strings.Join(ct.EnumValues, ", "))
	}

	opts := make([]string, 0, 16)
	if ct.Unsigned {
		opts = append(opts, keywordStrings[UNSIGNED])
	}
	if ct.Zerofill {
		opts = append(opts, keywordStrings[ZEROFILL])
	}
	if ct.Charset != "" {
		opts = append(opts, keywordStrings[CHARACTER], keywordStrings[SET], ct.Charset)
	}
	if ct.Collate != "" {
		opts = append(opts, keywordStrings[COLLATE], ct.Collate)
	}
	if ct.NotNull {
		opts = append(opts, keywordStrings[NOT], keywordStrings[NULL])
	}
	if ct.Default != nil {
		opts = append(opts, keywordStrings[DEFAULT], String(ct.Default))
	}
	if ct.OnUpdate != nil {
		opts = append(opts, keywordStrings[ON], keywordStrings[UPDATE], String(ct.OnUpdate))
	}
	if ct.Autoincrement {
		opts = append(opts, keywordStrings[AUTO_INCREMENT])
	}
	if ct.Comment != nil {
		opts = append(opts, keywordStrings[COMMENT_KEYWORD], String(ct.Comment))
	}
	if ct.KeyOpt == colKeyPrimary {
		opts = append(opts, keywordStrings[PRIMARY], keywordStrings[KEY])
	}
	if ct.KeyOpt == colKeyUnique {
		opts = append(opts, keywordStrings[UNIQUE])
	}
	if ct.KeyOpt == colKeyUniqueKey {
		opts = append(opts, keywordStrings[UNIQUE], keywordStrings[KEY])
	}
	if ct.KeyOpt == colKeySpatialKey {
		opts = append(opts, keywordStrings[SPATIAL], keywordStrings[KEY])
	}
	if ct.KeyOpt == colKey {
		opts = append(opts, keywordStrings[KEY])
	}

	if len(opts) != 0 {
		buf.Myprintf(" %s", strings.Join(opts, " "))
	}
}

// Format formats the node.
func (idx *IndexDefinition) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v (", idx.Info)
	for i, col := range idx.Columns {
		if i != 0 {
			buf.Myprintf(", %v", col.Column)
		} else {
			buf.Myprintf("%v", col.Column)
		}
		if col.Length != nil {
			buf.Myprintf("(%v)", col.Length)
		}
	}
	buf.Myprintf(")")

	for _, opt := range idx.Options {
		buf.Myprintf(" %s", opt.Name)
		if opt.Using != "" {
			buf.Myprintf(" %s", opt.Using)
		} else {
			buf.Myprintf(" %v", opt.Value)
		}
	}
}

// Format formats the node.
func (ii *IndexInfo) Format(buf *TrackedBuffer) {
	if ii.Primary {
		buf.Myprintf("%s", ii.Type)
	} else {
		buf.Myprintf("%s", ii.Type)
		if !ii.Name.IsEmpty() {
			buf.Myprintf(" %v", ii.Name)
		}
	}
}

// Format formats the node.
func (node *AutoIncSpec) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v ", node.Column)
	buf.Myprintf("using %v", node.Sequence)
}

// Format formats the node. The "CREATE VINDEX" preamble was formatted in
// the containing DDL node Format, so this just prints the type, any
// parameters, and optionally the owner
func (node *VindexSpec) Format(buf *TrackedBuffer) {
	buf.Myprintf("using %v", node.Type)

	numParams := len(node.Params)
	if numParams != 0 {
		buf.Myprintf(" with ")
		for i, p := range node.Params {
			if i != 0 {
				buf.Myprintf(", ")
			}
			buf.Myprintf("%v", p)
		}
	}
}

// Format formats the node.
func (node VindexParam) Format(buf *TrackedBuffer) {
	buf.Myprintf("%s=%s", node.Key.String(), node.Val)
}

// Format formats the node.
func (c *ConstraintDefinition) Format(buf *TrackedBuffer) {
	if c.Name != "" {
		buf.Myprintf("constraint %s ", c.Name)
	}
	c.Details.Format(buf)
}

// Format formats the node.
func (a ReferenceAction) Format(buf *TrackedBuffer) {
	switch a {
	case Restrict:
		buf.WriteString("restrict")
	case Cascade:
		buf.WriteString("cascade")
	case NoAction:
		buf.WriteString("no action")
	case SetNull:
		buf.WriteString("set null")
	case SetDefault:
		buf.WriteString("set default")
	}
}

// Format formats the node.
func (f *ForeignKeyDefinition) Format(buf *TrackedBuffer) {
	buf.Myprintf("foreign key %v references %v %v", f.Source, f.ReferencedTable, f.ReferencedColumns)
	if f.OnDelete != DefaultAction {
		buf.Myprintf(" on delete %v", f.OnDelete)
	}
	if f.OnUpdate != DefaultAction {
		buf.Myprintf(" on update %v", f.OnUpdate)
	}
}

// Format formats the node.
func (node *Show) Format(buf *TrackedBuffer) {
	nodeType := strings.ToLower(node.Type)
	if (nodeType == "tables" || nodeType == "columns" || nodeType == "fields" || nodeType == "index" || nodeType == "keys") && node.ShowTablesOpt != nil {
		opt := node.ShowTablesOpt
		buf.Myprintf("show %s%s", opt.Full, nodeType)
		if (nodeType == "columns" || nodeType == "fields" || nodeType == "index" || nodeType == "keys") && node.HasOnTable() {
			buf.Myprintf(" from %v", node.OnTable)
		}
		if opt.DbName != "" {
			buf.Myprintf(" from %s", opt.DbName)
		}
		buf.Myprintf("%v", opt.Filter)
		return
	}
	if node.Scope == "" {
		buf.Myprintf("show %s", nodeType)
	} else {
		buf.Myprintf("show %s %s", node.Scope, nodeType)
	}
	if node.HasOnTable() {
		buf.Myprintf(" on %v", node.OnTable)
	}
	if nodeType == "collation" && node.ShowCollationFilterOpt != nil {
		buf.Myprintf(" where %v", *node.ShowCollationFilterOpt)
	}
	if nodeType == "charset" && node.ShowTablesOpt != nil {
		buf.Myprintf("%v", node.ShowTablesOpt.Filter)
	}
	if node.HasTable() {
		buf.Myprintf(" %v", node.Table)
	}
}

// Format formats the node.
func (node *ShowFilter) Format(buf *TrackedBuffer) {
	if node == nil {
		return
	}
	if node.Like != "" {
		buf.Myprintf(" like '%s'", node.Like)
	} else {
		buf.Myprintf(" where %v", node.Filter)
	}
}

// Format formats the node.
func (node *Use) Format(buf *TrackedBuffer) {
	if node.DBName.v != "" {
		buf.Myprintf("use %v", node.DBName)
	} else {
		buf.Myprintf("use")
	}
}

// Format formats the node.
func (node *Commit) Format(buf *TrackedBuffer) {
	buf.WriteString("commit")
}

// Format formats the node.
func (node *Begin) Format(buf *TrackedBuffer) {
	buf.WriteString("begin")
}

// Format formats the node.
func (node *Rollback) Format(buf *TrackedBuffer) {
	buf.WriteString("rollback")
}

// Format formats the node.
func (node *OtherRead) Format(buf *TrackedBuffer) {
	buf.WriteString("otherread")
}

// Format formats the node.
func (node *OtherAdmin) Format(buf *TrackedBuffer) {
	buf.WriteString("otheradmin")
}

// Format formats the node.
func (node Comments) Format(buf *TrackedBuffer) {
	for _, c := range node {
		buf.Myprintf("%s ", c)
	}
}

// Format formats the node.
func (node SelectExprs) Format(buf *TrackedBuffer) {
	var prefix string
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node *StarExpr) Format(buf *TrackedBuffer) {
	if !node.TableName.IsEmpty() {
		buf.Myprintf("%v.", node.TableName)
	}
	buf.Myprintf("*")
}

// Format formats the node.
func (node *AliasedExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v", node.Expr)
	if !node.As.IsEmpty() {
		buf.Myprintf(" as %v", node.As)
	}
}

// Format formats the node.
func (node Nextval) Format(buf *TrackedBuffer) {
	buf.Myprintf("next %v values", node.Expr)
}

// Format formats the node.
func (node Columns) Format(buf *TrackedBuffer) {
	if node == nil {
		return
	}
	prefix := "("
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
	buf.WriteString(")")
}

// Format formats the node
func (node Partitions) Format(buf *TrackedBuffer) {
	if node == nil {
		return
	}
	prefix := " partition ("
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
	buf.WriteString(")")
}

// Format formats the node.
func (node TableExprs) Format(buf *TrackedBuffer) {
	var prefix string
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node *AliasedTableExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v%v", node.Expr, node.Partitions)
	if !node.As.IsEmpty() {
		buf.Myprintf(" as %v", node.As)
	}
	if node.Hints != nil {
		// Hint node provides the space padding.
		buf.Myprintf("%v", node.Hints)
	}
}

// Format formats the node.
func (node TableNames) Format(buf *TrackedBuffer) {
	var prefix string
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node TableName) Format(buf *TrackedBuffer) {
	if node.IsEmpty() {
		return
	}
	if !node.Qualifier.IsEmpty() {
		buf.Myprintf("%v.", node.Qualifier)
	}
	buf.Myprintf("%v", node.Name)
}

// Format formats the node.
func (node *ParenTableExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("(%v)", node.Exprs)
}

// Format formats the node.
func (node JoinCondition) Format(buf *TrackedBuffer) {
	if node.On != nil {
		buf.Myprintf(" on %v", node.On)
	}
	if node.Using != nil {
		buf.Myprintf(" using %v", node.Using)
	}
}

// Format formats the node.
func (node *JoinTableExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v %s %v%v", node.LeftExpr, node.Join, node.RightExpr, node.Condition)
}

// Format formats the node.
func (node *IndexHints) Format(buf *TrackedBuffer) {
	buf.Myprintf(" %sindex ", node.Type)
	if len(node.Indexes) == 0 {
		buf.Myprintf("()")
	} else {
		prefix := "("
		for _, n := range node.Indexes {
			buf.Myprintf("%s%v", prefix, n)
			prefix = ", "
		}
		buf.Myprintf(")")
	}
}

// Format formats the node.
func (node *Where) Format(buf *TrackedBuffer) {
	if node == nil || node.Expr == nil {
		return
	}
	buf.Myprintf(" %s %v", node.Type, node.Expr)
}

// Format formats the node.
func (node Exprs) Format(buf *TrackedBuffer) {
	var prefix string
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node *AndExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v and %v", node.Left, node.Right)
}

// Format formats the node.
func (node *OrExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v or %v", node.Left, node.Right)
}

// Format formats the node.
func (node *NotExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("not %v", node.Expr)
}

// Format formats the node.
func (node *ParenExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("(%v)", node.Expr)
}

// Format formats the node.
func (node *ComparisonExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v %s %v", node.Left, node.Operator, node.Right)
	if node.Escape != nil {
		buf.Myprintf(" escape %v", node.Escape)
	}
}

// Format formats the node.
func (node *RangeCond) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v %s %v and %v", node.Left, node.Operator, node.From, node.To)
}

// Format formats the node.
func (node *IsExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v %s", node.Expr, node.Operator)
}

// Format formats the node.
func (node *ExistsExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("exists %v", node.Subquery)
}

// Format formats the node.
func (node *SQLVal) Format(buf *TrackedBuffer) {
	switch node.Type {
	case StrVal:
		sqltypes.MakeTrusted(sqltypes.VarBinary, node.Val).EncodeSQL(buf)
	case IntVal, FloatVal, HexNum:
		buf.Myprintf("%s", []byte(node.Val))
	case HexVal:
		buf.Myprintf("X'%s'", []byte(node.Val))
	case BitVal:
		buf.Myprintf("B'%s'", []byte(node.Val))
	case ValArg:
		buf.WriteArg(string(node.Val))
	default:
		panic("unexpected")
	}
}

// Format formats the node.
func (node *NullVal) Format(buf *TrackedBuffer) {
	buf.Myprintf("null")
}

// Format formats the node.
func (node BoolVal) Format(buf *TrackedBuffer) {
	if node {
		buf.Myprintf("true")
	} else {
		buf.Myprintf("false")
	}
}

// Format formats the node.
func (node *ColName) Format(buf *TrackedBuffer) {
	if !node.Qualifier.IsEmpty() {
		buf.Myprintf("%v.", node.Qualifier)
	}
	buf.Myprintf("%v", node.Name)
}

// Format formats the node.
func (node ValTuple) Format(buf *TrackedBuffer) {
	buf.Myprintf("(%v)", Exprs(node))
}

// Format formats the node.
func (node *Subquery) Format(buf *TrackedBuffer) {
	buf.Myprintf("(%v)", node.Select)
}

// Format formats the node.
func (node ListArg) Format(buf *TrackedBuffer) {
	buf.WriteArg(string(node))
}

// Format formats the node.
func (node *BinaryExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v %s %v", node.Left, node.Operator, node.Right)
}

// Format formats the node.
func (node *UnaryExpr) Format(buf *TrackedBuffer) {
	if _, unary := node.Expr.(*UnaryExpr); unary {
		buf.Myprintf("%s %v", node.Operator, node.Expr)
		return
	}
	buf.Myprintf("%s%v", node.Operator, node.Expr)
}

// Format formats the node.
func (node *IntervalExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("interval %v %s", node.Expr, node.Unit)
}

// Format formats the node.
func (node *TimestampFuncExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%s(%s, %v, %v)", node.Name, node.Unit, node.Expr1, node.Expr2)
}

// Format formats the node.
func (node *CurTimeFuncExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%s(%v)", node.Name.String(), node.Fsp)
}

// Format formats the node.
func (node *CollateExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v collate %s", node.Expr, node.Charset)
}

// Format formats the node.
func (node *FuncExpr) Format(buf *TrackedBuffer) {
	var distinct string
	if node.Distinct {
		distinct = "distinct "
	}
	if !node.Qualifier.IsEmpty() {
		buf.Myprintf("%v.", node.Qualifier)
	}
	// Function names should not be back-quoted even
	// if they match a reserved word, only if they contain illegal characters
	funcName := node.Name.String()

	if containEscapableChars(funcName, NoAt) {
		writeEscapedString(buf, funcName)
	} else {
		buf.WriteString(funcName)
	}
	buf.Myprintf("(%s%v)", distinct, node.Exprs)
}

// Format formats the node
func (node *GroupConcatExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("group_concat(%s%v%v%s%v)", node.Distinct, node.Exprs, node.OrderBy, node.Separator, node.Limit)
}

// Format formats the node.
func (node *ValuesFuncExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("values(%v)", node.Name)
}

// Format formats the node.
func (node *SubstrExpr) Format(buf *TrackedBuffer) {
	var val interface{}
	if node.Name != nil {
		val = node.Name
	} else {
		val = node.StrVal
	}

	if node.To == nil {
		buf.Myprintf("substr(%v, %v)", val, node.From)
	} else {
		buf.Myprintf("substr(%v, %v, %v)", val, node.From, node.To)
	}
}

// Format formats the node.
func (node *ConvertExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("convert(%v, %v)", node.Expr, node.Type)
}

// Format formats the node.
func (node *ConvertUsingExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("convert(%v using %s)", node.Expr, node.Type)
}

// Format formats the node.
func (node *ConvertType) Format(buf *TrackedBuffer) {
	buf.Myprintf("%s", node.Type)
	if node.Length != nil {
		buf.Myprintf("(%v", node.Length)
		if node.Scale != nil {
			buf.Myprintf(", %v", node.Scale)
		}
		buf.Myprintf(")")
	}
	if node.Charset != "" {
		buf.Myprintf("%s %s", node.Operator, node.Charset)
	}
}

// Format formats the node
func (node *MatchExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("match(%v) against (%v%s)", node.Columns, node.Expr, node.Option)
}

// Format formats the node.
func (node *CaseExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("case ")
	if node.Expr != nil {
		buf.Myprintf("%v ", node.Expr)
	}
	for _, when := range node.Whens {
		buf.Myprintf("%v ", when)
	}
	if node.Else != nil {
		buf.Myprintf("else %v ", node.Else)
	}
	buf.Myprintf("end")
}

// Format formats the node.
func (node *Default) Format(buf *TrackedBuffer) {
	buf.Myprintf("default")
	if node.ColName != "" {
		buf.Myprintf("(%s)", node.ColName)
	}
}

// Format formats the node.
func (node *When) Format(buf *TrackedBuffer) {
	buf.Myprintf("when %v then %v", node.Cond, node.Val)
}

// Format formats the node.
func (node GroupBy) Format(buf *TrackedBuffer) {
	prefix := " group by "
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node OrderBy) Format(buf *TrackedBuffer) {
	prefix := " order by "
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node *Order) Format(buf *TrackedBuffer) {
	if node, ok := node.Expr.(*NullVal); ok {
		buf.Myprintf("%v", node)
		return
	}
	if node, ok := node.Expr.(*FuncExpr); ok {
		if node.Name.Lowered() == "rand" {
			buf.Myprintf("%v", node)
			return
		}
	}

	buf.Myprintf("%v %s", node.Expr, node.Direction)
}

// Format formats the node.
func (node *Limit) Format(buf *TrackedBuffer) {
	if node == nil {
		return
	}
	buf.Myprintf(" limit ")
	if node.Offset != nil {
		buf.Myprintf("%v, ", node.Offset)
	}
	buf.Myprintf("%v", node.Rowcount)
}

// Format formats the node.
func (node Values) Format(buf *TrackedBuffer) {
	prefix := "values "
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node UpdateExprs) Format(buf *TrackedBuffer) {
	var prefix string
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node *UpdateExpr) Format(buf *TrackedBuffer) {
	buf.Myprintf("%v = %v", node.Name, node.Expr)
}

// Format formats the node.
func (node SetExprs) Format(buf *TrackedBuffer) {
	var prefix string
	for _, n := range node {
		buf.Myprintf("%s%v", prefix, n)
		prefix = ", "
	}
}

// Format formats the node.
func (node *SetExpr) Format(buf *TrackedBuffer) {
	// We don't have to backtick set variable names.
	if node.Name.EqualString("charset") || node.Name.EqualString("names") {
		buf.Myprintf("%s %v", node.Name.String(), node.Expr)
	} else if node.Name.EqualString(TransactionStr) {
		sqlVal := node.Expr.(*SQLVal)
		buf.Myprintf("%s %s", node.Name.String(), strings.ToLower(string(sqlVal.Val)))
	} else {
		buf.Myprintf("%v = %v", node.Name, node.Expr)
	}
}

// Format formats the node.
func (node OnDup) Format(buf *TrackedBuffer) {
	if node == nil {
		return
	}
	buf.Myprintf(" on duplicate key update %v", UpdateExprs(node))
}

// Format formats the node.
func (node ColIdent) Format(buf *TrackedBuffer) {
	for i := NoAt; i < node.at; i++ {
		buf.WriteByte('@')
	}
	formatID(buf, node.val, node.Lowered(), node.at)
}

// Format formats the node.
func (node TableIdent) Format(buf *TrackedBuffer) {
	formatID(buf, node.v, strings.ToLower(node.v), NoAt)
}
