Skip to content

Commit

Permalink
feat(spanner/spansql): parse join hints (#2936)
Browse files Browse the repository at this point in the history
This includes both the special case "HASH JOIN" syntax and the
"JOIN@{...}" syntax.

Initial work towards #2462.
  • Loading branch information
dsymonds committed Oct 1, 2020
1 parent 5cd1a61 commit 3cbc76c
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 5 deletions.
1 change: 0 additions & 1 deletion spanner/spannertest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,3 @@ by ascending esotericism:
- partition support
- conditional expressions
- table sampling (implementation)
- hints for table/joins
53 changes: 50 additions & 3 deletions spanner/spansql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ func (p *parser) advance() {
p.cur.typ = unknownToken
// TODO: array, struct, date, timestamp literals
switch p.s[0] {
case ',', ';', '(', ')', '*':
case ',', ';', '(', ')', '{', '}', '*':
// Single character symbol.
p.cur.value, p.s = p.s[:1], p.s[1:]
p.offset++
Expand Down Expand Up @@ -1841,6 +1841,14 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) {
p.back()
return sf, nil
}
var hashJoin bool // Special case for "HASH JOIN" syntax.
if tok.value == "HASH" {
hashJoin = true
tok = p.next()
if tok.err != nil {
return nil, err
}
}
var jt JoinType
if tok.value == "JOIN" {
// This is implicitly an inner join.
Expand All @@ -1861,12 +1869,51 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) {
return sf, nil
}

// TODO: consume "HASH"

sfj := SelectFromJoin{
Type: jt,
LHS: sf,
}
setHint := func(k, v string) {
if sfj.Hints == nil {
sfj.Hints = make(map[string]string)
}
sfj.Hints[k] = v
}
if hashJoin {
setHint("JOIN_METHOD", "HASH_JOIN")
}

if p.eat("@") {
if err := p.expect("{"); err != nil {
return nil, err
}
for {
if p.sniff("}") {
break
}
tok := p.next()
if tok.err != nil {
return nil, tok.err
}
k := tok.value
if err := p.expect("="); err != nil {
return nil, err
}
tok = p.next()
if tok.err != nil {
return nil, tok.err
}
v := tok.value
setHint(k, v)
if !p.eat(",") {
break
}
}
if err := p.expect("}"); err != nil {
return nil, err
}
}

sfj.RHS, err = p.parseSelectFrom()
if err != nil {
return nil, err
Expand Down
29 changes: 29 additions & 0 deletions spanner/spansql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,35 @@ func TestParseQuery(t *testing.T) {
},
},
},
// Joins with hints.
{`SELECT * FROM A HASH JOIN B USING (x)`,
Query{
Select: Select{
List: []Expr{Star},
From: []SelectFrom{SelectFromJoin{
Type: InnerJoin,
LHS: SelectFromTable{Table: "A"},
RHS: SelectFromTable{Table: "B"},
Using: []ID{"x"},
Hints: map[string]string{"JOIN_METHOD": "HASH_JOIN"},
}},
},
},
},
{`SELECT * FROM A JOIN @{ JOIN_METHOD=HASH_JOIN } B USING (x)`,
Query{
Select: Select{
List: []Expr{Star},
From: []SelectFrom{SelectFromJoin{
Type: InnerJoin,
LHS: SelectFromTable{Table: "A"},
RHS: SelectFromTable{Table: "B"},
Using: []ID{"x"},
Hints: map[string]string{"JOIN_METHOD": "HASH_JOIN"},
}},
},
},
},
}
for _, test := range tests {
got, err := ParseQuery(test.in)
Expand Down
4 changes: 3 additions & 1 deletion spanner/spansql/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,9 @@ type SelectFromJoin struct {
On BoolExpr
Using []ID

// TODO: hint keys (this will cover `X HASH JOIN Y` too).
// Hints are suggestions for how to evaluate a join.
// https://cloud.google.com/spanner/docs/query-syntax#join-hints
Hints map[string]string
}

func (SelectFromJoin) isSelectFrom() {}
Expand Down

0 comments on commit 3cbc76c

Please sign in to comment.