16
16
17
17
package com .google .cloud .spanner .connection ;
18
18
19
+ import com .google .cloud .Tuple ;
19
20
import com .google .cloud .spanner .ErrorCode ;
20
21
import com .google .cloud .spanner .SpannerExceptionFactory ;
21
22
import com .google .cloud .spanner .connection .AbstractStatementParser .ParsedStatement ;
22
23
import com .google .cloud .spanner .connection .ClientSideStatementImpl .CompileException ;
23
24
import com .google .common .base .Preconditions ;
25
+ import com .google .common .cache .Cache ;
26
+ import com .google .common .cache .CacheBuilder ;
27
+ import com .google .common .util .concurrent .UncheckedExecutionException ;
24
28
import java .lang .reflect .Constructor ;
25
29
import java .lang .reflect .Method ;
30
+ import java .util .concurrent .ExecutionException ;
26
31
import java .util .regex .Matcher ;
27
32
import java .util .regex .Pattern ;
28
33
31
36
* AUTOCOMMIT=TRUE.
32
37
*/
33
38
class ClientSideStatementSetExecutor <T > implements ClientSideStatementExecutor {
39
+ private final Cache <String , Tuple <T , Boolean >> cache ;
34
40
private final ClientSideStatementImpl statement ;
35
41
private final Method method ;
42
+ private final boolean supportsLocal ;
36
43
private final ClientSideStatementValueConverter <T > converter ;
37
44
private final Pattern allowedValuesPattern ;
38
45
@@ -46,12 +53,18 @@ class ClientSideStatementSetExecutor<T> implements ClientSideStatementExecutor {
46
53
@ SuppressWarnings ("unchecked" )
47
54
ClientSideStatementSetExecutor (ClientSideStatementImpl statement ) throws CompileException {
48
55
Preconditions .checkNotNull (statement .getSetStatement ());
56
+ this .cache =
57
+ CacheBuilder .newBuilder ()
58
+ .maximumSize (25 )
59
+ // Set the concurrency level to 1, as we don't expect many concurrent updates.
60
+ .concurrencyLevel (1 )
61
+ .build ();
49
62
try {
50
63
this .statement = statement ;
51
64
this .allowedValuesPattern =
52
65
Pattern .compile (
53
66
String .format (
54
- "(?is)\\ A\\ s*set\\ s+%s\\ s*%s\\ s*%s\\ s*\\ z" ,
67
+ "(?is)\\ A\\ s*set\\ s+((?:local|session) \\ s+)? %s\\ s*%s\\ s*%s\\ s*\\ z" ,
55
68
statement .getSetStatement ().getPropertyName (),
56
69
statement .getSetStatement ().getSeparator (),
57
70
statement .getSetStatement ().getAllowedValues ()));
@@ -64,9 +77,21 @@ class ClientSideStatementSetExecutor<T> implements ClientSideStatementExecutor {
64
77
Constructor <ClientSideStatementValueConverter <T >> constructor =
65
78
converterClass .getConstructor (String .class );
66
79
this .converter = constructor .newInstance (statement .getSetStatement ().getAllowedValues ());
67
- this .method =
68
- ConnectionStatementExecutor .class .getDeclaredMethod (
69
- statement .getMethodName (), converter .getParameterClass ());
80
+ Method method ;
81
+ boolean supportsLocal ;
82
+ try {
83
+ method =
84
+ ConnectionStatementExecutor .class .getDeclaredMethod (
85
+ statement .getMethodName (), converter .getParameterClass ());
86
+ supportsLocal = false ;
87
+ } catch (NoSuchMethodException ignore ) {
88
+ method =
89
+ ConnectionStatementExecutor .class .getDeclaredMethod (
90
+ statement .getMethodName (), converter .getParameterClass (), Boolean .class );
91
+ supportsLocal = true ;
92
+ }
93
+ this .method = method ;
94
+ this .supportsLocal = supportsLocal ;
70
95
} catch (Exception e ) {
71
96
throw new CompileException (e , statement );
72
97
}
@@ -75,17 +100,29 @@ class ClientSideStatementSetExecutor<T> implements ClientSideStatementExecutor {
75
100
@ Override
76
101
public StatementResult execute (ConnectionStatementExecutor connection , ParsedStatement statement )
77
102
throws Exception {
78
- return (StatementResult )
79
- method .invoke (connection , getParameterValue (statement .getSqlWithoutComments ()));
103
+ Tuple <T , Boolean > value ;
104
+ try {
105
+ value =
106
+ this .cache .get (
107
+ statement .getSqlWithoutComments (),
108
+ () -> getParameterValue (statement .getSqlWithoutComments ()));
109
+ } catch (ExecutionException | UncheckedExecutionException executionException ) {
110
+ throw SpannerExceptionFactory .asSpannerException (executionException .getCause ());
111
+ }
112
+ if (this .supportsLocal ) {
113
+ return (StatementResult ) method .invoke (connection , value .x (), value .y ());
114
+ }
115
+ return (StatementResult ) method .invoke (connection , value .x ());
80
116
}
81
117
82
- T getParameterValue (String sql ) {
118
+ Tuple < T , Boolean > getParameterValue (String sql ) {
83
119
Matcher matcher = allowedValuesPattern .matcher (sql );
84
- if (matcher .find () && matcher .groupCount () >= 1 ) {
85
- String value = matcher .group (1 );
120
+ if (matcher .find () && matcher .groupCount () >= 2 ) {
121
+ boolean local = matcher .group (1 ) != null && "local" .equalsIgnoreCase (matcher .group (1 ).trim ());
122
+ String value = matcher .group (2 );
86
123
T res = converter .convert (value );
87
124
if (res != null ) {
88
- return res ;
125
+ return Tuple . of ( res , local ) ;
89
126
}
90
127
throw SpannerExceptionFactory .newSpannerException (
91
128
ErrorCode .INVALID_ARGUMENT ,
@@ -94,8 +131,9 @@ T getParameterValue(String sql) {
94
131
this .statement .getSetStatement ().getPropertyName (), value ));
95
132
} else {
96
133
Matcher invalidMatcher = this .statement .getPattern ().matcher (sql );
97
- if (invalidMatcher .find () && invalidMatcher .groupCount () == 1 ) {
98
- String invalidValue = invalidMatcher .group (1 );
134
+ int valueGroup = this .supportsLocal ? 2 : 1 ;
135
+ if (invalidMatcher .find () && invalidMatcher .groupCount () == valueGroup ) {
136
+ String invalidValue = invalidMatcher .group (valueGroup );
99
137
throw SpannerExceptionFactory .newSpannerException (
100
138
ErrorCode .INVALID_ARGUMENT ,
101
139
String .format (
0 commit comments