Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List join predicates used in GetVExplainKeys #17130

Merged
merged 10 commits into from
Nov 7, 2024
85 changes: 78 additions & 7 deletions go/vt/vtgate/planbuilder/operators/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@ type (
Column Column
Uses sqlparser.ComparisonExprOperator
}
JoinPredicate struct {
LHS, RHS Column
Uses sqlparser.ComparisonExprOperator
}
VExplainKeys struct {
StatementType string `json:"statementType"`
TableName []string `json:"tableName,omitempty"`
GroupingColumns []Column `json:"groupingColumns,omitempty"`
JoinColumns []ColumnUse `json:"joinColumns,omitempty"`
FilterColumns []ColumnUse `json:"filterColumns,omitempty"`
SelectColumns []Column `json:"selectColumns,omitempty"`
StatementType string `json:"statementType"`
TableName []string `json:"tableName,omitempty"`
GroupingColumns []Column `json:"groupingColumns,omitempty"`
FilterColumns []ColumnUse `json:"filterColumns,omitempty"`
SelectColumns []Column `json:"selectColumns,omitempty"`
JoinPredicates []JoinPredicate `json:"joinPredicates,omitempty"`
}
)

Expand Down Expand Up @@ -111,6 +115,35 @@ func (cu *ColumnUse) UnmarshalJSON(data []byte) error {
return nil
}

func (jp *JoinPredicate) MarshalJSON() ([]byte, error) {
return json.Marshal(jp.String())
}

func (jp *JoinPredicate) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
subStrings := strings.Split(s, " ")
if len(subStrings) != 3 {
return fmt.Errorf("invalid JoinPredicate format: %s", s)
}

op, err := sqlparser.ComparisonExprOperatorFromJson(subStrings[1])
if err != nil {
return fmt.Errorf("invalid comparison operator: %w", err)
}
jp.Uses = op

if err = jp.LHS.UnmarshalJSON([]byte(`"` + subStrings[0] + `"`)); err != nil {
return err
}
if err = jp.RHS.UnmarshalJSON([]byte(`"` + subStrings[2] + `"`)); err != nil {
return err
}
return nil
}

func (c Column) String() string {
return fmt.Sprintf("%s.%s", c.Table, c.Name)
}
Expand All @@ -119,14 +152,25 @@ func (cu ColumnUse) String() string {
return fmt.Sprintf("%s %s", cu.Column, cu.Uses.JSONString())
}

func (jp JoinPredicate) String() string {
return fmt.Sprintf("%s %s %s", jp.LHS.String(), jp.Uses.JSONString(), jp.RHS.String())
}

type columnUse struct {
col *sqlparser.ColName
use sqlparser.ComparisonExprOperator
}

type joinPredicate struct {
lhs *sqlparser.ColName
rhs *sqlparser.ColName
uses sqlparser.ComparisonExprOperator
}

