Skip to content

Commit 7f78b3b

Browse files
committed
chore: parse SET/SHOW statements with simple parser
Parse SET/SHOW statements using the simple parser. Any connection variable that is not covered by a regular expression client-side statement, will be picked up by this simple parser. In a following step, all regex-based client-side statements will be removed, and the parsing will happen using the simple parser. This further simplifies and unifies all client-side statement parsing and handling, and makes all connection variables transactional.
1 parent 1803771 commit 7f78b3b

File tree

8 files changed

+1018
-7
lines changed

8 files changed

+1018
-7
lines changed

conn.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,40 @@ func (c *conn) CommitResponse() (commitResponse *spanner.CommitResponse, err err
268268
return c.commitResponse, nil
269269
}
270270

271+
func (c *conn) showConnectionVariable(identifier identifier) (any, error) {
272+
extension, name, err := toExtensionAndName(identifier)
273+
if err != nil {
274+
return nil, err
275+
}
276+
return c.state.GetValue(extension, name)
277+
}
278+
279+
func (c *conn) setConnectionVariable(identifier identifier, value string, local bool) error {
280+
extension, name, err := toExtensionAndName(identifier)
281+
if err != nil {
282+
return err
283+
}
284+
if local {
285+
return c.state.SetLocalValue(extension, name, value)
286+
}
287+
return c.state.SetValue(extension, name, value, connectionstate.ContextUser)
288+
}
289+
290+
func toExtensionAndName(identifier identifier) (string, string, error) {
291+
var extension string
292+
var name string
293+
if len(identifier.parts) == 1 {
294+
extension = ""
295+
name = identifier.parts[0]
296+
} else if len(identifier.parts) == 2 {
297+
extension = identifier.parts[0]
298+
name = identifier.parts[1]
299+
} else {
300+
return "", "", status.Errorf(codes.InvalidArgument, "invalid variable name: %s", identifier)
301+
}
302+
return extension, name, nil
303+
}
304+
271305
func (c *conn) RetryAbortsInternally() bool {
272306
return propertyRetryAbortsInternally.GetValueOrDefault(c.state)
273307
}

