Skip to content

Commit 8064e3d

Browse files
committed
chore: cleanup and add comments
1 parent c299895 commit 8064e3d

File tree

8 files changed

+189
-48
lines changed

8 files changed

+189
-48
lines changed

parser/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Spanner Statement Parser
22

3-
This package is an internal package and can make breaking changes without prior notice.
3+
This package is an internal package and can receive breaking changes without prior notice.

parser/simple_parser.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package parser
216

317
import (
@@ -159,6 +173,11 @@ func (p *simpleParser) isDollar() bool {
159173
return !p.isMultibyte() && p.sql[p.pos] == '$'
160174
}
161175

176+
// Identifier represents an identifier (e.g. table name, variable name) in a SQL string.
177+
// An identifier can consist of multiple parts separated by a dot in a SQL string.
178+
// E.g. table name my_schema.my_table is an Identifier with two parts:
179+
// - my_schema
180+
// - my_table
162181
type Identifier struct {
163182
Parts []string
164183
}
@@ -228,6 +247,7 @@ func (p *simpleParser) eatIdentifier() (Identifier, error) {
228247
return result, nil
229248
}
230249

250+
// Literal is a string, number or other literal in a SQL string.
231251
type Literal struct {
232252
Value string
233253
}

parser/statement_cache.go

Lines changed: 0 additions & 8 deletions
This file was deleted.

parser/statement_parser.go

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package parser
216

317
import (
@@ -50,6 +64,13 @@ func union(m1 map[string]bool, m2 map[string]bool) map[string]bool {
5064
return res
5165
}
5266

67+
type statementsCacheEntry struct {
68+
sql string
69+
params []string
70+
info *StatementInfo
71+
parsedStatement ParsedStatement
72+
}
73+
5374
var createParserLock sync.Mutex
5475
var statementParsers = sync.Map{}
5576

@@ -72,12 +93,22 @@ func getStatementParser(dialect databasepb.DatabaseDialect, cacheSize int) (*Sta
7293
}
7394
}
7495

96+
// StatementParser is a simple, dialect-aware SQL statement parser for Spanner.
97+
// It can be used to determine the type of SQL statement (e.g. DQL/DML/DDL), and
98+
// extract further information from the statement, such as the query parameters.
99+
//
100+
// This is an internal type that can receive breaking changes without prior notice.
75101
type StatementParser struct {
76102
Dialect databasepb.DatabaseDialect
77103
useCache bool
78104
statementsCache *lru.Cache[string, *statementsCacheEntry]
79105
}
80106

107+
// NewStatementParser creates a new parser for the given SQL dialect and with the given
108+
// cache size. Parsers can be shared among multiple database connections. The Spanner
109+
// database/sql driver will only create one parser per database dialect and cache size combination.
110+
//
111+
// This is an internal function that can receive breaking changes without prior notice.
81112
func NewStatementParser(dialect databasepb.DatabaseDialect, cacheSize int) (*StatementParser, error) {
82113
if cacheSize > 0 {
83114
cache, err := lru.New[string, *statementsCacheEntry](cacheSize)
@@ -89,32 +120,54 @@ func NewStatementParser(dialect databasepb.DatabaseDialect, cacheSize int) (*Sta
89120
return &StatementParser{Dialect: dialect}, nil
90121
}
91122

123+
// CacheSize returns the current size of the statement cache of this StatementParser.
92124
func (p *StatementParser) CacheSize() int {
93125
if p.useCache {
94126
return p.statementsCache.Len()
95127
}
96128
return 0
97129
}
98130

131+
// UseCache returns true if this StatementParser uses a cache.
99132
func (p *StatementParser) UseCache() bool {
100133
return p.useCache
101134
}
102135

136+
// supportsHashSingleLineComments returns true if the database dialect of this parser supports
137+
// comments of the following form:
138+
//
139+
// # This is a single-line comment.
140+
//
141+
// GoogleSQL supports this type of comment.
142+
// PostgreSQL does not support this type of comment.
103143
func (p *StatementParser) supportsHashSingleLineComments() bool {
104144
return p.Dialect != databasepb.DatabaseDialect_POSTGRESQL
105145
}
106146

147+
// supportsNestedComments returns true if the database dialect of this parser supports
148+
// nested comments. Nested comments means that comments of this style are supported:
149+
//
150+
// /* This is a comment. /* This is a nested comment. */ This is still a comment. */
151+
//
152+
// GoogleSQL does not support nested comments.
153+
// PostgreSQL supports nested comments.
107154
func (p *StatementParser) supportsNestedComments() bool {
108155
return p.Dialect == databasepb.DatabaseDialect_POSTGRESQL
109156
}
110157

158+
// identifierQuoteToken returns the token that is used for quoted identifiers.
159+
// GoogleSQL uses ` (backtick) for quoted identifiers.
160+
// PostgreSQL uses " (double quotes) for quoted identifiers.
111161
func (p *StatementParser) identifierQuoteToken() byte {
112162
if p.Dialect == databasepb.DatabaseDialect_POSTGRESQL {
113163
return '"'
114164
}
115165
return '`'
116166
}
117167

168+
// supportsBacktickQuotes returns true if the dialect supports backticks as a valid quote token.
169+
// GoogleSQL supports backtick quotes for identifiers.
170+
// PostgreSQL does not support backticks as a valid quote token.
118171
func (p *StatementParser) supportsBacktickQuotes() bool {
119172
return p.Dialect != databasepb.DatabaseDialect_POSTGRESQL
120173
}
@@ -125,10 +178,26 @@ func (p *StatementParser) supportsDoubleQuotedStringLiterals() bool {
125178
return p.Dialect != databasepb.DatabaseDialect_POSTGRESQL
126179
}
127180

181+
// supportsTripleQuotedLiterals returns true if the dialect supports quoted strings and identifiers
182+
// that start with three occurrences of the same quote token. Triple-quoted strings and identifiers
183+
// are allowed to contain linefeeds and single occurrences of the quote token. Example:
184+
//
185+
// ”'This is a string, and it's allowed to use a single quote inside the string”'
186+
//
187+
// GoogleSQL supports triple-quoted literals.
188+
// PostgreSQL does not support triple-quoted literals.
128189
func (p *StatementParser) supportsTripleQuotedLiterals() bool {
129190
return p.Dialect != databasepb.DatabaseDialect_POSTGRESQL
130191
}
131192

193+
// supportsDollarQuotedStrings returns true if the dialect supports strings that use double dollar signs
194+
// to mark the start and end of a string. The two dollar signs can optionally contain a tag. Examples:
195+
//
196+
// $$ This is a dollar-quoted string without a tag $$
197+
// $my_tag$ This is a dollar-quoted string with a tag $my_tag$
198+
//
199+
// GoogleSQL does not support dollar-quoted strings.
200+
// PostgreSQL supports dollar-quoted strings.
132201
func (p *StatementParser) supportsDollarQuotedStrings() bool {
133202
return p.Dialect == databasepb.DatabaseDialect_POSTGRESQL
134203
}
@@ -492,7 +561,7 @@ func (p *StatementParser) calculateFindParamsResult(sql string) (string, []strin
492561
func (p *StatementParser) ParseClientSideStatement(query string) (ParsedStatement, error) {
493562
if p.useCache {
494563
if val, ok := p.statementsCache.Get(query); ok {
495-
if val.info.StatementType == statementTypeClientSide {
564+
if val.info.StatementType == StatementTypeClientSide {
496565
return val.parsedStatement, nil
497566
}
498567
return nil, nil
@@ -513,7 +582,7 @@ func (p *StatementParser) ParseClientSideStatement(query string) (ParsedStatemen
513582
sql: query,
514583
parsedStatement: stmt,
515584
info: &StatementInfo{
516-
StatementType: statementTypeClientSide,
585+
StatementType: StatementTypeClientSide,
517586
},
518587
}
519588
p.statementsCache.Add(query, cacheEntry)
@@ -552,7 +621,7 @@ func isDmlKeyword(keyword string) bool {
552621
// of the sql string have been removed.
553622
func (p *StatementParser) isQuery(query string) bool {
554623
info := p.DetectStatementType(query)
555-
return info.StatementType == statementTypeQuery
624+
return info.StatementType == StatementTypeQuery
556625
}
557626

558627
func isCreateKeyword(keyword string) bool {
@@ -596,28 +665,46 @@ func isStatementKeyword(keyword string, keywords map[string]bool) bool {
596665
return ok
597666
}
598667

668+
// StatementType indicates the type of SQL statement.
599669
type StatementType int
600670

601671
const (
602-
statementTypeUnknown StatementType = iota
603-
statementTypeQuery
672+
// StatementTypeUnknown indicates that the parser was not able to determine the
673+
// type of SQL statement. This could be an indication that the SQL string is invalid,
674+
// or that it uses a syntax that is not (yet) supported by the parser.
675+
StatementTypeUnknown StatementType = iota
676+
// StatementTypeQuery indicates that the statement is a query that will return rows from
677+
// Spanner, and that will not make any modifications to the database.
678+
StatementTypeQuery
679+
// StatementTypeDml indicates that the statement is a data modification language (DML)
680+
// statement that will make modifications to the data in the database. It may or may not
681+
// return rows, depending on whether it contains a THEN RETURN (GoogleSQL) or RETURNING
682+
// (PostgreSQL) clause.
604683
StatementTypeDml
684+
// StatementTypeDdl indicates that the statement is a data definition language (DDL)
685+
// statement that will modify the schema of the database. It will never return rows.
605686
StatementTypeDdl
606-
statementTypeClientSide
687+
// StatementTypeClientSide indicates that the statement will be handled client-side in
688+
// the database/sql driver, and not be sent to Spanner. Examples of this includes SHOW
689+
// and SET statements.
690+
StatementTypeClientSide
607691
)
608692

609-
type dmlType int
693+
// DmlType designates the type of modification that a DML statement will execute.
694+
type DmlType int
610695

611696
const (
612-
dmlTypeUnknown dmlType = iota
697+
DmlTypeUnknown DmlType = iota
613698
DmlTypeInsert
614-
dmlTypeUpdate
615-
dmlTypeDelete
699+
DmlTypeUpdate
700+
DmlTypeDelete
616701
)
617702

703+
// StatementInfo contains the type of SQL statement, and in case of a DML statement,
704+
// the type of DML command.
618705
type StatementInfo struct {
619706
StatementType StatementType
620-
DmlType dmlType
707+
DmlType DmlType
621708
}
622709

623710
// DetectStatementType returns the type of SQL statement based on the first
@@ -643,7 +730,7 @@ func (p *StatementParser) calculateDetectStatementType(sql string) *StatementInf
643730
_ = parser.skipStatementHint()
644731
keyword := strings.ToUpper(parser.readKeyword())
645732
if isQueryKeyword(keyword) {
646-
return &StatementInfo{StatementType: statementTypeQuery}
733+
return &StatementInfo{StatementType: StatementTypeQuery}
647734
} else if isDmlKeyword(keyword) {
648735
return &StatementInfo{
649736
StatementType: StatementTypeDml,
@@ -652,16 +739,16 @@ func (p *StatementParser) calculateDetectStatementType(sql string) *StatementInf
652739
} else if isDDLKeyword(keyword) {
653740
return &StatementInfo{StatementType: StatementTypeDdl}
654741
}
655-
return &StatementInfo{StatementType: statementTypeUnknown}
742+
return &StatementInfo{StatementType: StatementTypeUnknown}
656743
}
657744

658-
func detectDmlKeyword(keyword string) dmlType {
745+
func detectDmlKeyword(keyword string) DmlType {
659746
if isStatementKeyword(keyword, insertStatements) {
660747
return DmlTypeInsert
661748
} else if isStatementKeyword(keyword, updateStatements) {
662-
return dmlTypeUpdate
749+
return DmlTypeUpdate
663750
} else if isStatementKeyword(keyword, deleteStatements) {
664-
return dmlTypeDelete
751+
return DmlTypeDelete
665752
}
666-
return dmlTypeUnknown
753+
return DmlTypeUnknown
667754
}

parser/statement_parser_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 Google LLC
1+
// Copyright 2025 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -2299,23 +2299,23 @@ func generateDetectStatementTypeTests() []detectStatementTypeTest {
22992299
return []detectStatementTypeTest{
23002300
{
23012301
input: "select 1",
2302-
want: statementTypeQuery,
2302+
want: StatementTypeQuery,
23032303
},
23042304
{
23052305
input: "from test",
2306-
want: statementTypeQuery,
2306+
want: StatementTypeQuery,
23072307
},
23082308
{
23092309
input: "with t as (select 1) select * from t",
2310-
want: statementTypeQuery,
2310+
want: StatementTypeQuery,
23112311
},
23122312
{
23132313
input: "GRAPH FinGraph\nMATCH (n)\nRETURN LABELS(n) AS label, n.id",
2314-
want: statementTypeQuery,
2314+
want: StatementTypeQuery,
23152315
},
23162316
{
23172317
input: "/* this is a comment */ -- this is also a comment\n @ { statement_hint_key=value } select 1",
2318-
want: statementTypeQuery,
2318+
want: StatementTypeQuery,
23192319
},
23202320
{
23212321
input: "update foo set bar=1 where true",
@@ -2343,23 +2343,23 @@ func generateDetectStatementTypeTests() []detectStatementTypeTest {
23432343
},
23442344
{
23452345
input: "input from borkisland",
2346-
want: statementTypeUnknown,
2346+
want: StatementTypeUnknown,
23472347
},
23482348
{
23492349
input: "start batch ddl",
2350-
want: statementTypeClientSide,
2350+
want: StatementTypeClientSide,
23512351
},
23522352
{
23532353
input: "set autocommit_dml_mode = 'partitioned_non_atomic'",
2354-
want: statementTypeClientSide,
2354+
want: StatementTypeClientSide,
23552355
},
23562356
{
23572357
input: "show variable commit_timestamp",
2358-
want: statementTypeClientSide,
2358+
want: StatementTypeClientSide,
23592359
},
23602360
{
23612361
input: "run batch",
2362-
want: statementTypeClientSide,
2362+
want: StatementTypeClientSide,
23632363
},
23642364
}
23652365
}
@@ -2374,7 +2374,7 @@ func TestDetectStatementType(t *testing.T) {
23742374
if cs, err := parser.ParseClientSideStatement(test.input); err != nil {
23752375
t.Errorf("failed to parse the statement as a client-side statement")
23762376
} else if cs != nil {
2377-
if g, w := statementTypeClientSide, test.want; g != w {
2377+
if g, w := StatementTypeClientSide, test.want; g != w {
23782378
t.Errorf("statement type mismatch for %q\n Got: %v\nWant: %v", test.input, g, w)
23792379
}
23802380
} else if g, w := parser.DetectStatementType(test.input).StatementType, test.want; g != w {
@@ -2623,7 +2623,7 @@ func benchmarkDetectStatementType(b *testing.B, parser *StatementParser) {
26232623
if cs, err := parser.ParseClientSideStatement(test.input); err != nil {
26242624
b.Errorf("failed to parse the statement as a client-side statement")
26252625
} else if cs != nil {
2626-
if g, w := statementTypeClientSide, test.want; g != w {
2626+
if g, w := StatementTypeClientSide, test.want; g != w {
26272627
b.Errorf("statement type mismatch for %q\n Got: %v\nWant: %v", test.input, g, w)
26282628
}
26292629
} else if g, w := parser.DetectStatementType(test.input).StatementType, test.want; g != w {

0 commit comments

Comments
 (0)