From 6e961d4d3bb4c28b9e571136fcba126be9c875eb Mon Sep 17 00:00:00 2001 From: "takeshi.nakata" Date: Mon, 19 Jul 2021 15:29:15 +0900 Subject: [PATCH 1/3] 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. --- spanner/spansql/parser.go | 84 ++++++++++++++++++++-------------- spanner/spansql/parser_test.go | 14 ++++++ spanner/spansql/sql.go | 8 ++++ spanner/spansql/sql_test.go | 18 ++++++++ spanner/spansql/types.go | 1 + 5 files changed, 91 insertions(+), 34 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index afe7d210d6e..6c907bf8782 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -1885,7 +1885,7 @@ func (p *parser) parseQuery() (Query, *parseError) { [ LIMIT count [ OFFSET skip_rows ] ] */ - // TODO: hints, sub-selects, etc. + // TODO: sub-selects, etc. if err := p.expect("SELECT"); err != nil { return Query{}, err @@ -2111,6 +2111,13 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) { return nil, err } sf := SelectFromTable{Table: tname} + if p.eat("@") { + hint, err := p.parseHints(map[string]string{}, false) + if err != nil { + return nil, err + } + sf.Hint = hint + } // TODO: The "AS" keyword is optional. if p.eat("AS") { @@ -2159,46 +2166,20 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) { Type: jt, LHS: sf, } - setHint := func(k, v string) { - if sfj.Hints == nil { - sfj.Hints = make(map[string]string) - } - sfj.Hints[k] = v - } + var hints map[string]string if hashJoin { - setHint("JOIN_METHOD", "HASH_JOIN") + hints = map[string]string{} + hints["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 { + h, err := p.parseHints(hints, true) + if err != nil { return nil, err } + hints = h } + sfj.Hints = hints sfj.RHS, err = p.parseSelectFrom() if err != nil { @@ -2889,6 +2870,41 @@ func (p *parser) parseAlias() (ID, *parseError) { return p.parseTableOrIndexOrColumnName() } +func (p *parser) parseHints(hints map[string]string, multiple bool) (map[string]string, *parseError) { + if hints == nil { + hints = map[string]string{} + } + 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 + hints[k] = v + if !multiple || !p.eat(",") { + break + } + } + if err := p.expect("}"); err != nil { + return nil, err + } + return hints, nil +} + func (p *parser) parseTableOrIndexOrColumnName() (ID, *parseError) { /* table_name and column_name and index_name: diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index fbc68c55970..374a07f1b83 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -114,6 +114,20 @@ func TestParseQuery(t *testing.T) { }, }, }, + // with table hint + {`SELECT * FROM Packages@{FORCE_INDEX=PackagesIdx} WHERE package_idx=@packageIdx`, + Query{ + Select: Select{ + List: []Expr{Star}, + From: []SelectFrom{SelectFromTable{Table: "Packages", Hint: map[string]string{"FORCE_INDEX": "PackagesIdx"}}}, + Where: ComparisonOp{ + Op: Eq, + LHS: ID("package_idx"), + RHS: Param("packageIdx"), + }, + }, + }, + }, {`SELECT * FROM A INNER JOIN B ON A.w = B.y`, Query{ Select: Select{ diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 19aac6dc480..c7d34e65917 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -365,6 +365,14 @@ func (sel Select) addSQL(sb *strings.Builder) { func (sft SelectFromTable) SQL() string { str := sft.Table.SQL() + if len(sft.Hint) > 0 { + for k, v := range sft.Hint { + str += fmt.Sprintf("@{%s=%s}", k, v) + // table hint exists only one key + break + } + } + if sft.Alias != "" { str += " AS " + sft.Alias.SQL() } diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 3632aba06b3..6c16ac5ea1f 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -309,6 +309,24 @@ func TestSQL(t *testing.T) { `SELECT A, B AS banana FROM Table WHERE C < "whelp" AND D IS NOT NULL ORDER BY OCol DESC LIMIT 1000`, reparseQuery, }, + { + Query{ + Select: Select{ + List: []Expr{ID("A")}, + From: []SelectFrom{SelectFromTable{ + Table: "Table", + Hint: map[string]string{"FORCE_INDEX": "Idx"}, + }}, + Where: ComparisonOp{ + LHS: ID("B"), + Op: Eq, + RHS: Param("b"), + }, + }, + }, + `SELECT A FROM Table@{FORCE_INDEX=Idx} WHERE B = @b`, + reparseQuery, + }, { Query{ Select: Select{ diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 34d0e0fee5f..3dbb782d749 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -394,6 +394,7 @@ type SelectFrom interface { type SelectFromTable struct { Table ID Alias ID // empty if not aliased + Hint map[string]string } func (SelectFromTable) isSelectFrom() {} From 7191acffedf9c0a27523b77c32d9c9759117c927 Mon Sep 17 00:00:00 2001 From: "takeshi.nakata" Date: Mon, 19 Jul 2021 18:35:41 +0900 Subject: [PATCH 2/3] feat(spanner/spansql): modify to enable multiple table hint keys. --- spanner/spansql/parser.go | 10 +++++----- spanner/spansql/parser_test.go | 18 ++++++++++++++++-- spanner/spansql/sql.go | 14 +++++++++----- spanner/spansql/sql_test.go | 20 +++++++++++++++++++- spanner/spansql/types.go | 2 +- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go index 6c907bf8782..ab3220271d5 100644 --- a/spanner/spansql/parser.go +++ b/spanner/spansql/parser.go @@ -2112,11 +2112,11 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) { } sf := SelectFromTable{Table: tname} if p.eat("@") { - hint, err := p.parseHints(map[string]string{}, false) + hints, err := p.parseHints(map[string]string{}) if err != nil { return nil, err } - sf.Hint = hint + sf.Hints = hints } // TODO: The "AS" keyword is optional. @@ -2173,7 +2173,7 @@ func (p *parser) parseSelectFrom() (SelectFrom, *parseError) { } if p.eat("@") { - h, err := p.parseHints(hints, true) + h, err := p.parseHints(hints) if err != nil { return nil, err } @@ -2870,7 +2870,7 @@ func (p *parser) parseAlias() (ID, *parseError) { return p.parseTableOrIndexOrColumnName() } -func (p *parser) parseHints(hints map[string]string, multiple bool) (map[string]string, *parseError) { +func (p *parser) parseHints(hints map[string]string) (map[string]string, *parseError) { if hints == nil { hints = map[string]string{} } @@ -2895,7 +2895,7 @@ func (p *parser) parseHints(hints map[string]string, multiple bool) (map[string] } v := tok.value hints[k] = v - if !multiple || !p.eat(",") { + if !p.eat(",") { break } } diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index 374a07f1b83..fd1360c8506 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -114,12 +114,26 @@ func TestParseQuery(t *testing.T) { }, }, }, - // with table hint + // with single table hint {`SELECT * FROM Packages@{FORCE_INDEX=PackagesIdx} WHERE package_idx=@packageIdx`, Query{ Select: Select{ List: []Expr{Star}, - From: []SelectFrom{SelectFromTable{Table: "Packages", Hint: map[string]string{"FORCE_INDEX": "PackagesIdx"}}}, + From: []SelectFrom{SelectFromTable{Table: "Packages", Hints: map[string]string{"FORCE_INDEX": "PackagesIdx"}}}, + Where: ComparisonOp{ + Op: Eq, + LHS: ID("package_idx"), + RHS: Param("packageIdx"), + }, + }, + }, + }, + // with multiple table hints + {`SELECT * FROM Packages@{ FORCE_INDEX=PackagesIdx, GROUPBY_SCAN_OPTIMIZATION=TRUE } WHERE package_idx=@packageIdx`, + Query{ + Select: Select{ + List: []Expr{Star}, + From: []SelectFrom{SelectFromTable{Table: "Packages", Hints: map[string]string{"FORCE_INDEX": "PackagesIdx", "GROUPBY_SCAN_OPTIMIZATION": "TRUE"}}}, Where: ComparisonOp{ Op: Eq, LHS: ID("package_idx"), diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index c7d34e65917..0c272d75a6f 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -365,12 +365,16 @@ func (sel Select) addSQL(sb *strings.Builder) { func (sft SelectFromTable) SQL() string { str := sft.Table.SQL() - if len(sft.Hint) > 0 { - for k, v := range sft.Hint { - str += fmt.Sprintf("@{%s=%s}", k, v) - // table hint exists only one key - break + if len(sft.Hints) > 0 { + str += "@{" + kvs := make([]string, len(sft.Hints)) + i := 0 + for k, v := range sft.Hints { + kvs[i] = fmt.Sprintf("%s=%s", k, v) + i += 1 } + str += strings.Join(kvs, ",") + str += "}" } if sft.Alias != "" { diff --git a/spanner/spansql/sql_test.go b/spanner/spansql/sql_test.go index 6c16ac5ea1f..74527149ab3 100644 --- a/spanner/spansql/sql_test.go +++ b/spanner/spansql/sql_test.go @@ -315,7 +315,7 @@ func TestSQL(t *testing.T) { List: []Expr{ID("A")}, From: []SelectFrom{SelectFromTable{ Table: "Table", - Hint: map[string]string{"FORCE_INDEX": "Idx"}, + Hints: map[string]string{"FORCE_INDEX": "Idx"}, }}, Where: ComparisonOp{ LHS: ID("B"), @@ -327,6 +327,24 @@ func TestSQL(t *testing.T) { `SELECT A FROM Table@{FORCE_INDEX=Idx} WHERE B = @b`, reparseQuery, }, + { + Query{ + Select: Select{ + List: []Expr{ID("A")}, + From: []SelectFrom{SelectFromTable{ + Table: "Table", + Hints: map[string]string{"FORCE_INDEX": "Idx", "GROUPBY_SCAN_OPTIMIZATION": "TRUE"}, + }}, + Where: ComparisonOp{ + LHS: ID("B"), + Op: Eq, + RHS: Param("b"), + }, + }, + }, + `SELECT A FROM Table@{FORCE_INDEX=Idx,GROUPBY_SCAN_OPTIMIZATION=TRUE} WHERE B = @b`, + reparseQuery, + }, { Query{ Select: Select{ diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go index 3dbb782d749..e7d622f6d5a 100644 --- a/spanner/spansql/types.go +++ b/spanner/spansql/types.go @@ -394,7 +394,7 @@ type SelectFrom interface { type SelectFromTable struct { Table ID Alias ID // empty if not aliased - Hint map[string]string + Hints map[string]string } func (SelectFromTable) isSelectFrom() {} From 83d63c51f46dd98706915589f285dbb177878383 Mon Sep 17 00:00:00 2001 From: "takeshi.nakata" Date: Mon, 19 Jul 2021 21:36:02 +0900 Subject: [PATCH 3/3] feat(spanner/spansql): use ++ increment for lint --- spanner/spansql/sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go index 0c272d75a6f..80289d864fa 100644 --- a/spanner/spansql/sql.go +++ b/spanner/spansql/sql.go @@ -371,7 +371,7 @@ func (sft SelectFromTable) SQL() string { i := 0 for k, v := range sft.Hints { kvs[i] = fmt.Sprintf("%s=%s", k, v) - i += 1 + i++ } str += strings.Join(kvs, ",") str += "}"