conn_with_mockserver_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,3 +634,150 @@ func TestConfigureConnectionProperty(t *testing.T) {
634634
}
635635
}
636636
}
637+
638+
func TestSetAndShowWithExtension(t *testing.T) {
639+
t.Parallel()
640+
641+
db, _, teardown := setupTestDBConnection(t)
642+
defer teardown()
643+
ctx := context.Background()
644+
645+
conn, err := db.Conn(ctx)
646+
if err != nil {
647+
t.Fatal(err)
648+
}
649+
defer func() { _ = conn.Close() }()
650+
651+
// Getting an unknown variable fails, even if it is a variable with an extension.
652+
if _, err := conn.QueryContext(ctx, "show my_extension.my_property"); err == nil {
653+
t.Fatal("missing expected error")
654+
}
655+
656+
// Setting an unknown variable with an extension is allowed.
657+
if _, err := conn.ExecContext(context.Background(), "set my_extension.my_property='my-value'"); err != nil {
658+
t.Fatal(err)
659+
}
660+
it, err := conn.QueryContext(ctx, "show my_extension.my_property")
661+
if err != nil {
662+
t.Fatal(err)
663+
}
664+
if !it.Next() {
665+
t.Fatal("expected it.Next to return true")
666+
}
667+
var val string
668+
if err := it.Scan(&val); err != nil {
669+
t.Fatal(err)
670+
}
671+
if g, w := val, "my-value"; g != w {
672+
t.Fatalf("value mismatch\n Got: %v\nWant: %v", g, w)
673+
}
674+
if it.Next() {
675+
t.Fatal("expected it.Next to return false")
676+
}
677+
if err := it.Close(); err != nil {
678+
t.Fatal(err)
679+
}
680+
}
681+
682+
func TestSetAndShowIsolationLevel(t *testing.T) {
683+
t.Parallel()
684+
685+
db, server, teardown := setupTestDBConnection(t)
686+
defer teardown()
687+
ctx := context.Background()
688+
689+
conn, err := db.Conn(ctx)
690+
if err != nil {
691+
t.Fatal(err)
692+
}
693+
defer func() { _ = conn.Close() }()
694+
695+
// Isolation level should start with 'Default'.
696+
row := conn.QueryRowContext(ctx, "show isolation_level")
697+
var val string
698+
if err := row.Scan(&val); err != nil {
699+
t.Fatal(err)
700+
}
701+
if g, w := val, sql.LevelDefault.String(); g != w {
702+
t.Fatalf("value mismatch\n Got: %v\nWant: %v", g, w)
703+
}
704+
705+
// We can set the isolation level using a SET statement.
706+
if _, err := conn.ExecContext(context.Background(), "set isolation_level = 'repeatable_read'"); err != nil {
707+
t.Fatal(err)
708+
}
709+
// The isolation level should now be 'RepeatableRead'.
710+
row = conn.QueryRowContext(ctx, "show isolation_level")
711+
if err := row.Scan(&val); err != nil {
712+
t.Fatal(err)
713+
}
714+
if g, w := val, sql.LevelRepeatableRead.String(); g != w {
715+
t.Fatalf("value mismatch\n Got: %v\nWant: %v", g, w)
716+
}
717+
// Verify that this is also the value that the connection uses.
718+
tx, err := conn.BeginTx(ctx, &sql.TxOptions{})
719+
if err != nil {
720+
t.Fatal(err)
721+
}
722+
if _, err := tx.ExecContext(ctx, testutil.UpdateBarSetFoo); err != nil {
723+
t.Fatal(err)
724+
}
725+
if err := tx.Commit(); err != nil {
726+
t.Fatal(err)
727+
}
728+
requests := drainRequestsFromServer(server.TestSpanner)
729+
executeRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
730+
if g, w := len(executeRequests), 1; g != w {
731+
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
732+
}
733+
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
734+
wantIsolationLevel, _ := toProtoIsolationLevel(sql.LevelRepeatableRead)
735+
if g, w := request.Transaction.GetBegin().GetIsolationLevel(), wantIsolationLevel; g != w {
736+
t.Fatalf("begin isolation level mismatch\n Got: %v\nWant: %v", g, w)
737+
}
738+
}
739+
740+
func TestSetLocalIsolationLevel(t *testing.T) {
741+
t.Skip("temporarily skipped, as transactions get their settings from the connection when the BeginTx call is done, instead of reading them when the transaction is actually started")
742+
t.Parallel()
743+
744+
db, server, teardown := setupTestDBConnection(t)
745+
defer teardown()
746+
ctx := context.Background()
747+
748+
// Start a transaction without specifying the isolation level.
749+
tx, err := db.BeginTx(ctx, &sql.TxOptions{})
750+
if err != nil {
751+
t.Fatal(err)
752+
}
753+
// We can set the isolation level only for this transaction using a SET LOCAL statement.
754+
if _, err := tx.ExecContext(context.Background(), "set local isolation_level = 'repeatable_read'"); err != nil {
755+
t.Fatal(err)
756+
}
757+
// The isolation level should now be 'RepeatableRead'.
758+
var val string
759+
row := tx.QueryRowContext(ctx, "show isolation_level")
760+
if err := row.Scan(&val); err != nil {
761+
t.Fatal(err)
762+
}
763+
if g, w := val, sql.LevelRepeatableRead.String(); g != w {
764+
t.Fatalf("value mismatch\n Got: %v\nWant: %v", g, w)
765+
}
766+
// Verify that the transaction actually uses the isolation level.
767+
if _, err := tx.ExecContext(ctx, testutil.UpdateBarSetFoo); err != nil {
768+
t.Fatal(err)
769+
}
770+
if err := tx.Commit(); err != nil {
771+
t.Fatal(err)
772+
}
773+
requests := drainRequestsFromServer(server.TestSpanner)
774+
executeRequests := requestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
775+
if g, w := len(executeRequests), 1; g != w {
776+
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
777+
}
778+
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
779+
wantIsolationLevel, _ := toProtoIsolationLevel(sql.LevelRepeatableRead)
780+
if g, w := request.Transaction.GetBegin().GetIsolationLevel(), wantIsolationLevel; g != w {
781+
t.Fatalf("begin isolation level mismatch\n Got: %v\nWant: %v", g, w)
782+
}
783+
}

connectionstate/connection_property.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ type ConnectionProperty interface {
5555
CreateDefaultValue() ConnectionPropertyValue
5656
// CreateInitialValue creates an initial value of the property with the given value as the default and reset value.
5757
CreateInitialValue(value any) (ConnectionPropertyValue, error)
58+
// GetValue returns the current value of the property.
59+
GetValue(state *ConnectionState) (any, error)
60+
// SetUntypedValue sets a new value for the property without knowing the type in advance.
61+
// This is a synonym for SetValue.
62+
SetUntypedValue(state *ConnectionState, value any, context Context) error
63+
// SetLocalUntypedValue sets a new local value for the property without knowing the type in advance.
64+
// This is a synonym for SetLocalValue.
65+
SetLocalUntypedValue(state *ConnectionState, value any) error
5866
// Convert converts a string to the corresponding value type of the connection property.
5967
Convert(value string) (any, error)
6068
}
@@ -155,6 +163,11 @@ func (p *TypedConnectionProperty[T]) Convert(value string) (any, error) {
155163
return p.converter(value)
156164
}
157165

