Skip to content
16 changes: 16 additions & 0 deletions pkg/expression/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ func (s *Schema) Clone() *Schema {
return schema
}

// Equal checks if two schemas are equal.
func (s *Schema) Equal(other *Schema) bool {
if s == nil || other == nil {
return s == other
}
if len(s.Columns) != len(other.Columns) {
return false
}
for i, col := range s.Columns {
if !col.EqualColumn(other.Columns[i]) {
return false
}
}
return true
}

// ExprReferenceSchema checks if any column of this expression are from the schema.
func ExprReferenceSchema(expr Expression, schema *Schema) bool {
switch v := expr.(type) {
Expand Down
94 changes: 91 additions & 3 deletions pkg/parser/ast/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -3896,6 +3896,16 @@ type HintTimeRange struct {
To string
}

// LeadingList represents a nested structure in LEADING hints.
// It could be *HintTable or LeadingList
//
// eg: LEADING(a, (b, c), d)
// will be parsed into a LeadingList like:
// Items = [HintTable("a"), LeadingList{[HintTable("b"), HintTable("c")]}, HintTable("d")]
type LeadingList struct {
Items []interface{}
}

// HintSetVar is the payload of `SET_VAR` hint
type HintSetVar struct {
VarName string
Expand All @@ -3910,6 +3920,25 @@ type HintTable struct {
PartitionList []model.CIStr
}

// FlattenLeadingList collects all HintTable nodes from a possibly nested LeadingList into a flat slice.
// Note:
// - Only table names are preserved.
func FlattenLeadingList(list *LeadingList) []HintTable {
if list == nil {
return nil
}
var result []HintTable
for _, item := range list.Items {
switch t := item.(type) {
case *HintTable:
result = append(result, *t)
case *LeadingList:
result = append(result, FlattenLeadingList(t)...)
}
}
return result
}

func (ht *HintTable) Restore(ctx *format.RestoreCtx) {
if !ctx.Flags.HasWithoutSchemaNameFlag() {
if ht.DBName.L != "" {
Expand All @@ -3935,11 +3964,52 @@ func (ht *HintTable) Restore(ctx *format.RestoreCtx) {
}
}

func (lt *LeadingList) RestoreWithQB(ctx *format.RestoreCtx, qbName model.CIStr, needParen bool, isTop bool, qbOnTable bool) error {
if lt == nil || len(lt.Items) == 0 {
return nil
}
if needParen {
ctx.WritePlain("(")
}

currentQBName := qbName // hint level QBName

for i, item := range lt.Items {
if i > 0 {
ctx.WritePlain(", ")
}

switch t := item.(type) {
case *HintTable:
if i == 0 && currentQBName.L != "" && !qbOnTable {
ctx.WriteKeyWord("@")
ctx.WriteName(currentQBName.String())
ctx.WritePlain(" ")
t.Restore(ctx)
currentQBName = model.CIStr{}
} else {
t.Restore(ctx)
}
case *LeadingList:
if err := t.RestoreWithQB(ctx, currentQBName, true, false, qbOnTable); err != nil {
return err
}
currentQBName = model.CIStr{}
default:
return fmt.Errorf("unexpected type in LeadingList: %T", t)
}
}
if needParen {
ctx.WritePlain(")")
}
return nil
Comment thread
guo-shaoge marked this conversation as resolved.
}

// Restore implements Node interface.
func (n *TableOptimizerHint) Restore(ctx *format.RestoreCtx) error {
ctx.WriteKeyWord(n.HintName.String())
ctx.WritePlain("(")
if n.QBName.L != "" {
if n.HintName.L != "leading" && n.QBName.L != "" {
if n.HintName.L != "qb_name" {
ctx.WriteKeyWord("@")
}
Expand All @@ -3955,7 +4025,7 @@ func (n *TableOptimizerHint) Restore(ctx *format.RestoreCtx) error {
ctx.WritePlain(")")
return nil
}
if n.QBName.L != "" {
if n.HintName.L != "leading" && n.QBName.L != "" {
ctx.WritePlain(" ")
}
// Hints with args except query block.
Expand All @@ -3966,8 +4036,26 @@ func (n *TableOptimizerHint) Restore(ctx *format.RestoreCtx) error {
ctx.WriteName(n.HintData.(string))
case "nth_plan":
ctx.WritePlainf("%d", n.HintData.(int64))
case "leading":
if list, ok := n.HintData.(*LeadingList); ok && list != nil {
// if table level QBName or not
qbOnTable := false
if len(n.Tables) > 0 && n.Tables[0].QBName.L != "" {
qbOnTable = true
}
if err := list.RestoreWithQB(ctx, n.QBName, false, true, qbOnTable); err != nil {
return err
}
} else {
for i, table := range n.Tables {
if i != 0 {
ctx.WritePlain(", ")
}
table.Restore(ctx)
}
}
case "tidb_hj", "tidb_smj", "tidb_inlj", "hash_join", "hash_join_build", "hash_join_probe", "merge_join", "inl_join",
"broadcast_join", "shuffle_join", "inl_hash_join", "inl_merge_join", "leading", "no_hash_join", "no_merge_join",
"broadcast_join", "shuffle_join", "inl_hash_join", "inl_merge_join", "no_hash_join", "no_merge_join",
"no_index_join", "no_index_hash_join", "no_index_merge_join":
for i, table := range n.Tables {
if i != 0 {
Expand Down
10 changes: 10 additions & 0 deletions pkg/parser/ast/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,23 @@ func TestTableOptimizerHintRestore(t *testing.T) {
{"HASH_JOIN_PROBE(t1)", "HASH_JOIN_PROBE(`t1`)"},
{"LEADING(t1)", "LEADING(`t1`)"},
{"LEADING(t1, c1)", "LEADING(`t1`, `c1`)"},
{"LEADING((t1, c1), t2)", "LEADING((`t1`, `c1`), `t2`)"},
{"LEADING(t1, (c1, t2))", "LEADING(`t1`, (`c1`, `t2`))"},
{"LEADING(((t1, c1), t2), t3)", "LEADING(((`t1`, `c1`), `t2`), `t3`)"},
{"LEADING(t1, (c1, (t2, t3)))", "LEADING(`t1`, (`c1`, (`t2`, `t3`)))"},
{"LEADING(t1, c1, t2)", "LEADING(`t1`, `c1`, `t2`)"},
{"LEADING(@sel1 t1, c1)", "LEADING(@`sel1` `t1`, `c1`)"},
{"LEADING(@sel1 t1)", "LEADING(@`sel1` `t1`)"},
{"LEADING(@sel1 t1, c1, t2)", "LEADING(@`sel1` `t1`, `c1`, `t2`)"},
{"LEADING(@sel1 t1, (c1, t2))", "LEADING(@`sel1` `t1`, (`c1`, `t2`))"},
{"LEADING(@sel1 t1, (c1, t2), d3)", "LEADING(@`sel1` `t1`, (`c1`, `t2`), `d3`)"},
{"LEADING(t1@sel1)", "LEADING(`t1`@`sel1`)"},
{"LEADING(t1@sel1, c1)", "LEADING(`t1`@`sel1`, `c1`)"},
{"LEADING(t1@sel1, c1, t2)", "LEADING(`t1`@`sel1`, `c1`, `t2`)"},
{"LEADING((t1@sel1, c1), t2)", "LEADING((`t1`@`sel1`, `c1`), `t2`)"},
{"LEADING(t1@sel1, (c1, t2))", "LEADING(`t1`@`sel1`, (`c1`, `t2`))"},
{"LEADING(t1@sel1, c1, t2, d3)", "LEADING(`t1`@`sel1`, `c1`, `t2`, `d3`)"},
{"LEADING(t1@sel1, (c1, t2), d3)", "LEADING(`t1`@`sel1`, (`c1`, `t2`), `d3`)"},
{"MAX_EXECUTION_TIME(3000)", "MAX_EXECUTION_TIME(3000)"},
{"MAX_EXECUTION_TIME(@sel1 3000)", "MAX_EXECUTION_TIME(@`sel1` 3000)"},
{"USE_INDEX_MERGE(t1 c1)", "USE_INDEX_MERGE(`t1` `c1`)"},
Expand Down
Loading