Skip to content

Commit

Permalink
feat(spanner/spannertest): support INNER and CROSS JOIN (#3037)
Browse files Browse the repository at this point in the history
These are similar to the existing LEFT JOIN.
Implementing FULL and RIGHT JOIN is harder.
  • Loading branch information
dsymonds committed Oct 16, 2020
1 parent 6f7fa86 commit 0033acc
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 6 deletions.
85 changes: 83 additions & 2 deletions spanner/spannertest/db_query.go
Expand Up @@ -800,11 +800,92 @@ func (ji *joinIter) nextLeft() error {
}

func (ji *joinIter) Next() (row, error) {
// TODO: More join types.
if ji.jt != spansql.LeftJoin {
// TODO: FULL and RIGHT joins; they'll need more structural work in joinIter.
switch ji.jt {
default:
return nil, fmt.Errorf("TODO: can't yet evaluate join of type %v", ji.jt)
case spansql.InnerJoin:
return ji.innerJoin()
case spansql.CrossJoin:
return ji.crossJoin()
case spansql.LeftJoin:
return ji.leftJoin()
}
}

// TODO: Refactor these individual JOIN implementations when they are complete.

func (ji *joinIter) innerJoin() (row, error) {
/*
An INNER JOIN, or simply JOIN, effectively calculates the
Cartesian product of the two from_items and discards all rows
that do not meet the join condition.
*/
if ji.lhsRow == nil {
if err := ji.nextLeft(); err != nil {
return nil, err
}
}

for {
rhsRow, err := ji.rhs.Next()
if err == io.EOF {
// Finished the current LHS row;
// advance to next one.
if err := ji.nextLeft(); err != nil {
return nil, err
}
continue
}
if err != nil {
return nil, err
}
match, err := ji.cond(ji.lhsRow, rhsRow)
if err != nil {
return nil, err
}
if !match {
continue
}
return ji.ec.row, nil
}
}

func (ji *joinIter) crossJoin() (row, error) {
/*
CROSS JOIN returns the Cartesian product of the two from_items.
In other words, it combines each row from the first from_item
with each row from the second from_item.
*/
if ji.lhsRow == nil {
if err := ji.nextLeft(); err != nil {
return nil, err
}
}

for {
rhsRow, err := ji.rhs.Next()
if err == io.EOF {
// Finished the current LHS row;
// advance to next one.
if err := ji.nextLeft(); err != nil {
return nil, err
}
continue
}
if err != nil {
return nil, err
}
// The condition will be trivially true.
_, err = ji.cond(ji.lhsRow, rhsRow)
if err != nil {
return nil, err
}
return ji.ec.row, nil
}
}

func (ji *joinIter) leftJoin() (row, error) {
/*
The result of a LEFT OUTER JOIN (or simply LEFT JOIN) for two
from_items always retains all rows of the left from_item in the
Expand Down
35 changes: 31 additions & 4 deletions spanner/spannertest/integration_test.go
Expand Up @@ -407,10 +407,7 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
allTables := []string{
"Staff",
"PlayerStats",
"JoinA",
"JoinB",
"JoinC",
"JoinD",
"JoinA", "JoinB", "JoinC", "JoinD", "JoinE", "JoinF",
"SomeStrings",
}
for _, table := range allTables {
Expand Down Expand Up @@ -600,6 +597,8 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
`CREATE TABLE JoinB ( y INT64, z STRING(MAX) ) PRIMARY KEY (y, z)`,
`CREATE TABLE JoinC ( x INT64, y STRING(MAX) ) PRIMARY KEY (x, y)`,
`CREATE TABLE JoinD ( x INT64, z STRING(MAX) ) PRIMARY KEY (x, z)`,
`CREATE TABLE JoinE ( w INT64, x STRING(MAX) ) PRIMARY KEY (w, x)`,
`CREATE TABLE JoinF ( y INT64, z STRING(MAX) ) PRIMARY KEY (y, z)`,
// Some other test tables.
`CREATE TABLE SomeStrings ( i INT64, str STRING(MAX) ) PRIMARY KEY (i)`,
)
Expand Down Expand Up @@ -634,6 +633,13 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
spanner.Insert("JoinD", []string{"x", "z"}, []interface{}{3, "n"}),
spanner.Insert("JoinD", []string{"x", "z"}, []interface{}{4, "p"}),

// JoinE and JoinF are used in the CROSS JOIN test.
spanner.Insert("JoinE", []string{"w", "x"}, []interface{}{1, "a"}),
spanner.Insert("JoinE", []string{"w", "x"}, []interface{}{2, "b"}),

spanner.Insert("JoinF", []string{"y", "z"}, []interface{}{2, "c"}),
spanner.Insert("JoinF", []string{"y", "z"}, []interface{}{3, "d"}),

spanner.Insert("SomeStrings", []string{"i", "str"}, []interface{}{0, "afoo"}),
spanner.Insert("SomeStrings", []string{"i", "str"}, []interface{}{1, "abar"}),
spanner.Insert("SomeStrings", []string{"i", "str"}, []interface{}{2, nil}),
Expand Down Expand Up @@ -872,6 +878,27 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
},
},
// Joins.
{
`SELECT * FROM JoinA INNER JOIN JoinB ON JoinA.w = JoinB.y ORDER BY w, x, y, z`,
nil,
[][]interface{}{
{int64(2), "b", int64(2), "k"},
{int64(3), "c", int64(3), "m"},
{int64(3), "c", int64(3), "n"},
{int64(3), "d", int64(3), "m"},
{int64(3), "d", int64(3), "n"},
},
},
{
`SELECT * FROM JoinE CROSS JOIN JoinF ORDER BY w, x, y, z`,
nil,
[][]interface{}{
{int64(1), "a", int64(2), "c"},
{int64(1), "a", int64(3), "d"},
{int64(2), "b", int64(2), "c"},
{int64(2), "b", int64(3), "d"},
},
},
{
`SELECT * FROM JoinA LEFT OUTER JOIN JoinB AS B ON JoinA.w = B.y ORDER BY w, x, y, z`,
nil,
Expand Down

0 comments on commit 0033acc

Please sign in to comment.