Skip to content
This repository has been archived by the owner on Jan 28, 2021. It is now read-only.

Commit

Permalink
sql: implement LEFT and RIGHT join
Browse files Browse the repository at this point in the history
Closes #707

This PR implements LEFT and RIGHT join. Initial issue only required
LEFT join, but the effort to also add support to RIGHT join was
minimal, so it was added as well.

The join iterator, previously inner join iterator, has been generalized
so it can work for all joins and share all the logic in a single
component instead of having three iterators handling the complex logic
of how to compute the joins (in memory, multipass, etc).

The nodes, however, have very subtle differences that make it worthless
to abstract because most methods need to be different. The only abstractable
part was the RowIter method, which was extracted to a function that's
called from InnerJoin, LeftJoin and RightJoin nodes.

Signed-off-by: Miguel Molina <miguel@erizocosmi.co>
  • Loading branch information
erizocosmico committed May 24, 2019
1 parent 746eb11 commit 1158b47
Show file tree
Hide file tree
Showing 8 changed files with 851 additions and 394 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ SET <variable name> = <value>
|:-----|:-----|:------------|
|`INMEMORY_JOINS`|environment|If set it will perform all joins in memory. Default is off.|
|`inmemory_joins`|session|If set it will perform all joins in memory. Default is off. This has precedence over `INMEMORY_JOINS`.|
|`MAX_MEMORY_INNER_JOIN`|environment|The maximum number of memory, in megabytes, that can be consumed by go-mysql-server before switching to multipass mode in inner joins. Default is the 20% of all available physical memory.|
|`max_memory_joins`|session|The maximum number of memory, in megabytes, that can be consumed by go-mysql-server before switching to multipass mode in inner joins. Default is the 20% of all available physical memory. This has precedence over `MAX_MEMORY_INNER_JOIN`.|
|`MAX_MEMORY_JOIN`|environment|The maximum number of memory, in megabytes, that can be consumed by go-mysql-server before switching to multipass mode in joins. Default is the 20% of all available physical memory.|
|`max_memory_joins`|session|The maximum number of memory, in megabytes, that can be consumed by go-mysql-server before switching to multipass mode in joins. Default is the 20% of all available physical memory. This has precedence over `MAX_MEMORY_JOIN`.|
|`DEBUG_ANALYZER`|environment|If set, the analyzer will print debug messages. Default is off.|
|`PILOSA_INDEX_THREADS`|environment|Number of threads used in index creation. Default is the number of cores available in the machine.|
|`pilosa_index_threads`|environment|Number of threads used in index creation. Default is the number of cores available in the machine. This has precedence over `PILOSA_INDEX_THREADS`.|
Expand Down
57 changes: 56 additions & 1 deletion engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,6 @@ var queries = []struct {
[]sql.Row{{float64(1)}},
},
{

`SELECT ARRAY_LENGTH(JSON_EXTRACT('[1, 2, 3]', '$'))`,
[]sql.Row{{int32(3)}},
},
Expand Down Expand Up @@ -1139,6 +1138,62 @@ var queries = []struct {
`SELECT LEAST(i, s) FROM mytable`,
[]sql.Row{{float64(1)}, {float64(2)}, {float64(3)}},
},
{
"SELECT i, i2, s2 FROM mytable LEFT JOIN othertable ON i = i2",
[]sql.Row{
{int64(1), int64(1), "third"},
{int64(1), nil, nil},
{int64(1), nil, nil},
{int64(2), int64(2), "second"},
{int64(2), nil, nil},
{int64(2), nil, nil},
{int64(3), int64(3), "first"},
{int64(3), nil, nil},
{int64(3), nil, nil},
},
},
{
"SELECT i, i2, s2 FROM mytable RIGHT JOIN othertable ON i = i2",
[]sql.Row{
{int64(1), int64(1), "third"},
{nil, int64(1), "third"},
{nil, int64(1), "third"},
{int64(2), int64(2), "second"},
{nil, int64(2), "second"},
{nil, int64(2), "second"},
{int64(3), int64(3), "first"},
{nil, int64(3), "first"},
{nil, int64(3), "first"},
},
},
{
"SELECT i, i2, s2 FROM mytable LEFT OUTER JOIN othertable ON i = i2",
[]sql.Row{
{int64(1), int64(1), "third"},
{int64(1), nil, nil},
{int64(1), nil, nil},
{int64(2), int64(2), "second"},
{int64(2), nil, nil},
{int64(2), nil, nil},
{int64(3), int64(3), "first"},
{int64(3), nil, nil},
{int64(3), nil, nil},
},
},
{
"SELECT i, i2, s2 FROM mytable RIGHT OUTER JOIN othertable ON i = i2",
[]sql.Row{
{int64(1), int64(1), "third"},
{nil, int64(1), "third"},
{nil, int64(1), "third"},
{int64(2), int64(2), "second"},
{nil, int64(2), "second"},
{nil, int64(2), "second"},
{int64(3), int64(3), "first"},
{nil, int64(3), "first"},
{nil, int64(3), "first"},
},
},
}

