Skip to content

Commit

Permalink
Merge pull request #8312 from planetscale/gen4-outer-join
Browse files Browse the repository at this point in the history
gen4: outer joins
  • Loading branch information
systay authored Jun 23, 2021
2 parents 4385d67 + 95b122b commit dcdd254
Show file tree
Hide file tree
Showing 34 changed files with 2,306 additions and 1,098 deletions.
29 changes: 29 additions & 0 deletions go/vt/sqlparser/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,35 @@ func SplitAndExpression(filters []Expr, node Expr) []Expr {
return append(filters, node)
}

// AndExpressions ands together two expression, minimising the expr when possible
func AndExpressions(exprs ...Expr) Expr {
switch len(exprs) {
case 0:
return nil
case 1:
return exprs[0]
default:
result := (Expr)(nil)
for i, expr := range exprs {
if result == nil {
result = expr
} else {
found := false
for j := 0; j < i; j++ {
if EqualsExpr(expr, exprs[j]) {
found = true
break
}
}
if !found {
result = &AndExpr{Left: result, Right: expr}
}
}
}
return result
}
}

// TableFromStatement returns the qualified table name for the query.
// This works only for select statements.
func TableFromStatement(sql string) (TableName, error) {
Expand Down
70 changes: 70 additions & 0 deletions go/vt/sqlparser/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,76 @@ func TestSplitAndExpression(t *testing.T) {
}
}

func TestAndExpressions(t *testing.T) {
greaterThanExpr := &ComparisonExpr{
Operator: GreaterThanOp,
Left: &ColName{
Name: NewColIdent("val"),
Qualifier: TableName{
Name: NewTableIdent("a"),
},
},
Right: &ColName{
Name: NewColIdent("val"),
Qualifier: TableName{
Name: NewTableIdent("b"),
},
},
}
equalExpr := &ComparisonExpr{
Operator: EqualOp,
Left: &ColName{
Name: NewColIdent("id"),
Qualifier: TableName{
Name: NewTableIdent("a"),
},
},
Right: &ColName{
Name: NewColIdent("id"),
Qualifier: TableName{
Name: NewTableIdent("b"),
},
},
}
testcases := []struct {
name string
expressions Exprs
expectedOutput Expr
}{
{
name: "empty input",
expressions: nil,
expectedOutput: nil,
}, {
name: "two equal inputs",
expressions: Exprs{
greaterThanExpr,
equalExpr,
equalExpr,
},
expectedOutput: &AndExpr{
Left: greaterThanExpr,
Right: equalExpr,
},
},
{
name: "two equal inputs",
expressions: Exprs{
equalExpr,
equalExpr,
},
expectedOutput: equalExpr,
},
}

for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
output := AndExpressions(testcase.expressions...)
assert.Equal(t, String(testcase.expectedOutput), String(output))
})
}
}

func TestTableFromStatement(t *testing.T) {
testcases := []struct {
in, out string
Expand Down
4 changes: 2 additions & 2 deletions go/vt/vtgate/engine/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,12 @@ type JoinOpcode int

// This is the list of JoinOpcode values.
const (
NormalJoin = JoinOpcode(iota)
InnerJoin = JoinOpcode(iota)
LeftJoin
)

func (code JoinOpcode) String() string {
if code == NormalJoin {
if code == InnerJoin {
return "Join"
}
return "LeftJoin"
Expand Down
18 changes: 9 additions & 9 deletions go/vt/vtgate/engine/join_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func TestJoinExecute(t *testing.T) {

// Normal join
jn := &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand Down Expand Up @@ -185,7 +185,7 @@ func TestJoinExecuteMaxMemoryRows(t *testing.T) {

// Normal join
jn := &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand Down Expand Up @@ -227,7 +227,7 @@ func TestJoinExecuteNoResult(t *testing.T) {
}

jn := &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand Down Expand Up @@ -262,7 +262,7 @@ func TestJoinExecuteErrors(t *testing.T) {
}

jn := &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
}
_, err := jn.Execute(&noopVCursor{}, map[string]*querypb.BindVariable{}, true)
Expand All @@ -287,7 +287,7 @@ func TestJoinExecuteErrors(t *testing.T) {
}

jn = &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand All @@ -314,7 +314,7 @@ func TestJoinExecuteErrors(t *testing.T) {
}

jn = &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand Down Expand Up @@ -368,7 +368,7 @@ func TestJoinStreamExecute(t *testing.T) {

// Normal join
jn := &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand Down Expand Up @@ -456,7 +456,7 @@ func TestGetFields(t *testing.T) {
}

jn := &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand Down Expand Up @@ -493,7 +493,7 @@ func TestGetFieldsErrors(t *testing.T) {
}

jn := &Join{
Opcode: NormalJoin,
Opcode: InnerJoin,
Left: leftPrim,
Right: rightPrim,
Cols: []int{-1, -2, 1, 2},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/
// +build gofuzz

package planbuilder
package abstract

import (
"fmt"
Expand Down Expand Up @@ -54,7 +54,8 @@ func FuzzAnalyse(data []byte) int {
if err != nil {
return 0
}
_, _ = createQGFromSelect(stmt, semTable)
_, _ = CreateOperatorFromSelect(stmt, semTable)

default:
return 0
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package planbuilder
package abstract

import (
"io/ioutil"
Expand Down
49 changes: 49 additions & 0 deletions go/vt/vtgate/planbuilder/abstract/join.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2021 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 abstract

import (
"vitess.io/vitess/go/vt/sqlparser"
"vitess.io/vitess/go/vt/vtgate/semantics"
)

// Join represents an join. If we have a predicate, this is an inner join. If no predicate exists, it is a cross join
type Join struct {
LHS, RHS Operator
Exp sqlparser.Expr
}

// PushPredicate implements the Operator interface
func (j *Join) PushPredicate(expr sqlparser.Expr, semTable *semantics.SemTable) error {
deps := semTable.Dependencies(expr)
switch {
case deps.IsSolvedBy(j.LHS.TableID()):
return j.LHS.PushPredicate(expr, semTable)
case deps.IsSolvedBy(j.RHS.TableID()):
return j.RHS.PushPredicate(expr, semTable)
case deps.IsSolvedBy(j.LHS.TableID().Merge(j.RHS.TableID())):
j.Exp = sqlparser.AndExpressions(j.Exp, expr)
return nil
}

return semantics.Gen4NotSupportedF("still not sure what to do with this predicate")
}

// TableID implements the Operator interface
func (j *Join) TableID() semantics.TableSet {
return j.RHS.TableID().Merge(j.LHS.TableID())
}
Loading

0 comments on commit dcdd254

Please sign in to comment.