15
15
package parser
16
16
17
17
import (
18
+ "fmt"
19
+
18
20
"cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
19
21
"google.golang.org/grpc/codes"
20
22
"google.golang.org/grpc/status"
@@ -142,11 +144,27 @@ func (s *ParsedShowStatement) parse(parser *StatementParser, query string) error
142
144
143
145
// ParsedSetStatement is a statement of the form
144
146
// SET [SESSION | LOCAL] [my_extension.]my_property {=|to} <value>
147
+ //
148
+ // It also covers statements of the form SET TRANSACTION. This is a
149
+ // synonym for SET LOCAL, but is only supported for a specific set of
150
+ // properties, and may only be executed before a transaction has been
151
+ // activated. Examples include:
152
+ // SET TRANSACTION READ ONLY
153
+ // SET TRANSACTION ISOLATION LEVEL [SERIALIZABLE | REPEATABLE READ]
154
+ //
155
+ // One SET statement can set more than one property.
145
156
type ParsedSetStatement struct {
146
- query string
147
- Identifier Identifier
148
- Literal Literal
149
- IsLocal bool
157
+ query string
158
+ // Identifiers contains the properties that are being set. The number of elements in this slice
159
+ // must be equal to the number of Literals.
160
+ Identifiers []Identifier
161
+ // Literals contains the values that should be set for the properties.
162
+ Literals []Literal
163
+ // IsLocal indicates whether this is a SET LOCAL statement or not.
164
+ IsLocal bool
165
+ // IsTransaction indicates whether this is a SET TRANSACTION statement or not.
166
+ // IsTransaction automatically also implies IsLocal.
167
+ IsTransaction bool
150
168
}
151
169
152
170
func (s * ParsedSetStatement ) Name () string {
@@ -165,10 +183,17 @@ func (s *ParsedSetStatement) parse(parser *StatementParser, query string) error
165
183
return status .Errorf (codes .InvalidArgument , "syntax error: expected SET" )
166
184
}
167
185
isLocal := sp .eatKeyword ("LOCAL" )
168
- if ! isLocal && parser .Dialect == databasepb .DatabaseDialect_POSTGRESQL {
186
+ isTransaction := false
187
+ if ! isLocal {
188
+ isTransaction = sp .eatKeyword ("TRANSACTION" )
189
+ }
190
+ if ! isLocal && ! isTransaction && parser .Dialect == databasepb .DatabaseDialect_POSTGRESQL {
169
191
// Just eat and ignore the SESSION keyword if it exists, as SESSION is the default.
170
192
_ = sp .eatKeyword ("SESSION" )
171
193
}
194
+ if isTransaction {
195
+ return s .parseSetTransaction (sp , query )
196
+ }
172
197
identifier , err := sp .eatIdentifier ()
173
198
if err != nil {
174
199
return err
@@ -191,12 +216,93 @@ func (s *ParsedSetStatement) parse(parser *StatementParser, query string) error
191
216
return status .Errorf (codes .InvalidArgument , "unexpected tokens at position %d in %q" , sp .pos , sp .sql )
192
217
}
193
218
s .query = query
194
- s .Identifier = identifier
195
- s .Literal = literalValue
219
+ s .Identifiers = [] Identifier { identifier }
220
+ s .Literals = [] Literal { literalValue }
196
221
s .IsLocal = isLocal
197
222
return nil
198
223
}
199
224
225
+ func (s * ParsedSetStatement ) parseSetTransaction (sp * simpleParser , query string ) error {
226
+ if ! sp .hasMoreTokens () {
227
+ return status .Errorf (codes .InvalidArgument , "syntax error: missing TRANSACTION OPTION, expected one of ISOLATION LEVEL, READ WRITE, or READ ONLY" )
228
+ }
229
+ s .query = query
230
+ s .IsLocal = true
231
+ s .IsTransaction = true
232
+
233
+ for {
234
+ if sp .peekKeyword ("ISOLATION" ) {
235
+ if err := s .parseSetTransactionIsolationLevel (sp , query ); err != nil {
236
+ return err
237
+ }
238
+ } else if sp .peekKeyword ("READ" ) {
239
+ if err := s .parseSetTransactionMode (sp , query ); err != nil {
240
+ return err
241
+ }
242
+ } else if sp .statementParser .Dialect == databasepb .DatabaseDialect_POSTGRESQL && (sp .peekKeyword ("DEFERRABLE" ) || sp .peekKeyword ("NOT" )) {
243
+ // https://www.postgresql.org/docs/current/sql-set-transaction.html
244
+ if err := s .parseSetTransactionDeferrable (sp , query ); err != nil {
245
+ return err
246
+ }
247
+ } else {
248
+ return status .Error (codes .InvalidArgument , "invalid TRANSACTION option, expected one of ISOLATION LEVEL, READ WRITE, or READ ONLY" )
249
+ }
250
+ if ! sp .hasMoreTokens () {
251
+ return nil
252
+ }
253
+ // Eat and ignore any commas separating the various options.
254
+ sp .eatToken (',' )
255
+ }
256
+ }
257
+
258
+ func (s * ParsedSetStatement ) parseSetTransactionIsolationLevel (sp * simpleParser , query string ) error {
259
+ if ! sp .eatKeywords ([]string {"ISOLATION" , "LEVEL" }) {
260
+ return status .Errorf (codes .InvalidArgument , "syntax error: expected ISOLATION LEVEL" )
261
+ }
262
+ var value Literal
263
+ if sp .eatKeyword ("SERIALIZABLE" ) {
264
+ value = Literal {Value : "serializable" }
265
+ } else if sp .eatKeywords ([]string {"REPEATABLE" , "READ" }) {
266
+ value = Literal {Value : "repeatable_read" }
267
+ } else {
268
+ return status .Errorf (codes .InvalidArgument , "syntax error: expected SERIALIZABLE OR REPETABLE READ" )
269
+ }
270
+
271
+ s .Identifiers = append (s .Identifiers , Identifier {Parts : []string {"isolation_level" }})
272
+ s .Literals = append (s .Literals , value )
273
+ return nil
274
+ }
275
+
276
+ func (s * ParsedSetStatement ) parseSetTransactionMode (sp * simpleParser , query string ) error {
277
+ readOnly := false
278
+ if sp .eatKeywords ([]string {"READ" , "ONLY" }) {
279
+ readOnly = true
280
+ } else if sp .eatKeywords ([]string {"READ" , "WRITE" }) {
281
+ readOnly = false
282
+ } else {
283
+ return status .Errorf (codes .InvalidArgument , "syntax error: expected READ ONLY or READ WRITE" )
284
+ }
285
+
286
+ s .Identifiers = append (s .Identifiers , Identifier {Parts : []string {"transaction_read_only" }})
287
+ s .Literals = append (s .Literals , Literal {Value : fmt .Sprintf ("%v" , readOnly )})
288
+ return nil
289
+ }
290
+
291
+ func (s * ParsedSetStatement ) parseSetTransactionDeferrable (sp * simpleParser , query string ) error {
292
+ deferrable := false
293
+ if sp .eatKeywords ([]string {"NOT" , "DEFERRABLE" }) {
294
+ deferrable = false
295
+ } else if sp .eatKeyword ("DEFERRABLE" ) {
296
+ deferrable = true
297
+ } else {
298
+ return status .Errorf (codes .InvalidArgument , "syntax error: expected [NOT] DEFERRABLE" )
299
+ }
300
+
301
+ s .Identifiers = append (s .Identifiers , Identifier {Parts : []string {"transaction_deferrable" }})
302
+ s .Literals = append (s .Literals , Literal {Value : fmt .Sprintf ("%v" , deferrable )})
303
+ return nil
304
+ }
305
+
200
306
// ParsedResetStatement is a statement of the form
201
307
// RESET [my_extension.]my_property
202
308
type ParsedResetStatement struct {
@@ -404,6 +510,7 @@ func (s *ParsedBeginStatement) parse(parser *StatementParser, query string) erro
404
510
// Parse a statement of the form
405
511
// GoogleSQL: BEGIN [TRANSACTION]
406
512
// PostgreSQL: {START | BEGIN} [{TRANSACTION | WORK}] (https://www.postgresql.org/docs/current/sql-begin.html)
513
+ // TODO: Support transaction modes in the BEGIN / START statement.
407
514
sp := & simpleParser {sql : []byte (query ), statementParser : parser }
408
515
if sp .statementParser .Dialect == databasepb .DatabaseDialect_POSTGRESQL {
409
516
if ! sp .eatKeyword ("START" ) && ! sp .eatKeyword ("BEGIN" ) {
0 commit comments