func TestQueries(t *testing.T) {
Expand Down
21 changes: 18 additions & 3 deletions sql/analyzer/pushdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,28 @@ func pushdown(ctx *sql.Context, a *Analyzer, n sql.Node) (sql.Node, error) {
return nil, err
}

if ij, ok := n.(*plan.InnerJoin); ok {
cond, err := fixFieldIndexes(ij.Schema(), ij.Cond)
switch j := n.(type) {
case *plan.InnerJoin:
cond, err := fixFieldIndexes(j.Schema(), j.Cond)
if err != nil {
return nil, err
}

n = plan.NewInnerJoin(ij.Left, ij.Right, cond)
n = plan.NewInnerJoin(j.Left, j.Right, cond)
case *plan.RightJoin:
cond, err := fixFieldIndexes(j.Schema(), j.Cond)
if err != nil {
return nil, err
}

n = plan.NewRightJoin(j.Left, j.Right, cond)
case *plan.LeftJoin:
cond, err := fixFieldIndexes(j.Schema(), j.Cond)
if err != nil {
return nil, err
}

n = plan.NewLeftJoin(j.Left, j.Right, cond)
}

return n, nil
Expand Down
19 changes: 12 additions & 7 deletions sql/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,15 +504,10 @@ func tableExprToTable(
return nil, ErrUnsupportedSyntax.New(te)
}
case *sqlparser.JoinTableExpr:
// TODO: add support for the rest of joins
if t.Join != sqlparser.JoinStr && t.Join != sqlparser.NaturalJoinStr {
return nil, ErrUnsupportedFeature.New(t.Join)
}

// TODO: add support for using, once we have proper table
// qualification of fields
if len(t.Condition.Using) > 0 {
return nil, ErrUnsupportedFeature.New("using clause on join")
return nil, ErrUnsupportedFeature.New("USING clause on join")
}

left, err := tableExprToTable(ctx, t.LeftExpr)
Expand All @@ -537,7 +532,17 @@ func tableExprToTable(
if err != nil {
return nil, err
}
return plan.NewInnerJoin(left, right, cond), nil

switch t.Join {
case sqlparser.JoinStr:
return plan.NewInnerJoin(left, right, cond), nil
case sqlparser.LeftJoinStr:
return plan.NewLeftJoin(left, right, cond), nil
case sqlparser.RightJoinStr:
return plan.NewRightJoin(left, right, cond), nil
default:
return nil, ErrUnsupportedFeature.New(t.Join)
}
}
}

Expand Down
44 changes: 44 additions & 0 deletions sql/parse/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,50 @@ var fixtures = map[string]sql.Node{
),
),
),
`SELECT * FROM foo LEFT JOIN bar ON 1=1`: plan.NewProject(
[]sql.Expression{expression.NewStar()},
plan.NewLeftJoin(
plan.NewUnresolvedTable("foo", ""),
plan.NewUnresolvedTable("bar", ""),
expression.NewEquals(
expression.NewLiteral(int64(1), sql.Int64),
expression.NewLiteral(int64(1), sql.Int64),
),
),
),
`SELECT * FROM foo LEFT OUTER JOIN bar ON 1=1`: plan.NewProject(
[]sql.Expression{expression.NewStar()},
plan.NewLeftJoin(
plan.NewUnresolvedTable("foo", ""),
plan.NewUnresolvedTable("bar", ""),
expression.NewEquals(
expression.NewLiteral(int64(1), sql.Int64),
expression.NewLiteral(int64(1), sql.Int64),
),
),
),
`SELECT * FROM foo RIGHT JOIN bar ON 1=1`: plan.NewProject(
[]sql.Expression{expression.NewStar()},
plan.NewRightJoin(
plan.NewUnresolvedTable("foo", ""),
plan.NewUnresolvedTable("bar", ""),
expression.NewEquals(
expression.NewLiteral(int64(1), sql.Int64),
expression.NewLiteral(int64(1), sql.Int64),
),
),
),
`SELECT * FROM foo RIGHT OUTER JOIN bar ON 1=1`: plan.NewProject(
[]sql.Expression{expression.NewStar()},
plan.NewRightJoin(
plan.NewUnresolvedTable("foo", ""),
plan.NewUnresolvedTable("bar", ""),
expression.NewEquals(
expression.NewLiteral(int64(1), sql.Int64),
expression.NewLiteral(int64(1), sql.Int64),
),
),
),
}

func TestParse(t *testing.T) {
Expand Down
Loading

0 comments on commit 1158b47

Please sign in to comment.