func GetVExplainKeys(ctx *plancontext.PlanningContext, stmt sqlparser.Statement) (result VExplainKeys) {
var groupingColumns, selectColumns []*sqlparser.ColName
var filterColumns, joinColumns []columnUse
var jps []joinPredicate

addPredicate := func(predicate sqlparser.Expr) {
predicates := sqlparser.SplitAndExpression(nil, predicate)
Expand All @@ -140,6 +184,7 @@ func GetVExplainKeys(ctx *plancontext.PlanningContext, stmt sqlparser.Statement)
if lhsOK && rhsOK && ctx.SemTable.RecursiveDeps(lhs) != ctx.SemTable.RecursiveDeps(rhs) {
// If the columns are from different tables, they are considered join columns
output = &joinColumns
jps = append(jps, joinPredicate{lhs: lhs, rhs: rhs, uses: cmp.Operator})
}

if lhsOK {
Expand Down Expand Up @@ -189,10 +234,36 @@ func GetVExplainKeys(ctx *plancontext.PlanningContext, stmt sqlparser.Statement)
return VExplainKeys{
SelectColumns: getUniqueColNames(ctx, selectColumns),
GroupingColumns: getUniqueColNames(ctx, groupingColumns),
JoinColumns: getUniqueColUsages(ctx, joinColumns),
FilterColumns: getUniqueColUsages(ctx, filterColumns),
StatementType: sqlparser.ASTToStatementType(stmt).String(),
JoinPredicates: getUniqueJoinPredicates(ctx, jps),
}
}

func getUniqueJoinPredicates(ctx *plancontext.PlanningContext, joinPredicates []joinPredicate) []JoinPredicate {
var result []JoinPredicate
for _, predicate := range joinPredicates {
lhs := createColumn(ctx, predicate.lhs)
rhs := createColumn(ctx, predicate.rhs)
if lhs == nil || rhs == nil {
continue
}

result = append(result, JoinPredicate{
LHS: *lhs,
RHS: *rhs,
Uses: predicate.uses,
})
}

sort.Slice(result, func(i, j int) bool {
if result[i].LHS.Name == result[j].LHS.Name {
return result[i].RHS.Name < result[j].RHS.Name
}
return result[i].LHS.Name < result[j].LHS.Name
})

return slices.Compact(result)
}

func getUniqueColNames(ctx *plancontext.PlanningContext, inCols []*sqlparser.ColName) (columns []Column) {
Expand Down
7 changes: 3 additions & 4 deletions go/vt/vtgate/planbuilder/operators/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ func TestMarshalUnmarshal(t *testing.T) {
{Table: "orders", Name: "category"},
{Table: "users", Name: "department"},
},
JoinColumns: []ColumnUse{
{Column: Column{Table: "users", Name: "id"}, Uses: sqlparser.EqualOp},
{Column: Column{Table: "orders", Name: "user_id"}, Uses: sqlparser.EqualOp},
},
FilterColumns: []ColumnUse{
{Column: Column{Table: "users", Name: "age"}, Uses: sqlparser.GreaterThanOp},
{Column: Column{Table: "orders", Name: "total"}, Uses: sqlparser.LessThanOp},
Expand All @@ -49,6 +45,9 @@ func TestMarshalUnmarshal(t *testing.T) {
{Table: "users", Name: "email"},
{Table: "orders", Name: "amount"},
},
JoinPredicates: []JoinPredicate{
{LHS: Column{Table: "users", Name: "id"}, RHS: Column{Table: "orders", Name: "user_id"}, Uses: sqlparser.EqualOp},
},
}

jsonData, err := json.Marshal(original)
Expand Down
21 changes: 9 additions & 12 deletions go/vt/vtgate/testdata/executor_vexplain.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,25 @@
"query": "select * from user u join user_extra ue on u.id = ue.user_id where u.col1 \u003e 100 and ue.noLimit = 'foo'",
"expected": {
"statementType": "SELECT",
"joinColumns": [
"`user`.id =",
"user_extra.user_id ="
],
"filterColumns": [
"`user`.col1 gt",
"user_extra.noLimit ="
],
"joinPredicates": [
"`user`.id = user_extra.user_id"
]
}
},
{
"query": "select * from user_extra ue, user u where ue.noLimit = 'foo' and u.col1 \u003e 100 and ue.user_id = u.id",
"expected": {
"statementType": "SELECT",
"joinColumns": [
"`user`.id =",
"user_extra.user_id ="
],
"filterColumns": [
"`user`.col1 gt",
"user_extra.noLimit ="
],
"joinPredicates": [
"user_extra.user_id = `user`.id"
]
}
},
Expand All @@ -47,16 +45,15 @@
"`user`.foo",
"user_extra.bar"
],
"joinColumns": [
"`user`.id =",
"user_extra.user_id ="
],
"filterColumns": [
"`user`.`name` ="
],
"selectColumns": [
"`user`.foo",
"user_extra.bar"
],
"joinPredicates": [
"`user`.id = user_extra.user_id"
]
}
},
Expand Down
Loading