Skip to content

Commit 7047808

Browse files
Takeshi Nakatahengfengli
Takeshi Nakata
andauthored
feat(spanner/spansql): support table_hint_expr at from_clause on query_statement (#4457)
* feat(spanner/spansql): support table_hint_expr at from_clause on query_statement This fixes parse error when query statement includes table hint expr. This add function parseHints and use in parsing table hints and join hints. * feat(spanner/spansql): modify to enable multiple table hint keys. * feat(spanner/spansql): use ++ increment for lint Co-authored-by: Hengfeng Li <hengfeng@google.com>
1 parent b7ce742 commit 7047808

File tree

5 files changed

+127
-34
lines changed

5 files changed

+127
-34
lines changed

spanner/spansql/parser.go

+50-34
Original file line numberDiff line numberDiff line change
@@ -1885,7 +1885,7 @@ func (p *parser) parseQuery() (Query, *parseError) {
18851885
[ LIMIT count [ OFFSET skip_rows ] ]
18861886
*/
18871887

1888-
// TODO: hints, sub-selects, etc.
1888+
// TODO: sub-selects, etc.
18891889

18901890
if err := p.expect("SELECT"); err != nil {
18911891
return Query{}, err
@@ -2111,6 +2111,13 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) {
21112111
return nil, err
21122112
}
21132113
sf := SelectFromTable{Table: tname}
2114+
if p.eat("@") {
2115+
hints, err := p.parseHints(map[string]string{})
2116+
if err != nil {
2117+
return nil, err
2118+
}
2119+
sf.Hints = hints
2120+
}
21142121

21152122
// TODO: The "AS" keyword is optional.
21162123
if p.eat("AS") {
@@ -2159,46 +2166,20 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) {
21592166
Type: jt,
21602167
LHS: sf,
21612168
}
2162-
setHint := func(k, v string) {
2163-
if sfj.Hints == nil {
2164-
sfj.Hints = make(map[string]string)
2165-
}
2166-
sfj.Hints[k] = v
2167-
}
2169+
var hints map[string]string
21682170
if hashJoin {
2169-
setHint("JOIN_METHOD", "HASH_JOIN")
2171+
hints = map[string]string{}
2172+
hints["JOIN_METHOD"] = "HASH_JOIN"
21702173
}
21712174

21722175
if p.eat("@") {
2173-
if err := p.expect("{"); err != nil {
2174-
return nil, err
2175-
}
2176-
for {
2177-
if p.sniff("}") {
2178-
break
2179-
}
2180-
tok := p.next()
2181-
if tok.err != nil {
2182-
return nil, tok.err
2183-
}
2184-
k := tok.value
2185-
if err := p.expect("="); err != nil {
2186-
return nil, err
2187-
}
2188-
tok = p.next()
2189-
if tok.err != nil {
2190-
return nil, tok.err
2191-
}
2192-
v := tok.value
2193-
setHint(k, v)
2194-
if !p.eat(",") {
2195-
break
2196-
}
2197-
}
2198-
if err := p.expect("}"); err != nil {
2176+
h, err := p.parseHints(hints)
2177+
if err != nil {
21992178
return nil, err
22002179
}
2180+
hints = h
22012181
}
2182+
sfj.Hints = hints
22022183

22032184
sfj.RHS, err = p.parseSelectFrom()
22042185
if err != nil {
@@ -2889,6 +2870,41 @@ func (p *parser) parseAlias() (ID, *parseError) {
28892870
return p.parseTableOrIndexOrColumnName()
28902871
}
28912872

2873+
func (p *parser) parseHints(hints map[string]string) (map[string]string, *parseError) {
2874+
if hints == nil {
2875+
hints = map[string]string{}
2876+
}
2877+
if err := p.expect("{"); err != nil {
2878+
return nil, err
2879+
}
2880+
for {
2881+
if p.sniff("}") {
2882+
break
2883+
}
2884+
tok := p.next()
2885+
if tok.err != nil {
2886+
return nil, tok.err
2887+
}
2888+
k := tok.value
2889+
if err := p.expect("="); err != nil {
2890+
return nil, err
2891+
}
2892+
tok = p.next()
2893+
if tok.err != nil {
2894+
return nil, tok.err
2895+
}
2896+
v := tok.value
2897+
hints[k] = v
2898+
if !p.eat(",") {
2899+
break
2900+
}
2901+
}
2902+
if err := p.expect("}"); err != nil {
2903+
return nil, err
2904+
}
2905+
return hints, nil
2906+
}
2907+
28922908
func (p *parser) parseTableOrIndexOrColumnName() (ID, *parseError) {
28932909
/*
28942910
table_name and column_name and index_name:

spanner/spansql/parser_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,34 @@ func TestParseQuery(t *testing.T) {
114114
},
115115
},
116116
},
117+
// with single table hint
118+
{`SELECT * FROM Packages@{FORCE_INDEX=PackagesIdx} WHERE package_idx=@packageIdx`,
119+
Query{
120+
Select: Select{
121+
List: []Expr{Star},
122+
From: []SelectFrom{SelectFromTable{Table: "Packages", Hints: map[string]string{"FORCE_INDEX": "PackagesIdx"}}},
123+
Where: ComparisonOp{
124+
Op: Eq,
125+
LHS: ID("package_idx"),
126+
RHS: Param("packageIdx"),
127+
},
128+
},
129+
},
130+
},
131+
// with multiple table hints
132+
{`SELECT * FROM Packages@{ FORCE_INDEX=PackagesIdx, GROUPBY_SCAN_OPTIMIZATION=TRUE } WHERE package_idx=@packageIdx`,
133+
Query{
134+
Select: Select{
135+
List: []Expr{Star},
136+
From: []SelectFrom{SelectFromTable{Table: "Packages", Hints: map[string]string{"FORCE_INDEX": "PackagesIdx", "GROUPBY_SCAN_OPTIMIZATION": "TRUE"}}},
137+
Where: ComparisonOp{
138+
Op: Eq,
139+
LHS: ID("package_idx"),
140+
RHS: Param("packageIdx"),
141+
},
142+
},
143+
},
144+
},
117145
{`SELECT * FROM A INNER JOIN B ON A.w = B.y`,
118146
Query{
119147
Select: Select{

spanner/spansql/sql.go

+12
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,18 @@ func (sel Select) addSQL(sb *strings.Builder) {
365365

366366
func (sft SelectFromTable) SQL() string {
367367
str := sft.Table.SQL()
368+
if len(sft.Hints) > 0 {
369+
str += "@{"
370+
kvs := make([]string, len(sft.Hints))
371+
i := 0
372+
for k, v := range sft.Hints {
373+
kvs[i] = fmt.Sprintf("%s=%s", k, v)
374+
i++
375+
}
376+
str += strings.Join(kvs, ",")
377+
str += "}"
378+
}
379+
368380
if sft.Alias != "" {
369381
str += " AS " + sft.Alias.SQL()
370382
}

spanner/spansql/sql_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,42 @@ func TestSQL(t *testing.T) {
309309
`SELECT A, B AS banana FROM Table WHERE C < "whelp" AND D IS NOT NULL ORDER BY OCol DESC LIMIT 1000`,
310310
reparseQuery,
311311
},
312+
{
313+
Query{
314+
Select: Select{
315+
List: []Expr{ID("A")},
316+
From: []SelectFrom{SelectFromTable{
317+
Table: "Table",
318+
Hints: map[string]string{"FORCE_INDEX": "Idx"},
319+
}},
320+
Where: ComparisonOp{
321+
LHS: ID("B"),
322+
Op: Eq,
323+
RHS: Param("b"),
324+
},
325+
},
326+
},
327+
`SELECT A FROM Table@{FORCE_INDEX=Idx} WHERE B = @b`,
328+
reparseQuery,
329+
},
330+
{
331+
Query{
332+
Select: Select{
333+
List: []Expr{ID("A")},
334+
From: []SelectFrom{SelectFromTable{
335+
Table: "Table",
336+
Hints: map[string]string{"FORCE_INDEX": "Idx", "GROUPBY_SCAN_OPTIMIZATION": "TRUE"},
337+
}},
338+
Where: ComparisonOp{
339+
LHS: ID("B"),
340+
Op: Eq,
341+
RHS: Param("b"),
342+
},
343+
},
344+
},
345+
`SELECT A FROM Table@{FORCE_INDEX=Idx,GROUPBY_SCAN_OPTIMIZATION=TRUE} WHERE B = @b`,
346+
reparseQuery,
347+
},
312348
{
313349
Query{
314350
Select: Select{

spanner/spansql/types.go

+1
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ type SelectFrom interface {
394394
type SelectFromTable struct {
395395
Table ID
396396
Alias ID // empty if not aliased
397+
Hints map[string]string
397398
}
398399

399400
func (SelectFromTable) isSelectFrom() {}

0 commit comments

Comments
 (0)