package sqlparser

import (
	"encoding/hex"
	"encoding/json"
	"strings"

	"vitess.io/vitess/go/hack"
	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
	"vitess.io/vitess/go/vt/vterrors"

	"vitess.io/vitess/go/vt/log"

	"vitess.io/vitess/go/sqltypes"
	querypb "vitess.io/vitess/go/vt/proto/query"
)

func Walk(visit Visit, nodes ...SQLNode) error {
	for _, node := range nodes {
		err := VisitSQLNode(node, visit)
		if err != nil {
			return err
		}
	}
	return nil
}

type Visit func(node SQLNode) (kontinue bool, err error)

func Append(buf *strings.Builder, node SQLNode) {
	tbuf := &TrackedBuffer{
		Builder: buf,
	}
	node.Format(tbuf)
}

type IndexColumn struct {
	Column    ColIdent
	Length    *Literal
	Direction OrderDirection
}

type LengthScaleOption struct {
	Length *Literal
	Scale  *Literal
}

type IndexOption struct {
	Name   string
	Value  *Literal
	String string
}

type TableOption struct {
	Name   string
	Value  *Literal
	String string
	Tables TableNames
}

type ColumnKeyOption int

const (
	colKeyNone ColumnKeyOption = iota
	colKeyPrimary
	colKeySpatialKey
	colKeyFulltextKey
	colKeyUnique
	colKeyUniqueKey
	colKey
)

type ReferenceAction int

const (

	DefaultAction ReferenceAction = iota
	Restrict
	Cascade
	NoAction
	SetNull
	SetDefault
)

type ShowTablesOpt struct {
	Full   string
	DbName string
	Filter *ShowFilter
}

type ValType int

const (
	StrVal = ValType(iota)
	IntVal
	FloatVal
	HexNum
	HexVal
	BitVal
)

func (ts *TableSpec) AddColumn(cd *ColumnDefinition) {
	ts.Columns = append(ts.Columns, cd)
}

func (ts *TableSpec) AddIndex(id *IndexDefinition) {
	ts.Indexes = append(ts.Indexes, id)
}

func (ts *TableSpec) AddConstraint(cd *ConstraintDefinition) {
	ts.Constraints = append(ts.Constraints, cd)
}

func (ct *ColumnType) DescribeType() string {
	buf := NewTrackedBuffer(nil)
	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)
	}

	opts := make([]string, 0, 16)
	if ct.Unsigned {
		opts = append(opts, keywordStrings[UNSIGNED])
	}
	if ct.Zerofill {
		opts = append(opts, keywordStrings[ZEROFILL])
	}
	if len(opts) != 0 {
		buf.Myprintf(" %s", strings.Join(opts, " "))
	}
	return buf.String()
}

func (ct *ColumnType) SQLType() querypb.Type {
	switch strings.ToLower(ct.Type) {
	case keywordStrings[TINYINT]:
		if ct.Unsigned {
			return sqltypes.Uint8
		}
		return sqltypes.Int8
	case keywordStrings[SMALLINT]:
		if ct.Unsigned {
			return sqltypes.Uint16
		}
		return sqltypes.Int16
	case keywordStrings[MEDIUMINT]:
		if ct.Unsigned {
			return sqltypes.Uint24
		}
		return sqltypes.Int24
	case keywordStrings[INT], keywordStrings[INTEGER]:
		if ct.Unsigned {
			return sqltypes.Uint32
		}
		return sqltypes.Int32
	case keywordStrings[BIGINT]:
		if ct.Unsigned {
			return sqltypes.Uint64
		}
		return sqltypes.Int64
	case keywordStrings[BOOL], keywordStrings[BOOLEAN]:
		return sqltypes.Uint8
	case keywordStrings[TEXT]:
		return sqltypes.Text
	case keywordStrings[TINYTEXT]:
		return sqltypes.Text
	case keywordStrings[MEDIUMTEXT]:
		return sqltypes.Text
	case keywordStrings[LONGTEXT]:
		return sqltypes.Text
	case keywordStrings[BLOB]:
		return sqltypes.Blob
	case keywordStrings[TINYBLOB]:
		return sqltypes.Blob
	case keywordStrings[MEDIUMBLOB]:
		return sqltypes.Blob
	case keywordStrings[LONGBLOB]:
		return sqltypes.Blob
	case keywordStrings[CHAR]:
		return sqltypes.Char
	case keywordStrings[VARCHAR]:
		return sqltypes.VarChar
	case keywordStrings[BINARY]:
		return sqltypes.Binary
	case keywordStrings[VARBINARY]:
		return sqltypes.VarBinary
	case keywordStrings[DATE]:
		return sqltypes.Date
	case keywordStrings[TIME]:
		return sqltypes.Time
	case keywordStrings[DATETIME]:
		return sqltypes.Datetime
	case keywordStrings[TIMESTAMP]:
		return sqltypes.Timestamp
	case keywordStrings[YEAR]:
		return sqltypes.Year
	case keywordStrings[FLOAT_TYPE]:
		return sqltypes.Float32
	case keywordStrings[DOUBLE]:
		return sqltypes.Float64
	case keywordStrings[DECIMAL]:
		return sqltypes.Decimal
	case keywordStrings[BIT]:
		return sqltypes.Bit
	case keywordStrings[ENUM]:
		return sqltypes.Enum
	case keywordStrings[SET]:
		return sqltypes.Set
	case keywordStrings[JSON]:
		return sqltypes.TypeJSON
	case keywordStrings[GEOMETRY]:
		return sqltypes.Geometry
	case keywordStrings[POINT]:
		return sqltypes.Geometry
	case keywordStrings[LINESTRING]:
		return sqltypes.Geometry
	case keywordStrings[POLYGON]:
		return sqltypes.Geometry
	case keywordStrings[GEOMETRYCOLLECTION]:
		return sqltypes.Geometry
	case keywordStrings[MULTIPOINT]:
		return sqltypes.Geometry
	case keywordStrings[MULTILINESTRING]:
		return sqltypes.Geometry
	case keywordStrings[MULTIPOLYGON]:
		return sqltypes.Geometry
	}
	return sqltypes.Null
}

func (node *VindexSpec) ParseParams() (string, map[string]string) {
	var owner string
	params := map[string]string{}
	for _, p := range node.Params {
		if p.Key.Lowered() == VindexOwnerStr {
			owner = p.Val
		} else {
			params[p.Key.String()] = p.Val
		}
	}
	return owner, params
}

var _ ConstraintInfo = &ForeignKeyDefinition{}

func (f *ForeignKeyDefinition) iConstraintInfo() {}

var _ ConstraintInfo = &CheckConstraintDefinition{}

func (c *CheckConstraintDefinition) iConstraintInfo() {}

func (node *ShowLegacy) HasOnTable() bool {
	return node.OnTable.Name.v != ""
}

func (node *ShowLegacy) HasTable() bool {
	return node.Table.Name.v != ""
}

func (node Columns) FindColumn(col ColIdent) int {
	for i, colName := range node {
		if colName.Equal(col) {
			return i
		}
	}
	return -1
}

func (node *AliasedTableExpr) RemoveHints() *AliasedTableExpr {
	noHints := *node
	noHints.Hints = nil
	return &noHints
}

func (node *AliasedTableExpr) TableName() (TableName, error) {
	if !node.As.IsEmpty() {
		return TableName{Name: node.As}, nil
	}

	tableName, ok := node.Expr.(TableName)
	if !ok {
		return TableName{}, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "BUG: the AST has changed. This should not be possible")
	}

	return tableName, nil
}

func (node TableName) IsEmpty() bool {

	return node.Name.IsEmpty()
}

func (node TableName) ToViewName() TableName {
	return TableName{
		Qualifier: node.Qualifier,
		Name:      NewTableIdent(strings.ToLower(node.Name.v)),
	}
}

func NewWhere(typ WhereType, expr Expr) *Where {
	if expr == nil {
		return nil
	}
	return &Where{Type: typ, Expr: expr}
}

func ReplaceExpr(root, from, to Expr) Expr {
	tmp := Rewrite(root, replaceExpr(from, to), nil)

	expr, success := tmp.(Expr)
	if !success {
		log.Errorf("Failed to rewrite expression. Rewriter returned a non-expression:  %s", String(tmp))
		return from
	}

	return expr
}

func replaceExpr(from, to Expr) func(cursor *Cursor) bool {
	return func(cursor *Cursor) bool {
		if cursor.Node() == from {
			cursor.Replace(to)
		}
		switch cursor.Node().(type) {
		case *ExistsExpr, *Literal, *Subquery, *ValuesFuncExpr, *Default:
			return false
		}

		return true
	}
}

func (node *ComparisonExpr) IsImpossible() bool {
	var left, right *Literal
	var ok bool
	if left, ok = node.Left.(*Literal); !ok {
		return false
	}
	if right, ok = node.Right.(*Literal); !ok {
		return false
	}
	if node.Operator == NotEqualOp && left.Type == right.Type {
		if len(left.Val) != len(right.Val) {
			return false
		}

		for i := range left.Val {
			if left.Val[i] != right.Val[i] {
				return false
			}
		}
		return true
	}
	return false
}

func NewStrLiteral(in string) *Literal {
	return &Literal{Type: StrVal, Val: in}
}

