1515package parser
1616
1717import (
18+ "fmt"
19+
1820 "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
1921 "google.golang.org/grpc/codes"
2022 "google.golang.org/grpc/status"
@@ -142,11 +144,27 @@ func (s *ParsedShowStatement) parse(parser *StatementParser, query string) error
142144
143145// ParsedSetStatement is a statement of the form
144146// 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.
145156type 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
150168}
151169
152170func (s * ParsedSetStatement ) Name () string {
@@ -165,10 +183,17 @@ func (s *ParsedSetStatement) parse(parser *StatementParser, query string) error
165183 return status .Errorf (codes .InvalidArgument , "syntax error: expected SET" )
166184 }
167185 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 {
169191 // Just eat and ignore the SESSION keyword if it exists, as SESSION is the default.
170192 _ = sp .eatKeyword ("SESSION" )
171193 }
194+ if isTransaction {
195+ return s .parseSetTransaction (sp , query )
196+ }
172197 identifier , err := sp .eatIdentifier ()
173198 if err != nil {
174199 return err
@@ -191,12 +216,93 @@ func (s *ParsedSetStatement) parse(parser *StatementParser, query string) error
191216 return status .Errorf (codes .InvalidArgument , "unexpected tokens at position %d in %q" , sp .pos , sp .sql )
192217 }
193218 s .query = query
194- s .Identifier = identifier
195- s .Literal = literalValue
219+ s .Identifiers = [] Identifier { identifier }
220+ s .Literals = [] Literal { literalValue }
196221 s .IsLocal = isLocal
197222 return nil
198223}
199224
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+
200306// ParsedResetStatement is a statement of the form
201307// RESET [my_extension.]my_property
202308type ParsedResetStatement struct {
@@ -404,6 +510,7 @@ func (s *ParsedBeginStatement) parse(parser *StatementParser, query string) erro
404510 // Parse a statement of the form
405511 // GoogleSQL: BEGIN [TRANSACTION]
406512 // PostgreSQL: {START | BEGIN} [{TRANSACTION | WORK}] (https://www.postgresql.org/docs/current/sql-begin.html)
513+ // TODO: Support transaction modes in the BEGIN / START statement.
407514 sp := & simpleParser {sql : []byte (query ), statementParser : parser }
408515 if sp .statementParser .Dialect == databasepb .DatabaseDialect_POSTGRESQL {
409516 if ! sp .eatKeyword ("START" ) && ! sp .eatKeyword ("BEGIN" ) {
0 commit comments