166+
// GetValue implements ConnectionPropertyValue.GetValue
167+
func (p *TypedConnectionProperty[T]) GetValue(state *ConnectionState) (any, error) {
168+
return p.GetValueOrError(state)
169+
}
170+
158171
// GetValueOrDefault returns the current value of the property in the given ConnectionState.
159172
// It returns the default of the property if no value is found.
160173
func (p *TypedConnectionProperty[T]) GetValueOrDefault(state *ConnectionState) T {
@@ -202,6 +215,15 @@ func (p *TypedConnectionProperty[T]) ResetValue(state *ConnectionState, context
202215
return p.setConnectionStateValue(state, typedResetValue /*isReset=*/, true, context)
203216
}
204217

218+
// SetUntypedValue implements ConnectionProperty.SetUntypedValue.
219+
func (p *TypedConnectionProperty[T]) SetUntypedValue(state *ConnectionState, value any, context Context) error {
220+
valueT, ok := value.(T)
221+
if !ok {
222+
return status.Errorf(codes.InvalidArgument, "invalid type for value: %T", value)
223+
}
224+
return p.SetValue(state, valueT, context)
225+
}
226+
205227
// SetValue sets the value of the property in the given ConnectionState.
206228
//
207229
// The given Context should indicate the current context where the application tries to reset the value, e.g. it should
@@ -242,6 +264,15 @@ func (p *TypedConnectionProperty[T]) setConnectionStateValue(state *ConnectionSt
242264
return nil
243265
}
244266

267+
// SetLocalUntypedValue implements ConnectionProperty.SetLocalUntypedValue.
268+
func (p *TypedConnectionProperty[T]) SetLocalUntypedValue(state *ConnectionState, value any) error {
269+
valueT, ok := value.(T)
270+
if !ok {
271+
return status.Errorf(codes.InvalidArgument, "invalid type for value: %T", value)
272+
}
273+
return p.SetLocalValue(state, valueT)
274+
}
275+
245276
// SetLocalValue sets the local value of the property in the given ConnectionState. A local value is only visible
246277
// for the remainder of the current transaction. The value is reset to the value it had before the transaction when the
247278
// transaction ends, regardless whether the transaction committed or rolled back.
@@ -311,6 +342,8 @@ type ConnectionPropertyValue interface {
311342
ConnectionProperty() ConnectionProperty
312343
// Copy creates a shallow copy of the ConnectionPropertyValue.
313344
Copy() ConnectionPropertyValue
345+
// GetValue gets the current value of the property.
346+
GetValue() (any, error)
314347
// ClearValue removes the value of the property.
315348
ClearValue(context Context) error
316349
// SetValue sets the value of the property. The given value must be a valid value for the property.
@@ -350,6 +383,10 @@ func (v *connectionPropertyValue[T]) Copy() ConnectionPropertyValue {
350383
}
351384
}
352385

386+
func (v *connectionPropertyValue[T]) GetValue() (any, error) {
387+
return v.value, nil
388+
}
389+
353390
func (v *connectionPropertyValue[T]) ClearValue(context Context) error {
354391
if v.connectionProperty.context < context {
355392
return status.Errorf(codes.FailedPrecondition, "property has context %s and cannot be set in context %s", v.connectionProperty.context, context)

connectionstate/connection_state.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,67 @@ func NewConnectionState(connectionStateType Type, properties map[string]Connecti
110110
return state, nil
111111
}
112112

113+
func toKey(extension, name string) (key string) {
114+
if extension == "" {
115+
key = name
116+
} else {
117+
key = name + "." + extension
118+
}
119+
return
120+
}
121+
122+
func (cs *ConnectionState) GetValue(extension, name string) (any, error) {
123+
prop, err := cs.findProperty(extension, name)
124+
if err != nil {
125+
return nil, err
126+
}
127+
return prop.GetValue(cs)
128+
}
129+
130+
func (cs *ConnectionState) SetValue(extension, name, value string, context Context) error {
131+
return cs.setValue(extension, name, value, context, false)
132+
}
133+
134+
func (cs *ConnectionState) SetLocalValue(extension, name, value string) error {
135+
return cs.setValue(extension, name, value, ContextUser, true)
136+
}
137+
138+
func (cs *ConnectionState) setValue(extension, name, value string, context Context, local bool) error {
139+
prop, err := cs.findProperty(extension, name)
140+
if err != nil {
141+
return err
142+
}
143+
convertedValue, err := prop.Convert(value)
144+
if err != nil {
145+
return err
146+
}
147+
if local {
148+
return prop.SetLocalUntypedValue(cs, convertedValue)
149+
}
150+
return prop.SetUntypedValue(cs, convertedValue, context)
151+
}
152+
153+
func (cs *ConnectionState) findProperty(extension, name string) (ConnectionProperty, error) {
154+
key := toKey(extension, name)
155+
var prop ConnectionProperty
156+
existingValue, ok := cs.properties[key]
157+
if !ok {
158+
prop = &TypedConnectionProperty[string]{
159+
key: key,
160+
extension: extension,
161+
name: name,
162+
context: ContextUser,
163+
converter: ConvertString,
164+
}
165+
if extension == "" {
166+
return nil, unknownPropertyErr(prop)
167+
}
168+
} else {
169+
prop = existingValue.ConnectionProperty()
170+
}
171+
return prop, nil
172+
}
173+
113174
// Begin starts a new transaction for this ConnectionState.
114175
func (cs *ConnectionState) Begin() error {
115176
if cs.inTransaction {

0 commit comments

Comments
 (0)