func NewIntLiteral(in string) *Literal {
	return &Literal{Type: IntVal, Val: in}
}

func NewFloatLiteral(in string) *Literal {
	return &Literal{Type: FloatVal, Val: in}
}

func NewHexNumLiteral(in string) *Literal {
	return &Literal{Type: HexNum, Val: in}
}

func NewHexLiteral(in string) *Literal {
	return &Literal{Type: HexVal, Val: in}
}

func NewBitLiteral(in string) *Literal {
	return &Literal{Type: BitVal, Val: in}
}

func NewArgument(in string) Argument {
	return Argument(in)
}

func NewListArg(in string) ListArg {
	return ListArg(in)
}

func (node ListArg) String() string {
	return string(node)
}

func (node *Literal) Bytes() []byte {
	return []byte(node.Val)
}

func (node *Literal) HexDecode() ([]byte, error) {
	return hex.DecodeString(node.Val)
}

func (node *ColName) Equal(c *ColName) bool {

	if node == nil || c == nil {
		return false
	}
	return node.Name.Equal(c.Name) && node.Qualifier == c.Qualifier
}

var Aggregates = map[string]bool{
	"avg":          true,
	"bit_and":      true,
	"bit_or":       true,
	"bit_xor":      true,
	"count":        true,
	"group_concat": true,
	"max":          true,
	"min":          true,
	"std":          true,
	"stddev_pop":   true,
	"stddev_samp":  true,
	"stddev":       true,
	"sum":          true,
	"var_pop":      true,
	"var_samp":     true,
	"variance":     true,
}

func (node *FuncExpr) IsAggregate() bool {
	return Aggregates[node.Name.Lowered()]
}

func NewColIdent(str string) ColIdent {
	return ColIdent{
		val: str,
	}
}

func NewColName(str string) *ColName {
	return &ColName{
		Name: NewColIdent(str),
	}
}

func NewColNameWithQualifier(identifier string, table TableName) *ColName {
	return &ColName{
		Name: NewColIdent(identifier),
		Qualifier: TableName{
			Name:      NewTableIdent(table.Name.String()),
			Qualifier: NewTableIdent(table.Qualifier.String()),
		},
	}
}

func NewSelect(comments Comments, exprs SelectExprs, selectOptions []string, into *SelectInto, from TableExprs, where *Where, groupBy GroupBy, having *Where) *Select {
	var cache *bool
	var distinct, straightJoinHint, sqlFoundRows bool

	for _, option := range selectOptions {
		switch strings.ToLower(option) {
		case DistinctStr:
			distinct = true
		case SQLCacheStr:
			truth := true
			cache = &truth
		case SQLNoCacheStr:
			truth := false
			cache = &truth
		case StraightJoinHint:
			straightJoinHint = true
		case SQLCalcFoundRowsStr:
			sqlFoundRows = true
		}
	}
	return &Select{
		Cache:            cache,
		Comments:         comments,
		Distinct:         distinct,
		StraightJoinHint: straightJoinHint,
		SQLCalcFoundRows: sqlFoundRows,
		SelectExprs:      exprs,
		Into:             into,
		From:             from,
		Where:            where,
		GroupBy:          groupBy,
		Having:           having,
	}
}

func NewColIdentWithAt(str string, at AtCount) ColIdent {
	return ColIdent{
		val: str,
		at:  at,
	}
}

func (node ColIdent) IsEmpty() bool {
	return node.val == ""
}

func (node ColIdent) String() string {
	atStr := ""
	for i := NoAt; i < node.at; i++ {
		atStr += "@"
	}
	return atStr + node.val
}

func (node ColIdent) CompliantName() string {
	return compliantName(node.val)
}

func (node ColIdent) Lowered() string {
	if node.val == "" {
		return ""
	}
	if node.lowered == "" {
		node.lowered = strings.ToLower(node.val)
	}
	return node.lowered
}

func (node ColIdent) Equal(in ColIdent) bool {
	return node.Lowered() == in.Lowered()
}

func (node ColIdent) EqualString(str string) bool {
	return node.Lowered() == strings.ToLower(str)
}

func (node ColIdent) MarshalJSON() ([]byte, error) {
	return json.Marshal(node.val)
}

func (node *ColIdent) UnmarshalJSON(b []byte) error {
	var result string
	err := json.Unmarshal(b, &result)
	if err != nil {
		return err
	}
	node.val = result
	return nil
}

func NewTableIdent(str string) TableIdent {

	return TableIdent{v: hack.StringClone(str)}
}

func (node TableIdent) IsEmpty() bool {
	return node.v == ""
}

func (node TableIdent) String() string {
	return node.v
}

func (node TableIdent) CompliantName() string {
	return compliantName(node.v)
}

func (node TableIdent) MarshalJSON() ([]byte, error) {
	return json.Marshal(node.v)
}

func (node *TableIdent) UnmarshalJSON(b []byte) error {
	var result string
	err := json.Unmarshal(b, &result)
	if err != nil {
		return err
	}
	node.v = result
	return nil
}

func containEscapableChars(s string, at AtCount) bool {
	isDbSystemVariable := at != NoAt

	for i := range s {
		c := uint16(s[i])
		letter := isLetter(c)
		systemVarChar := isDbSystemVariable && isCarat(c)
		if !(letter || systemVarChar) {
			if i == 0 || !isDigit(c) {
				return true
			}
		}
	}

	return false
}

func formatID(buf *TrackedBuffer, original string, at AtCount) {
	_, isKeyword := keywordLookupTable.LookupString(original)
	if isKeyword || containEscapableChars(original, at) {
		writeEscapedString(buf, original)
	} else {
		buf.WriteString(original)
	}
}

func writeEscapedString(buf *TrackedBuffer, original string) {
	buf.WriteByte('`')
	for _, c := range original {
		buf.WriteRune(c)
		if c == '`' {
			buf.WriteByte('`')
		}
	}
	buf.WriteByte('`')
}

func compliantName(in string) string {
	var buf strings.Builder
	for i, c := range in {
		if !isLetter(uint16(c)) {
			if i == 0 || !isDigit(uint16(c)) {
				buf.WriteByte('_')
				continue
			}
		}
		buf.WriteRune(c)
	}
	return buf.String()
}

func (node *Select) AddOrder(order *Order) {
	node.OrderBy = append(node.OrderBy, order)
}

func (node *Select) SetOrderBy(orderBy OrderBy) {
	node.OrderBy = orderBy
}

func (node *Select) SetLimit(limit *Limit) {
	node.Limit = limit
}

func (node *Select) SetLock(lock Lock) {
	node.Lock = lock
}

func (node *Select) SetInto(into *SelectInto) {
	node.Into = into
}

func (node *Select) SetWith(with *With) {
	node.With = with
}

func (node *Select) MakeDistinct() {
	node.Distinct = true
}

func (node *Select) GetColumnCount() int {
	return len(node.SelectExprs)
}

func (node *Select) SetComments(comments Comments) {
	node.Comments = comments
}

func (node *Select) GetComments() Comments {
	return node.Comments
}

func (node *Select) AddWhere(expr Expr) {
	if node.Where == nil {
		node.Where = &Where{
			Type: WhereClause,
			Expr: expr,
		}
		return
	}
	node.Where.Expr = &AndExpr{
		Left:  node.Where.Expr,
		Right: expr,
	}
}

func (node *Select) AddHaving(expr Expr) {
	if node.Having == nil {
		node.Having = &Where{
			Type: HavingClause,
			Expr: expr,
		}
		return
	}
	node.Having.Expr = &AndExpr{
		Left:  node.Having.Expr,
		Right: expr,
	}
}

func (node *Update) AddWhere(expr Expr) {
	if node.Where == nil {
		node.Where = &Where{
			Type: WhereClause,
			Expr: expr,
		}
		return
	}
	node.Where.Expr = &AndExpr{
		Left:  node.Where.Expr,
		Right: expr,
	}
}

func (node *Union) AddOrder(order *Order) {
	node.OrderBy = append(node.OrderBy, order)
}

func (node *Union) SetOrderBy(orderBy OrderBy) {
	node.OrderBy = orderBy
}

func (node *Union) SetLimit(limit *Limit) {
	node.Limit = limit
}

func (node *Union) SetLock(lock Lock) {
	node.Lock = lock
}

func (node *Union) SetInto(into *SelectInto) {
	node.Into = into
}

func (node *Union) SetWith(with *With) {
	node.With = with
}

func (node *Union) MakeDistinct() {
	node.Distinct = true
}

func (node *Union) GetColumnCount() int {
	return node.Left.GetColumnCount()
}

func (node *Union) SetComments(comments Comments) {
	node.Left.SetComments(comments)
}

func (node *Union) GetComments() Comments {
	return node.Left.GetComments()
}

func requiresParen(stmt SelectStatement) bool {
	switch node := stmt.(type) {
	case *Union:
		return len(node.OrderBy) != 0 || node.Lock != 0 || node.Into != nil || node.Limit != nil
	case *Select:
		return len(node.OrderBy) != 0 || node.Lock != 0 || node.Into != nil || node.Limit != nil
	}

	return false
}

func setLockInSelect(stmt SelectStatement, lock Lock) {
	stmt.SetLock(lock)
}

func (action DDLAction) ToString() string {
	switch action {
	case CreateDDLAction:
		return CreateStr
	case AlterDDLAction:
		return AlterStr
	case DropDDLAction:
		return DropStr
	case RenameDDLAction:
		return RenameStr
	case TruncateDDLAction:
		return TruncateStr
	case CreateVindexDDLAction:
		return CreateVindexStr
	case DropVindexDDLAction:
		return DropVindexStr
	case AddVschemaTableDDLAction:
		return AddVschemaTableStr
	case DropVschemaTableDDLAction:
		return DropVschemaTableStr
	case AddColVindexDDLAction:
		return AddColVindexStr
	case DropColVindexDDLAction:
		return DropColVindexStr
	case AddSequenceDDLAction:
		return AddSequenceStr
	case AddAutoIncDDLAction:
		return AddAutoIncStr
	default:
		return "Unknown DDL Action"
	}
}

func (scope Scope) ToString() string {
	switch scope {
	case SessionScope:
		return SessionStr
	case GlobalScope:
		return GlobalStr
	case VitessMetadataScope:
		return VitessMetadataStr
	case VariableScope:
		return VariableStr
	case LocalScope:
		return LocalStr
	case ImplicitScope:
		return ImplicitStr
	default:
		return "Unknown Scope"
	}
}

func (ignore Ignore) ToString() string {
	if ignore {
		return IgnoreStr
	}
	return ""
}

func (lock Lock) ToString() string {
	switch lock {
	case NoLock:
		return NoLockStr
	case ForUpdateLock:
		return ForUpdateStr
	case ShareModeLock:
		return ShareModeStr
	default:
		return "Unknown lock"
	}
}

func (whereType WhereType) ToString() string {
	switch whereType {
	case WhereClause:
		return WhereStr
	case HavingClause:
		return HavingStr
	default:
		return "Unknown where type"
	}
}

func (joinType JoinType) ToString() string {
	switch joinType {
	case NormalJoinType:
		return JoinStr
	case StraightJoinType:
		return StraightJoinStr
	case LeftJoinType:
		return LeftJoinStr
	case RightJoinType:
		return RightJoinStr
	case NaturalJoinType:
		return NaturalJoinStr
	case NaturalLeftJoinType:
		return NaturalLeftJoinStr
	case NaturalRightJoinType:
		return NaturalRightJoinStr
	default:
		return "Unknown join type"
	}
}

func (op ComparisonExprOperator) ToString() string {
	switch op {
	case EqualOp:
		return EqualStr
	case LessThanOp:
		return LessThanStr
	case GreaterThanOp:
		return GreaterThanStr
	case LessEqualOp:
		return LessEqualStr
	case GreaterEqualOp:
		return GreaterEqualStr
	case NotEqualOp:
		return NotEqualStr
	case NullSafeEqualOp:
		return NullSafeEqualStr
	case InOp:
		return InStr
	case NotInOp:
		return NotInStr
	case LikeOp:
		return LikeStr
	case NotLikeOp:
		return NotLikeStr
	case RegexpOp:
		return RegexpStr
	case NotRegexpOp:
		return NotRegexpStr
	default:
		return "Unknown ComparisonExpOperator"
	}
}

func (op RangeCondOperator) ToString() string {
	switch op {
	case BetweenOp:
		return BetweenStr
	case NotBetweenOp:
		return NotBetweenStr
	default:
		return "Unknown RangeCondOperator"
	}
}

func (op IsExprOperator) ToString() string {
	switch op {
	case IsNullOp:
		return IsNullStr
	case IsNotNullOp:
		return IsNotNullStr
	case IsTrueOp:
		return IsTrueStr
	case IsNotTrueOp:
		return IsNotTrueStr
	case IsFalseOp:
		return IsFalseStr
	case IsNotFalseOp:
		return IsNotFalseStr
	default:
		return "Unknown IsExprOperator"
	}
}

func (op BinaryExprOperator) ToString() string {
	switch op {
	case BitAndOp:
		return BitAndStr
	case BitOrOp:
		return BitOrStr
	case BitXorOp:
		return BitXorStr
	case PlusOp:
		return PlusStr
	case MinusOp:
		return MinusStr
	case MultOp:
		return MultStr
	case DivOp:
		return DivStr
	case IntDivOp:
		return IntDivStr
	case ModOp:
		return ModStr
	case ShiftLeftOp:
		return ShiftLeftStr
	case ShiftRightOp:
		return ShiftRightStr
	case JSONExtractOp:
		return JSONExtractOpStr
	case JSONUnquoteExtractOp:
		return JSONUnquoteExtractOpStr
	default:
		return "Unknown BinaryExprOperator"
	}
}

func (op UnaryExprOperator) ToString() string {
	switch op {
	case UPlusOp:
		return UPlusStr
	case UMinusOp:
		return UMinusStr
	case TildaOp:
		return TildaStr
	case BangOp:
		return BangStr
	case BinaryOp:
		return BinaryStr
	case UBinaryOp:
		return UBinaryStr
	case Utf8mb4Op:
		return Utf8mb4Str
	case Utf8Op:
		return Utf8Str
	case Latin1Op:
		return Latin1Str
	case NStringOp:
		return NStringStr
	default:
		return "Unknown UnaryExprOperator"
	}
}

func (option MatchExprOption) ToString() string {
	switch option {
	case NoOption:
		return NoOptionStr
	case BooleanModeOpt:
		return BooleanModeStr
	case NaturalLanguageModeOpt:
		return NaturalLanguageModeStr
	case NaturalLanguageModeWithQueryExpansionOpt:
		return NaturalLanguageModeWithQueryExpansionStr
	case QueryExpansionOpt:
		return QueryExpansionStr
	default:
		return "Unknown MatchExprOption"
	}
}

func (dir OrderDirection) ToString() string {
	switch dir {
	case AscOrder:
		return AscScr
	case DescOrder:
		return DescScr
	default:
		return "Unknown OrderDirection"
	}
}

func (op ConvertTypeOperator) ToString() string {
	switch op {
	case NoOperator:
		return NoOperatorStr
	case CharacterSetOp:
		return CharacterSetStr
	default:
		return "Unknown ConvertTypeOperator"
	}
}

func (ty IndexHintsType) ToString() string {
	switch ty {
	case UseOp:
		return UseStr
	case IgnoreOp:
		return IgnoreStr
	case ForceOp:
		return ForceStr
	default:
		return "Unknown IndexHintsType"
	}
}

func (ty ExplainType) ToString() string {
	switch ty {
	case EmptyType:
		return EmptyStr
	case TreeType:
		return TreeStr
	case JSONType:
		return JSONStr
	case VitessType:
		return VitessStr
	case TraditionalType:
		return TraditionalStr
	case AnalyzeType:
		return AnalyzeStr
	default:
		return "Unknown ExplainType"
	}
}

func (sel SelectIntoType) ToString() string {
	switch sel {
	case IntoOutfile:
		return IntoOutfileStr
	case IntoOutfileS3:
		return IntoOutfileS3Str
	case IntoDumpfile:
		return IntoDumpfileStr
	default:
		return "Unknown Select Into Type"
	}
}

func (node CollateAndCharsetType) ToString() string {
	switch node {
	case CharacterSetType:
		return CharacterSetStr
	case CollateType:
		return CollateStr
	default:
		return "Unknown CollateAndCharsetType Type"
	}
}

func (ty LockType) ToString() string {
	switch ty {
	case Read:
		return ReadStr
	case ReadLocal:
		return ReadLocalStr
	case Write:
		return WriteStr
	case LowPriorityWrite:
		return LowPriorityWriteStr
	default:
		return "Unknown LockType"
	}
}

func (ty ShowCommandType) ToString() string {
	switch ty {
	case Charset:
		return CharsetStr
	case Collation:
		return CollationStr
	case Column:
		return ColumnStr
	case CreateDb:
		return CreateDbStr
	case CreateE:
		return CreateEStr
	case CreateF:
		return CreateFStr
	case CreateProc:
		return CreateProcStr
	case CreateTbl:
		return CreateTblStr
	case CreateTr:
		return CreateTrStr
	case CreateV:
		return CreateVStr
	case Database:
		return DatabaseStr
	case FunctionC:
		return FunctionCStr
	case Function:
		return FunctionStr
	case GtidExecGlobal:
		return GtidExecGlobalStr
	case Index:
		return IndexStr
	case OpenTable:
		return OpenTableStr
	case Privilege:
		return PrivilegeStr
	case ProcedureC:
		return ProcedureCStr
	case Procedure:
		return ProcedureStr
	case StatusGlobal:
		return StatusGlobalStr
	case StatusSession:
		return StatusSessionStr
	case Table:
		return TableStr
	case TableStatus:
		return TableStatusStr
	case Trigger:
		return TriggerStr
	case VariableGlobal:
		return VariableGlobalStr
	case VariableSession:
		return VariableSessionStr
	case VGtidExecGlobal:
		return VGtidExecGlobalStr
	case VitessMigrations:
		return VitessMigrationsStr
	case Warnings:
		return WarningsStr
	case Keyspace:
		return KeyspaceStr
	default:
		return "" +
			"Unknown ShowCommandType"
	}
}

func (key DropKeyType) ToString() string {
	switch key {
	case PrimaryKeyType:
		return PrimaryKeyTypeStr
	case ForeignKeyType:
		return ForeignKeyTypeStr
	case NormalKeyType:
		return NormalKeyTypeStr
	default:
		return "Unknown DropKeyType"
	}
}

func (lock LockOptionType) ToString() string {
	switch lock {
	case NoneType:
		return NoneTypeStr
	case DefaultType:
		return DefaultTypeStr
	case SharedType:
		return SharedTypeStr
	case ExclusiveType:
		return ExclusiveTypeStr
	default:
		return "Unknown type LockOptionType"
	}
}

func (node *ColName) CompliantName() string {
	if !node.Qualifier.IsEmpty() {
		return node.Qualifier.Name.CompliantName() + "_" + node.Name.CompliantName()
	}
	return node.Name.CompliantName()
}

func isExprAliasForCurrentTimeStamp(expr Expr) bool {
	switch node := expr.(type) {
	case *FuncExpr:
		return node.Name.EqualString("current_timestamp") || node.Name.EqualString("now") || node.Name.EqualString("localtimestamp") || node.Name.EqualString("localtime")
	case *CurTimeFuncExpr:
		return node.Name.EqualString("current_timestamp") || node.Name.EqualString("now") || node.Name.EqualString("localtimestamp") || node.Name.EqualString("localtime")
	}
	return false
}

type AtCount int

const (

	NoAt AtCount = iota

	SingleAt

	DoubleAt
)

func handleUnaryMinus(expr Expr) Expr {
	if num, ok := expr.(*Literal); ok && (num.Type == IntVal || num.Type == FloatVal) {

		if num.Val[0] == '-' {
			num.Val = num.Val[1:]
			return num
		}
		return NewIntLiteral("-" + num.Val)
	}
	if unaryExpr, ok := expr.(*UnaryExpr); ok && unaryExpr.Operator == UMinusOp {
		return unaryExpr.Expr
	}
	return &UnaryExpr{Operator: UMinusOp, Expr: expr}
}

func encodeSQLString(val string) string {
	return sqltypes.EncodeStringSQL(val)
}

func ToString(exprs []TableExpr) string {
	buf := NewTrackedBuffer(nil)
	prefix := ""
	for _, expr := range exprs {
		buf.astPrintf(nil, "%s%v", prefix, expr)
		prefix = ", "
	}
	return buf.String()
}

func ContainsAggregation(e SQLNode) bool {
	hasAggregates := false
	_ = Walk(func(node SQLNode) (kontinue bool, err error) {
		if IsAggregation(node) {
			hasAggregates = true
			return false, nil
		}
		return true, nil
	}, e)
	return hasAggregates
}

func IsAggregation(node SQLNode) bool {
	switch node := node.(type) {
	case *FuncExpr:
		return node.IsAggregate()
	case *GroupConcatExpr:
		return true
	}
	return false
}

func GetFirstSelect(selStmt SelectStatement) *Select {
	switch node := selStmt.(type) {
	case *Select:
		return node
	case *Union:
		return GetFirstSelect(node.Left)
	}
	panic("[BUG]: unknown type for SelectStatement")
}

func GetAllSelects(selStmt SelectStatement) []*Select {
	switch node := selStmt.(type) {
	case *Select:
		return []*Select{node}
	case *Union:
		return append(GetAllSelects(node.Left), GetAllSelects(node.Right)...)
	}
	panic("[BUG]: unknown type for SelectStatement")
}

func (es *ExtractedSubquery) SetArgName(n string) {
	es.argName = n
	es.updateAlternative()
}

func (es *ExtractedSubquery) SetHasValuesArg(n string) {
	es.hasValuesArg = n
	es.updateAlternative()
}

func (es *ExtractedSubquery) GetArgName() string {
	return es.argName
}

func (es *ExtractedSubquery) GetHasValuesArg() string {
	return es.hasValuesArg

}

func (es *ExtractedSubquery) updateAlternative() {
	switch original := es.Original.(type) {
	case *ExistsExpr:
		es.alternative = NewArgument(es.argName)
	case *Subquery:
		es.alternative = NewArgument(es.argName)
	case *ComparisonExpr:

		cmp := &ComparisonExpr{
			Left:     es.OtherSide,
			Right:    NewArgument(es.argName),
			Operator: original.Operator,
		}
		var expr Expr = cmp
		switch original.Operator {
		case InOp:

			cmp.Right = NewListArg(es.argName)
			hasValue := &ComparisonExpr{Left: NewArgument(es.hasValuesArg), Right: NewIntLiteral("1"), Operator: EqualOp}
			expr = AndExpressions(hasValue, cmp)
		case NotInOp:

			cmp.Right = NewListArg(es.argName)
			hasValue := &ComparisonExpr{Left: NewArgument(es.hasValuesArg), Right: NewIntLiteral("0"), Operator: EqualOp}
			expr = &OrExpr{hasValue, cmp}
		}
		es.alternative = expr
	}
}

func defaultRequiresParens(ct *ColumnType) bool {
	switch strings.ToUpper(ct.Type) {
	case "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "TINYBLOB", "BLOB", "MEDIUMBLOB",
		"LONGBLOB", "JSON", "GEOMETRY", "POINT",
		"LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING",
		"MULTIPOLYGON", "GEOMETRYCOLLECTION":
		return true
	}

	_, isLiteral := ct.Options.Default.(*Literal)
	_, isBool := ct.Options.Default.(BoolVal)
	_, isNullVal := ct.Options.Default.(*NullVal)

	if isLiteral || isNullVal || isBool || isExprAliasForCurrentTimeStamp(ct.Options.Default) {
		return false
	}

	return true
}
/*
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 (
	"encoding/hex"
	"encoding/json"
	"strings"

	"vitess.io/vitess/go/hack"
	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
	"vitess.io/vitess/go/vt/vterrors"

	"vitess.io/vitess/go/vt/log"

	"vitess.io/vitess/go/sqltypes"
	querypb "vitess.io/vitess/go/vt/proto/query"
)

// Walk calls visit on every node.
// If visit returns true, the underlying nodes
// are also visited. If it returns an error, walking
// is interrupted, and the error is returned.
func Walk(visit Visit, nodes ...SQLNode) error {
	for _, node := range nodes {
		err := VisitSQLNode(node, visit)
		if err != nil {
			return err
		}
	}
	return nil
}

// Visit defines the signature of a function that
// can be used to visit all nodes of a parse tree.
// returning false on kontinue means that children will not be visited
// returning an error will abort the visitation and return the error
type Visit func(node SQLNode) (kontinue bool, err error)

// Append appends the SQLNode to the buffer.
func Append(buf *strings.Builder, node SQLNode) {
	tbuf := &TrackedBuffer{
		Builder: buf,
	}
	node.Format(tbuf)
}

// IndexColumn describes a column in an index definition with optional length
type IndexColumn struct {
	Column    ColIdent
	Length    *Literal
	Direction OrderDirection
}

// LengthScaleOption is used for types that have an optional length
// and scale
type LengthScaleOption struct {
	Length *Literal
	Scale  *Literal
}

// IndexOption is used for trailing options for indexes: COMMENT, KEY_BLOCK_SIZE, USING, WITH PARSER
type IndexOption struct {
	Name   string
	Value  *Literal
	String string
}

// TableOption is used for create table options like AUTO_INCREMENT, INSERT_METHOD, etc
type TableOption struct {
	Name   string
	Value  *Literal
	String string
	Tables TableNames
}

// ColumnKeyOption indicates whether or not the given column is defined as an
// index element and contains the type of the option
type ColumnKeyOption int

const (
	colKeyNone ColumnKeyOption = iota
	colKeyPrimary
	colKeySpatialKey
	colKeyFulltextKey
	colKeyUnique
	colKeyUniqueKey
	colKey
)

// ReferenceAction indicates the action takes by a referential constraint e.g.
// the `CASCADE` in a `FOREIGN KEY .. ON DELETE CASCADE` table definition.
type ReferenceAction int

// These map to the SQL-defined reference actions.
// See https://dev.mysql.com/doc/refman/8.0/en/create-table-foreign-keys.html#foreign-keys-referential-actions
const (
	// DefaultAction indicates no action was explicitly specified.
	DefaultAction ReferenceAction = iota
	Restrict
	Cascade
	NoAction
	SetNull
	SetDefault
)

// ShowTablesOpt is show tables option
type ShowTablesOpt struct {
	Full   string
	DbName string
	Filter *ShowFilter
}

// ValType specifies the type for Literal.
type ValType int

// These are the possible Valtype values.
// HexNum represents a 0x... value. It cannot
// be treated as a simple value because it can
// be interpreted differently depending on the
// context.
const (
	StrVal = ValType(iota)
	IntVal
	FloatVal
	HexNum
	HexVal
	BitVal
)

// AddColumn appends the given column to the list in the spec
func (ts *TableSpec) AddColumn(cd *ColumnDefinition) {
	ts.Columns = append(ts.Columns, cd)
}

// AddIndex appends the given index to the list in the spec
func (ts *TableSpec) AddIndex(id *IndexDefinition) {
	ts.Indexes = append(ts.Indexes, id)
}

// AddConstraint appends the given index to the list in the spec
func (ts *TableSpec) AddConstraint(cd *ConstraintDefinition) {
	ts.Constraints = append(ts.Constraints, cd)
}

// DescribeType returns the abbreviated type information as required for
// describe table
func (ct *ColumnType) DescribeType() string {
	buf := NewTrackedBuffer(nil)
	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)
	}

	opts := make([]string, 0, 16)
	if ct.Unsigned {
		opts = append(opts, keywordStrings[UNSIGNED])
	}
	if ct.Zerofill {
		opts = append(opts, keywordStrings[ZEROFILL])
	}
	if len(opts) != 0 {
		buf.Myprintf(" %s", strings.Join(opts, " "))
	}
	return buf.String()
}

// SQLType returns the sqltypes type code for the given column
func (ct *ColumnType) SQLType() querypb.Type {
	switch strings.ToLower(ct.Type) {
	case keywordStrings[TINYINT]:
		if ct.Unsigned {
			return sqltypes.Uint8
		}
		return sqltypes.Int8
	case keywordStrings[SMALLINT]:
		if ct.Unsigned {
			return sqltypes.Uint16
		}
		return sqltypes.Int16
	case keywordStrings[MEDIUMINT]:
		if ct.Unsigned {
			return sqltypes.Uint24
		}
		return sqltypes.Int24
	case keywordStrings[INT], keywordStrings[INTEGER]:
		if ct.Unsigned {
			return sqltypes.Uint32
		}
		return sqltypes.Int32
	case keywordStrings[BIGINT]:
		if ct.Unsigned {
			return sqltypes.Uint64
		}
		return sqltypes.Int64
	case keywordStrings[BOOL], keywordStrings[BOOLEAN]:
		return sqltypes.Uint8
	case keywordStrings[TEXT]:
		return sqltypes.Text
	case keywordStrings[TINYTEXT]:
		return sqltypes.Text
	case keywordStrings[MEDIUMTEXT]:
		return sqltypes.Text
	case keywordStrings[LONGTEXT]:
		return sqltypes.Text
	case keywordStrings[BLOB]:
		return sqltypes.Blob
	case keywordStrings[TINYBLOB]:
		return sqltypes.Blob
	case keywordStrings[MEDIUMBLOB]:
		return sqltypes.Blob
	case keywordStrings[LONGBLOB]:
		return sqltypes.Blob
	case keywordStrings[CHAR]:
		return sqltypes.Char
	case keywordStrings[VARCHAR]:
		return sqltypes.VarChar
	case keywordStrings[BINARY]:
		return sqltypes.Binary
	case keywordStrings[VARBINARY]:
		return sqltypes.VarBinary
	case keywordStrings[DATE]:
		return sqltypes.Date
	case keywordStrings[TIME]:
		return sqltypes.Time
	case keywordStrings[DATETIME]:
		return sqltypes.Datetime
	case keywordStrings[TIMESTAMP]:
		return sqltypes.Timestamp
	case keywordStrings[YEAR]:
		return sqltypes.Year
	case keywordStrings[FLOAT_TYPE]:
		return sqltypes.Float32
	case keywordStrings[DOUBLE]:
		return sqltypes.Float64
	case keywordStrings[DECIMAL]:
		return sqltypes.Decimal
	case keywordStrings[BIT]:
		return sqltypes.Bit
	case keywordStrings[ENUM]:
		return sqltypes.Enum
	case keywordStrings[SET]:
		return sqltypes.Set
	case keywordStrings[JSON]:
		return sqltypes.TypeJSON
	case keywordStrings[GEOMETRY]:
		return sqltypes.Geometry
	case keywordStrings[POINT]:
		return sqltypes.Geometry
	case keywordStrings[LINESTRING]:
		return sqltypes.Geometry
	case keywordStrings[POLYGON]:
		return sqltypes.Geometry
	case keywordStrings[GEOMETRYCOLLECTION]:
		return sqltypes.Geometry
	case keywordStrings[MULTIPOINT]:
		return sqltypes.Geometry
	case keywordStrings[MULTILINESTRING]:
		return sqltypes.Geometry
	case keywordStrings[MULTIPOLYGON]:
		return sqltypes.Geometry
	}
	return sqltypes.Null
}

// ParseParams parses the vindex parameter list, pulling out the special-case
// "owner" parameter
func (node *VindexSpec) ParseParams() (string, map[string]string) {
	var owner string
	params := map[string]string{}
	for _, p := range node.Params {
		if p.Key.Lowered() == VindexOwnerStr {
			owner = p.Val
		} else {
			params[p.Key.String()] = p.Val
		}
	}
	return owner, params
}

var _ ConstraintInfo = &ForeignKeyDefinition{}

func (f *ForeignKeyDefinition) iConstraintInfo() {}

var _ ConstraintInfo = &CheckConstraintDefinition{}

func (c *CheckConstraintDefinition) iConstraintInfo() {}

// HasOnTable returns true if the show statement has an "on" clause
func (node *ShowLegacy) HasOnTable() bool {
	return node.OnTable.Name.v != ""
}

// HasTable returns true if the show statement has a parsed table name.
// Not all show statements parse table names.
func (node *ShowLegacy) HasTable() bool {
	return node.Table.Name.v != ""
}

// FindColumn finds a column in the column list, returning
// the index if it exists or -1 otherwise
func (node Columns) FindColumn(col ColIdent) int {
	for i, colName := range node {
		if colName.Equal(col) {
			return i
		}
	}
	return -1
}

// RemoveHints returns a new AliasedTableExpr with the hints removed.
func (node *AliasedTableExpr) RemoveHints() *AliasedTableExpr {
	noHints := *node
	noHints.Hints = nil
	return &noHints
}

//TableName returns a TableName pointing to this table expr
func (node *AliasedTableExpr) TableName() (TableName, error) {
	if !node.As.IsEmpty() {
		return TableName{Name: node.As}, nil
	}

	tableName, ok := node.Expr.(TableName)
	if !ok {
		return TableName{}, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "BUG: the AST has changed. This should not be possible")
	}

	return tableName, nil
}

// IsEmpty returns true if TableName is nil or empty.
func (node TableName) IsEmpty() bool {
	// If Name is empty, Qualifier is also empty.
	return node.Name.IsEmpty()
}

// ToViewName returns a TableName acceptable for use as a VIEW. VIEW names are
// always lowercase, so ToViewName lowercasese the name. Databases are case-sensitive
// so Qualifier is left untouched.
func (node TableName) ToViewName() TableName {
	return TableName{
		Qualifier: node.Qualifier,
		Name:      NewTableIdent(strings.ToLower(node.Name.v)),
	}
}

// NewWhere creates a WHERE or HAVING clause out
// of a Expr. If the expression is nil, it returns nil.
func NewWhere(typ WhereType, expr Expr) *Where {
	if expr == nil {
		return nil
	}
	return &Where{Type: typ, Expr: expr}
}

// ReplaceExpr finds the from expression from root
// and replaces it with to. If from matches root,
// then to is returned.
func ReplaceExpr(root, from, to Expr) Expr {
	tmp := Rewrite(root, replaceExpr(from, to), nil)

	expr, success := tmp.(Expr)
	if !success {
		log.Errorf("Failed to rewrite expression. Rewriter returned a non-expression:  %s", String(tmp))
		return from
	}

	return expr
}

func replaceExpr(from, to Expr) func(cursor *Cursor) bool {
	return func(cursor *Cursor) bool {
		if cursor.Node() == from {
			cursor.Replace(to)
		}
		switch cursor.Node().(type) {
		case *ExistsExpr, *Literal, *Subquery, *ValuesFuncExpr, *Default:
			return false
		}

		return true
	}
}

// IsImpossible returns true if the comparison in the expression can never evaluate to true.
// Note that this is not currently exhaustive to ALL impossible comparisons.
func (node *ComparisonExpr) IsImpossible() bool {
	var left, right *Literal
	var ok bool
	if left, ok = node.Left.(*Literal); !ok {
		return false
	}
	if right, ok = node.Right.(*Literal); !ok {
		return false
	}
	if node.Operator == NotEqualOp && left.Type == right.Type {
		if len(left.Val) != len(right.Val) {
			return false
		}

		for i := range left.Val {
			if left.Val[i] != right.Val[i] {
				return false
			}
		}
		return true
	}
	return false
}

// NewStrLiteral builds a new StrVal.
func NewStrLiteral(in string) *Literal {
	return &Literal{Type: StrVal, Val: in}
}

// NewIntLiteral builds a new IntVal.
func NewIntLiteral(in string) *Literal {
	return &Literal{Type: IntVal, Val: in}
}

// NewFloatLiteral builds a new FloatVal.
func NewFloatLiteral(in string) *Literal {
	return &Literal{Type: FloatVal, Val: in}
}

// NewHexNumLiteral builds a new HexNum.
func NewHexNumLiteral(in string) *Literal {
	return &Literal{Type: HexNum, Val: in}
}

// NewHexLiteral builds a new HexVal.
func NewHexLiteral(in string) *Literal {
	return &Literal{Type: HexVal, Val: in}
}

// NewBitLiteral builds a new BitVal containing a bit literal.
func NewBitLiteral(in string) *Literal {
	return &Literal{Type: BitVal, Val: in}
}

// NewArgument builds a new ValArg.
func NewArgument(in string) Argument {
	return Argument(in)
}

// NewListArg builds a new ListArg.
func NewListArg(in string) ListArg {
	return ListArg(in)
}

// String returns ListArg as a string.
func (node ListArg) String() string {
	return string(node)
}

// Bytes return the []byte
func (node *Literal) Bytes() []byte {
	return []byte(node.Val)
}

// HexDecode decodes the hexval into bytes.
func (node *Literal) HexDecode() ([]byte, error) {
	return hex.DecodeString(node.Val)
}

// Equal returns true if the column names match.
func (node *ColName) Equal(c *ColName) bool {
	// Failsafe: ColName should not be empty.
	if node == nil || c == nil {
		return false
	}
	return node.Name.Equal(c.Name) && node.Qualifier == c.Qualifier
}

// Aggregates is a map of all aggregate functions.
var Aggregates = map[string]bool{
	"avg":          true,
	"bit_and":      true,
	"bit_or":       true,
	"bit_xor":      true,
	"count":        true,
	"group_concat": true,
	"max":          true,
	"min":          true,
	"std":          true,
	"stddev_pop":   true,
	"stddev_samp":  true,
	"stddev":       true,
	"sum":          true,
	"var_pop":      true,
	"var_samp":     true,
	"variance":     true,
}

// IsAggregate returns true if the function is an aggregate.
func (node *FuncExpr) IsAggregate() bool {
	return Aggregates[node.Name.Lowered()]
}

// NewColIdent makes a new ColIdent.
func NewColIdent(str string) ColIdent {
	return ColIdent{
		val: str,
	}
}

// NewColName makes a new ColName
func NewColName(str string) *ColName {
	return &ColName{
		Name: NewColIdent(str),
	}
}

// NewColNameWithQualifier makes a new ColName pointing to a specific table
func NewColNameWithQualifier(identifier string, table TableName) *ColName {
	return &ColName{
		Name: NewColIdent(identifier),
		Qualifier: TableName{
			Name:      NewTableIdent(table.Name.String()),
			Qualifier: NewTableIdent(table.Qualifier.String()),
		},
	}
}

//NewSelect is used to create a select statement
func NewSelect(comments Comments, exprs SelectExprs, selectOptions []string, into *SelectInto, from TableExprs, where *Where, groupBy GroupBy, having *Where) *Select {
	var cache *bool
	var distinct, straightJoinHint, sqlFoundRows bool

	for _, option := range selectOptions {
		switch strings.ToLower(option) {
		case DistinctStr:
			distinct = true
		case SQLCacheStr:
			truth := true
			cache = &truth
		case SQLNoCacheStr:
			truth := false
			cache = &truth
		case StraightJoinHint:
			straightJoinHint = true
		case SQLCalcFoundRowsStr:
			sqlFoundRows = true
		}
	}
	return &Select{
		Cache:            cache,
		Comments:         comments,
		Distinct:         distinct,
		StraightJoinHint: straightJoinHint,
		SQLCalcFoundRows: sqlFoundRows,
		SelectExprs:      exprs,
		Into:             into,
		From:             from,
		Where:            where,
		GroupBy:          groupBy,
		Having:           having,
	}
}

// NewColIdentWithAt makes a new ColIdent.
func NewColIdentWithAt(str string, at AtCount) ColIdent {
	return ColIdent{
		val: str,
		at:  at,
	}
}

// IsEmpty returns true if the name is empty.
func (node ColIdent) IsEmpty() bool {
	return node.val == ""
}

// String returns the unescaped column name. It must
// not be used for SQL generation. Use sqlparser.String
// instead. The Stringer conformance is for usage
// in templates.
func (node ColIdent) String() string {
	atStr := ""
	for i := NoAt; i < node.at; i++ {
		atStr += "@"
	}
	return atStr + node.val
}

// CompliantName returns a compliant id name
// that can be used for a bind var.
func (node ColIdent) CompliantName() string {
	return compliantName(node.val)
}

// Lowered returns a lower-cased column name.
// This function should generally be used only for optimizing
// comparisons.
func (node ColIdent) Lowered() string {
	if node.val == "" {
		return ""
	}
	if node.lowered == "" {
		node.lowered = strings.ToLower(node.val)
	}
	return node.lowered
}

// Equal performs a case-insensitive compare.
func (node ColIdent) Equal(in ColIdent) bool {
	return node.Lowered() == in.Lowered()
}

// EqualString performs a case-insensitive compare with str.
func (node ColIdent) EqualString(str string) bool {
	return node.Lowered() == strings.ToLower(str)
}

// MarshalJSON marshals into JSON.
func (node ColIdent) MarshalJSON() ([]byte, error) {
	return json.Marshal(node.val)
}

// UnmarshalJSON unmarshals from JSON.
func (node *ColIdent) UnmarshalJSON(b []byte) error {
	var result string
	err := json.Unmarshal(b, &result)
	if err != nil {
		return err
	}
	node.val = result
	return nil
}

// NewTableIdent creates a new TableIdent.
func NewTableIdent(str string) TableIdent {
	// Use StringClone on the table name to ensure it is not pinned to the
	// underlying query string that has been generated by the parser. This
	// could lead to a significant increase in memory usage when the table
	// name comes from a large query.
	return TableIdent{v: hack.StringClone(str)}
}

// IsEmpty returns true if TabIdent is empty.
func (node TableIdent) IsEmpty() bool {
	return node.v == ""
}

// String returns the unescaped table name. It must
// not be used for SQL generation. Use sqlparser.String
// instead. The Stringer conformance is for usage
// in templates.
func (node TableIdent) String() string {
	return node.v
}

// CompliantName returns a compliant id name
// that can be used for a bind var.
func (node TableIdent) CompliantName() string {
	return compliantName(node.v)
}

// MarshalJSON marshals into JSON.
func (node TableIdent) MarshalJSON() ([]byte, error) {
	return json.Marshal(node.v)
}

// UnmarshalJSON unmarshals from JSON.
func (node *TableIdent) UnmarshalJSON(b []byte) error {
	var result string
	err := json.Unmarshal(b, &result)
	if err != nil {
		return err
	}
	node.v = result
	return nil
}

func containEscapableChars(s string, at AtCount) bool {
	isDbSystemVariable := at != NoAt

	for i := range s {
		c := uint16(s[i])
		letter := isLetter(c)
		systemVarChar := isDbSystemVariable && isCarat(c)
		if !(letter || systemVarChar) {
			if i == 0 || !isDigit(c) {
				return true
			}
		}
	}

	return false
}

func formatID(buf *TrackedBuffer, original string, at AtCount) {
	_, isKeyword := keywordLookupTable.LookupString(original)
	if isKeyword || containEscapableChars(original, at) {
		writeEscapedString(buf, original)
	} else {
		buf.WriteString(original)
	}
}

func writeEscapedString(buf *TrackedBuffer, original string) {
	buf.WriteByte('`')
	for _, c := range original {
		buf.WriteRune(c)
		if c == '`' {
			buf.WriteByte('`')
		}
	}
	buf.WriteByte('`')
}

func compliantName(in string) string {
	var buf strings.Builder
	for i, c := range in {
		if !isLetter(uint16(c)) {
			if i == 0 || !isDigit(uint16(c)) {
				buf.WriteByte('_')
				continue
			}
		}
		buf.WriteRune(c)
	}
	return buf.String()
}

// AddOrder adds an order by element
func (node *Select) AddOrder(order *Order) {
	node.OrderBy = append(node.OrderBy, order)
}

// SetOrderBy sets the order by clause
func (node *Select) SetOrderBy(orderBy OrderBy) {
	node.OrderBy = orderBy
}

// SetLimit sets the limit clause
func (node *Select) SetLimit(limit *Limit) {
	node.Limit = limit
}

// SetLock sets the lock clause
func (node *Select) SetLock(lock Lock) {
	node.Lock = lock
}

// SetInto sets the into clause
func (node *Select) SetInto(into *SelectInto) {
	node.Into = into
}

// SetWith sets the with clause to a select statement
func (node *Select) SetWith(with *With) {
	node.With = with
}

// MakeDistinct makes the statement distinct
func (node *Select) MakeDistinct() {
	node.Distinct = true
}

// GetColumnCount return SelectExprs count.
func (node *Select) GetColumnCount() int {
	return len(node.SelectExprs)
}

// SetComments implements the SelectStatement interface
func (node *Select) SetComments(comments Comments) {
	node.Comments = comments
}

// GetComments implements the SelectStatement interface
func (node *Select) GetComments() Comments {
	return node.Comments
}

// AddWhere adds the boolean expression to the
// WHERE clause as an AND condition.
func (node *Select) AddWhere(expr Expr) {
	if node.Where == nil {
		node.Where = &Where{
			Type: WhereClause,
			Expr: expr,
		}
		return
	}
	node.Where.Expr = &AndExpr{
		Left:  node.Where.Expr,
		Right: expr,
	}
}

// AddHaving adds the boolean expression to the
// HAVING clause as an AND condition.
func (node *Select) AddHaving(expr Expr) {
	if node.Having == nil {
		node.Having = &Where{
			Type: HavingClause,
			Expr: expr,
		}
		return
	}
	node.Having.Expr = &AndExpr{
		Left:  node.Having.Expr,
		Right: expr,
	}
}

// AddWhere adds the boolean expression to the
// WHERE clause as an AND condition.
func (node *Update) AddWhere(expr Expr) {
	if node.Where == nil {
		node.Where = &Where{
			Type: WhereClause,
			Expr: expr,
		}
		return
	}
	node.Where.Expr = &AndExpr{
		Left:  node.Where.Expr,
		Right: expr,
	}
}

// AddOrder adds an order by element
func (node *Union) AddOrder(order *Order) {
	node.OrderBy = append(node.OrderBy, order)
}

// SetOrderBy sets the order by clause
func (node *Union) SetOrderBy(orderBy OrderBy) {
	node.OrderBy = orderBy
}

// SetLimit sets the limit clause
func (node *Union) SetLimit(limit *Limit) {
	node.Limit = limit
}

// SetLock sets the lock clause
func (node *Union) SetLock(lock Lock) {
	node.Lock = lock
}

// SetInto sets the into clause
func (node *Union) SetInto(into *SelectInto) {
	node.Into = into
}

// SetWith sets the with clause to a union statement
func (node *Union) SetWith(with *With) {
	node.With = with
}

// MakeDistinct implements the SelectStatement interface
func (node *Union) MakeDistinct() {
	node.Distinct = true
}

// GetColumnCount implements the SelectStatement interface
func (node *Union) GetColumnCount() int {
	return node.Left.GetColumnCount()
}

// SetComments implements the SelectStatement interface
func (node *Union) SetComments(comments Comments) {
	node.Left.SetComments(comments)
}

// GetComments implements the SelectStatement interface
func (node *Union) GetComments() Comments {
	return node.Left.GetComments()
}

func requiresParen(stmt SelectStatement) bool {
	switch node := stmt.(type) {
	case *Union:
		return len(node.OrderBy) != 0 || node.Lock != 0 || node.Into != nil || node.Limit != nil
	case *Select:
		return len(node.OrderBy) != 0 || node.Lock != 0 || node.Into != nil || node.Limit != nil
	}

	return false
}

func setLockInSelect(stmt SelectStatement, lock Lock) {
	stmt.SetLock(lock)
}

// ToString returns the string associated with the DDLAction Enum
func (action DDLAction) ToString() string {
	switch action {
	case CreateDDLAction:
		return CreateStr
	case AlterDDLAction:
		return AlterStr
	case DropDDLAction:
		return DropStr
	case RenameDDLAction:
		return RenameStr
	case TruncateDDLAction:
		return TruncateStr
	case CreateVindexDDLAction:
		return CreateVindexStr
	case DropVindexDDLAction:
		return DropVindexStr
	case AddVschemaTableDDLAction:
		return AddVschemaTableStr
	case DropVschemaTableDDLAction:
		return DropVschemaTableStr
	case AddColVindexDDLAction:
		return AddColVindexStr
	case DropColVindexDDLAction:
		return DropColVindexStr
	case AddSequenceDDLAction:
		return AddSequenceStr
	case AddAutoIncDDLAction:
		return AddAutoIncStr
	default:
		return "Unknown DDL Action"
	}
}

// ToString returns the string associated with the Scope enum
func (scope Scope) ToString() string {
	switch scope {
	case SessionScope:
		return SessionStr
	case GlobalScope:
		return GlobalStr
	case VitessMetadataScope:
		return VitessMetadataStr
	case VariableScope:
		return VariableStr
	case LocalScope:
		return LocalStr
	case ImplicitScope:
		return ImplicitStr
	default:
		return "Unknown Scope"
	}
}

// ToString returns the IgnoreStr if ignore is true.
func (ignore Ignore) ToString() string {
	if ignore {
		return IgnoreStr
	}
	return ""
}

// ToString returns the string associated with the type of lock
func (lock Lock) ToString() string {
	switch lock {
	case NoLock:
		return NoLockStr
	case ForUpdateLock:
		return ForUpdateStr
	case ShareModeLock:
		return ShareModeStr
	default:
		return "Unknown lock"
	}
}

// ToString returns the string associated with WhereType
func (whereType WhereType) ToString() string {
	switch whereType {
	case WhereClause:
		return WhereStr
	case HavingClause:
		return HavingStr
	default:
		return "Unknown where type"
	}
}

// ToString returns the string associated with JoinType
func (joinType JoinType) ToString() string {
	switch joinType {
	case NormalJoinType:
		return JoinStr
	case StraightJoinType:
		return StraightJoinStr
	case LeftJoinType:
		return LeftJoinStr
	case RightJoinType:
		return RightJoinStr
	case NaturalJoinType:
		return NaturalJoinStr
	case NaturalLeftJoinType:
		return NaturalLeftJoinStr
	case NaturalRightJoinType:
		return NaturalRightJoinStr
	default:
		return "Unknown join type"
	}
}

// ToString returns the operator as a string
func (op ComparisonExprOperator) ToString() string {
	switch op {
	case EqualOp:
		return EqualStr
	case LessThanOp:
		return LessThanStr
	case GreaterThanOp:
		return GreaterThanStr
	case LessEqualOp:
		return LessEqualStr
	case GreaterEqualOp:
		return GreaterEqualStr
	case NotEqualOp:
		return NotEqualStr
	case NullSafeEqualOp:
		return NullSafeEqualStr
	case InOp:
		return InStr
	case NotInOp:
		return NotInStr
	case LikeOp:
		return LikeStr
	case NotLikeOp:
		return NotLikeStr
	case RegexpOp:
		return RegexpStr
	case NotRegexpOp:
		return NotRegexpStr
	default:
		return "Unknown ComparisonExpOperator"
	}
}

// ToString returns the operator as a string
func (op RangeCondOperator) ToString() string {
	switch op {
	case BetweenOp:
		return BetweenStr
	case NotBetweenOp:
		return NotBetweenStr
	default:
		return "Unknown RangeCondOperator"
	}
}

// ToString returns the operator as a string
func (op IsExprOperator) ToString() string {
	switch op {
	case IsNullOp:
		return IsNullStr
	case IsNotNullOp:
		return IsNotNullStr
	case IsTrueOp:
		return IsTrueStr
	case IsNotTrueOp:
		return IsNotTrueStr
	case IsFalseOp:
		return IsFalseStr
	case IsNotFalseOp:
		return IsNotFalseStr
	default:
		return "Unknown IsExprOperator"
	}
}

// ToString returns the operator as a string
func (op BinaryExprOperator) ToString() string {
	switch op {
	case BitAndOp:
		return BitAndStr
	case BitOrOp:
		return BitOrStr
	case BitXorOp:
		return BitXorStr
	case PlusOp:
		return PlusStr
	case MinusOp:
		return MinusStr
	case MultOp:
		return MultStr
	case DivOp:
		return DivStr
	case IntDivOp:
		return IntDivStr
	case ModOp:
		return ModStr
	case ShiftLeftOp:
		return ShiftLeftStr
	case ShiftRightOp:
		return ShiftRightStr
	case JSONExtractOp:
		return JSONExtractOpStr
	case JSONUnquoteExtractOp:
		return JSONUnquoteExtractOpStr
	default:
		return "Unknown BinaryExprOperator"
	}
}

// ToString returns the operator as a string
func (op UnaryExprOperator) ToString() string {
	switch op {
	case UPlusOp:
		return UPlusStr
	case UMinusOp:
		return UMinusStr
	case TildaOp:
		return TildaStr
	case BangOp:
		return BangStr
	case BinaryOp:
		return BinaryStr
	case UBinaryOp:
		return UBinaryStr
	case Utf8mb4Op:
		return Utf8mb4Str
	case Utf8Op:
		return Utf8Str
	case Latin1Op:
		return Latin1Str
	case NStringOp:
		return NStringStr
	default:
		return "Unknown UnaryExprOperator"
	}
}

// ToString returns the option as a string
func (option MatchExprOption) ToString() string {
	switch option {
	case NoOption:
		return NoOptionStr
	case BooleanModeOpt:
		return BooleanModeStr
	case NaturalLanguageModeOpt:
		return NaturalLanguageModeStr
	case NaturalLanguageModeWithQueryExpansionOpt:
		return NaturalLanguageModeWithQueryExpansionStr
	case QueryExpansionOpt:
		return QueryExpansionStr
	default:
		return "Unknown MatchExprOption"
	}
}

// ToString returns the direction as a string
func (dir OrderDirection) ToString() string {
	switch dir {
	case AscOrder:
		return AscScr
	case DescOrder:
		return DescScr
	default:
		return "Unknown OrderDirection"
	}
}

// ToString returns the operator as a string
func (op ConvertTypeOperator) ToString() string {
	switch op {
	case NoOperator:
		return NoOperatorStr
	case CharacterSetOp:
		return CharacterSetStr
	default:
		return "Unknown ConvertTypeOperator"
	}
}

// ToString returns the type as a string
func (ty IndexHintsType) ToString() string {
	switch ty {
	case UseOp:
		return UseStr
	case IgnoreOp:
		return IgnoreStr
	case ForceOp:
		return ForceStr
	default:
		return "Unknown IndexHintsType"
	}
}

// ToString returns the type as a string
func (ty ExplainType) ToString() string {
	switch ty {
	case EmptyType:
		return EmptyStr
	case TreeType:
		return TreeStr
	case JSONType:
		return JSONStr
	case VitessType:
		return VitessStr
	case TraditionalType:
		return TraditionalStr
	case AnalyzeType:
		return AnalyzeStr
	default:
		return "Unknown ExplainType"
	}
}

// ToString returns the type as a string
func (sel SelectIntoType) ToString() string {
	switch sel {
	case IntoOutfile:
		return IntoOutfileStr
	case IntoOutfileS3:
		return IntoOutfileS3Str
	case IntoDumpfile:
		return IntoDumpfileStr
	default:
		return "Unknown Select Into Type"
	}
}

// ToString returns the type as a string
func (node CollateAndCharsetType) ToString() string {
	switch node {
	case CharacterSetType:
		return CharacterSetStr
	case CollateType:
		return CollateStr
	default:
		return "Unknown CollateAndCharsetType Type"
	}
}

// ToString returns the type as a string
func (ty LockType) ToString() string {
	switch ty {
	case Read:
		return ReadStr
	case ReadLocal:
		return ReadLocalStr
	case Write:
		return WriteStr
	case LowPriorityWrite:
		return LowPriorityWriteStr
	default:
		return "Unknown LockType"
	}
}

// ToString returns ShowCommandType as a string
func (ty ShowCommandType) ToString() string {
	switch ty {
	case Charset:
		return CharsetStr
	case Collation:
		return CollationStr
	case Column:
		return ColumnStr
	case CreateDb:
		return CreateDbStr
	case CreateE:
		return CreateEStr
	case CreateF:
		return CreateFStr
	case CreateProc:
		return CreateProcStr
	case CreateTbl:
		return CreateTblStr
	case CreateTr:
		return CreateTrStr
	case CreateV:
		return CreateVStr
	case Database:
		return DatabaseStr
	case FunctionC:
		return FunctionCStr
	case Function:
		return FunctionStr
	case GtidExecGlobal:
		return GtidExecGlobalStr
	case Index:
		return IndexStr
	case OpenTable:
		return OpenTableStr
	case Privilege:
		return PrivilegeStr
	case ProcedureC:
		return ProcedureCStr
	case Procedure:
		return ProcedureStr
	case StatusGlobal:
		return StatusGlobalStr
	case StatusSession:
		return StatusSessionStr
	case Table:
		return TableStr
	case TableStatus:
		return TableStatusStr
	case Trigger:
		return TriggerStr
	case VariableGlobal:
		return VariableGlobalStr
	case VariableSession:
		return VariableSessionStr
	case VGtidExecGlobal:
		return VGtidExecGlobalStr
	case VitessMigrations:
		return VitessMigrationsStr
	case Warnings:
		return WarningsStr
	case Keyspace:
		return KeyspaceStr
	default:
		return "" +
			"Unknown ShowCommandType"
	}
}

// ToString returns the DropKeyType as a string
func (key DropKeyType) ToString() string {
	switch key {
	case PrimaryKeyType:
		return PrimaryKeyTypeStr
	case ForeignKeyType:
		return ForeignKeyTypeStr
	case NormalKeyType:
		return NormalKeyTypeStr
	default:
		return "Unknown DropKeyType"
	}
}

// ToString returns the LockOptionType as a string
func (lock LockOptionType) ToString() string {
	switch lock {
	case NoneType:
		return NoneTypeStr
	case DefaultType:
		return DefaultTypeStr
	case SharedType:
		return SharedTypeStr
	case ExclusiveType:
		return ExclusiveTypeStr
	default:
		return "Unknown type LockOptionType"
	}
}

// CompliantName is used to get the name of the bind variable to use for this column name
func (node *ColName) CompliantName() string {
	if !node.Qualifier.IsEmpty() {
		return node.Qualifier.Name.CompliantName() + "_" + node.Name.CompliantName()
	}
	return node.Name.CompliantName()
}

// isExprAliasForCurrentTimeStamp returns true if the Expr provided is an alias for CURRENT_TIMESTAMP
func isExprAliasForCurrentTimeStamp(expr Expr) bool {
	switch node := expr.(type) {
	case *FuncExpr:
		return node.Name.EqualString("current_timestamp") || node.Name.EqualString("now") || node.Name.EqualString("localtimestamp") || node.Name.EqualString("localtime")
	case *CurTimeFuncExpr:
		return node.Name.EqualString("current_timestamp") || node.Name.EqualString("now") || node.Name.EqualString("localtimestamp") || node.Name.EqualString("localtime")
	}
	return false
}

// AtCount represents the '@' count in ColIdent
type AtCount int

const (
	// NoAt represents no @
	NoAt AtCount = iota
	// SingleAt represents @
	SingleAt
	// DoubleAt represnts @@
	DoubleAt
)

// handleUnaryMinus handles the case when a unary minus operator is seen in the parser. It takes 1 argument which is the expr to which the unary minus has been added to.
func handleUnaryMinus(expr Expr) Expr {
	if num, ok := expr.(*Literal); ok && (num.Type == IntVal || num.Type == FloatVal) {
		// Handle double negative
		if num.Val[0] == '-' {
			num.Val = num.Val[1:]
			return num
		}
		return NewIntLiteral("-" + num.Val)
	}
	if unaryExpr, ok := expr.(*UnaryExpr); ok && unaryExpr.Operator == UMinusOp {
		return unaryExpr.Expr
	}
	return &UnaryExpr{Operator: UMinusOp, Expr: expr}
}

// encodeSQLString encodes the string as a SQL string.
func encodeSQLString(val string) string {
	return sqltypes.EncodeStringSQL(val)
}

// ToString prints the list of table expressions as a string
// To be used as an alternate for String for []TableExpr
func ToString(exprs []TableExpr) string {
	buf := NewTrackedBuffer(nil)
	prefix := ""
	for _, expr := range exprs {
		buf.astPrintf(nil, "%s%v", prefix, expr)
		prefix = ", "
	}
	return buf.String()
}

// ContainsAggregation returns true if the expression contains aggregation
func ContainsAggregation(e SQLNode) bool {
	hasAggregates := false
	_ = Walk(func(node SQLNode) (kontinue bool, err error) {
		if IsAggregation(node) {
			hasAggregates = true
			return false, nil
		}
		return true, nil
	}, e)
	return hasAggregates
}

// IsAggregation returns true if the node is an aggregation expression
func IsAggregation(node SQLNode) bool {
	switch node := node.(type) {
	case *FuncExpr:
		return node.IsAggregate()
	case *GroupConcatExpr:
		return true
	}
	return false
}

// GetFirstSelect gets the first select statement
func GetFirstSelect(selStmt SelectStatement) *Select {
	switch node := selStmt.(type) {
	case *Select:
		return node
	case *Union:
		return GetFirstSelect(node.Left)
	}
	panic("[BUG]: unknown type for SelectStatement")
}

// GetAllSelects gets all the select statement s
func GetAllSelects(selStmt SelectStatement) []*Select {
	switch node := selStmt.(type) {
	case *Select:
		return []*Select{node}
	case *Union:
		return append(GetAllSelects(node.Left), GetAllSelects(node.Right)...)
	}
	panic("[BUG]: unknown type for SelectStatement")
}

// SetArgName sets argument name.
func (es *ExtractedSubquery) SetArgName(n string) {
	es.argName = n
	es.updateAlternative()
}

// SetHasValuesArg sets has_values argument.
func (es *ExtractedSubquery) SetHasValuesArg(n string) {
	es.hasValuesArg = n
	es.updateAlternative()
}

// GetArgName returns argument name.
func (es *ExtractedSubquery) GetArgName() string {
	return es.argName
}

// GetHasValuesArg returns has values argument.
func (es *ExtractedSubquery) GetHasValuesArg() string {
	return es.hasValuesArg

}

func (es *ExtractedSubquery) updateAlternative() {
	switch original := es.Original.(type) {
	case *ExistsExpr:
		es.alternative = NewArgument(es.argName)
	case *Subquery:
		es.alternative = NewArgument(es.argName)
	case *ComparisonExpr:
		// other_side = :__sq
		cmp := &ComparisonExpr{
			Left:     es.OtherSide,
			Right:    NewArgument(es.argName),
			Operator: original.Operator,
		}
		var expr Expr = cmp
		switch original.Operator {
		case InOp:
			// :__sq_has_values = 1 and other_side in ::__sq
			cmp.Right = NewListArg(es.argName)
			hasValue := &ComparisonExpr{Left: NewArgument(es.hasValuesArg), Right: NewIntLiteral("1"), Operator: EqualOp}
			expr = AndExpressions(hasValue, cmp)
		case NotInOp:
			// :__sq_has_values = 0 or other_side not in ::__sq
			cmp.Right = NewListArg(es.argName)
			hasValue := &ComparisonExpr{Left: NewArgument(es.hasValuesArg), Right: NewIntLiteral("0"), Operator: EqualOp}
			expr = &OrExpr{hasValue, cmp}
		}
		es.alternative = expr
	}
}

func defaultRequiresParens(ct *ColumnType) bool {
	switch strings.ToUpper(ct.Type) {
	case "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "TINYBLOB", "BLOB", "MEDIUMBLOB",
		"LONGBLOB", "JSON", "GEOMETRY", "POINT",
		"LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING",
		"MULTIPOLYGON", "GEOMETRYCOLLECTION":
		return true
	}

	_, isLiteral := ct.Options.Default.(*Literal)
	_, isBool := ct.Options.Default.(BoolVal)
	_, isNullVal := ct.Options.Default.(*NullVal)

	if isLiteral || isNullVal || isBool || isExprAliasForCurrentTimeStamp(ct.Options.Default) {
		return false
	}

	return true
}
