21
21
import com .google .cloud .spanner .SpannerExceptionFactory ;
22
22
import com .google .cloud .spanner .TimestampBound ;
23
23
import com .google .cloud .spanner .TimestampBound .Mode ;
24
+ import com .google .cloud .spanner .connection .PgTransactionMode .AccessMode ;
25
+ import com .google .cloud .spanner .connection .PgTransactionMode .IsolationLevel ;
24
26
import com .google .common .base .Function ;
25
27
import com .google .common .base .Preconditions ;
26
28
import com .google .protobuf .Duration ;
27
29
import com .google .protobuf .util .Durations ;
28
30
import com .google .spanner .v1 .RequestOptions .Priority ;
29
31
import java .util .EnumSet ;
30
32
import java .util .HashMap ;
33
+ import java .util .Locale ;
31
34
import java .util .Map ;
32
35
import java .util .concurrent .TimeUnit ;
33
36
import java .util .regex .Matcher ;
@@ -85,6 +88,53 @@ public Boolean convert(String value) {
85
88
}
86
89
}
87
90
91
+ /** Converter from string to {@link Boolean} */
92
+ static class PgBooleanConverter implements ClientSideStatementValueConverter <Boolean > {
93
+
94
+ public PgBooleanConverter (String allowedValues ) {}
95
+
96
+ @ Override
97
+ public Class <Boolean > getParameterClass () {
98
+ return Boolean .class ;
99
+ }
100
+
101
+ @ Override
102
+ public Boolean convert (String value ) {
103
+ if (value == null ) {
104
+ return null ;
105
+ }
106
+ if (value .length () > 1
107
+ && ((value .startsWith ("'" ) && value .endsWith ("'" ))
108
+ || (value .startsWith ("\" " ) && value .endsWith ("\" " )))) {
109
+ value = value .substring (1 , value .length () - 1 );
110
+ }
111
+ if ("true" .equalsIgnoreCase (value )
112
+ || "tru" .equalsIgnoreCase (value )
113
+ || "tr" .equalsIgnoreCase (value )
114
+ || "t" .equalsIgnoreCase (value )
115
+ || "on" .equalsIgnoreCase (value )
116
+ || "1" .equalsIgnoreCase (value )
117
+ || "yes" .equalsIgnoreCase (value )
118
+ || "ye" .equalsIgnoreCase (value )
119
+ || "y" .equalsIgnoreCase (value )) {
120
+ return Boolean .TRUE ;
121
+ }
122
+ if ("false" .equalsIgnoreCase (value )
123
+ || "fals" .equalsIgnoreCase (value )
124
+ || "fal" .equalsIgnoreCase (value )
125
+ || "fa" .equalsIgnoreCase (value )
126
+ || "f" .equalsIgnoreCase (value )
127
+ || "off" .equalsIgnoreCase (value )
128
+ || "of" .equalsIgnoreCase (value )
129
+ || "0" .equalsIgnoreCase (value )
130
+ || "no" .equalsIgnoreCase (value )
131
+ || "n" .equalsIgnoreCase (value )) {
132
+ return Boolean .FALSE ;
133
+ }
134
+ return null ;
135
+ }
136
+ }
137
+
88
138
/** Converter from string to {@link Duration}. */
89
139
static class DurationConverter implements ClientSideStatementValueConverter <Duration > {
90
140
private final Pattern allowedValues ;
@@ -286,16 +336,39 @@ public TransactionMode convert(String value) {
286
336
}
287
337
}
288
338
339
+ static class PgTransactionIsolationConverter
340
+ implements ClientSideStatementValueConverter <IsolationLevel > {
341
+ private final CaseInsensitiveEnumMap <IsolationLevel > values =
342
+ new CaseInsensitiveEnumMap <>(IsolationLevel .class , IsolationLevel ::getShortStatementString );
343
+
344
+ public PgTransactionIsolationConverter (String allowedValues ) {}
345
+
346
+ @ Override
347
+ public Class <IsolationLevel > getParameterClass () {
348
+ return IsolationLevel .class ;
349
+ }
350
+
351
+ @ Override
352
+ public IsolationLevel convert (String value ) {
353
+ // Isolation level may contain multiple spaces.
354
+ String valueWithSingleSpaces = value .replaceAll ("\\ s+" , " " );
355
+ if (valueWithSingleSpaces .length () > 1
356
+ && ((valueWithSingleSpaces .startsWith ("'" ) && valueWithSingleSpaces .endsWith ("'" ))
357
+ || (valueWithSingleSpaces .startsWith ("\" " )
358
+ && valueWithSingleSpaces .endsWith ("\" " )))) {
359
+ valueWithSingleSpaces =
360
+ valueWithSingleSpaces .substring (1 , valueWithSingleSpaces .length () - 1 );
361
+ }
362
+ return values .get (valueWithSingleSpaces );
363
+ }
364
+ }
365
+
289
366
/**
290
367
* Converter for converting string values to {@link PgTransactionMode} values. Includes no-op
291
368
* handling of setting the isolation level of the transaction to default or serializable.
292
369
*/
293
370
static class PgTransactionModeConverter
294
371
implements ClientSideStatementValueConverter <PgTransactionMode > {
295
- private final CaseInsensitiveEnumMap <PgTransactionMode > values =
296
- new CaseInsensitiveEnumMap <>(
297
- PgTransactionMode .class , PgTransactionMode ::getStatementString );
298
-
299
372
PgTransactionModeConverter () {}
300
373
301
374
public PgTransactionModeConverter (String allowedValues ) {}
@@ -307,9 +380,49 @@ public Class<PgTransactionMode> getParameterClass() {
307
380
308
381
@ Override
309
382
public PgTransactionMode convert (String value ) {
383
+ PgTransactionMode mode = new PgTransactionMode ();
310
384
// Transaction mode may contain multiple spaces.
311
- String valueWithSingleSpaces = value .replaceAll ("\\ s+" , " " );
312
- return values .get (valueWithSingleSpaces );
385
+ String valueWithSingleSpaces =
386
+ value .replaceAll ("\\ s+" , " " ).toLowerCase (Locale .ENGLISH ).trim ();
387
+ int currentIndex = 0 ;
388
+ while (currentIndex < valueWithSingleSpaces .length ()) {
389
+ // This will use the last access mode and isolation level that is encountered in the string.
390
+ // This is consistent with the behavior of PostgreSQL, which also allows multiple modes to
391
+ // be specified in one string, and will use the last one that is encountered.
392
+ if (valueWithSingleSpaces .substring (currentIndex ).startsWith ("read only" )) {
393
+ currentIndex += "read only" .length ();
394
+ mode .setAccessMode (AccessMode .READ_ONLY_TRANSACTION );
395
+ } else if (valueWithSingleSpaces .substring (currentIndex ).startsWith ("read write" )) {
396
+ currentIndex += "read write" .length ();
397
+ mode .setAccessMode (AccessMode .READ_WRITE_TRANSACTION );
398
+ } else if (valueWithSingleSpaces
399
+ .substring (currentIndex )
400
+ .startsWith ("isolation level serializable" )) {
401
+ currentIndex += "isolation level serializable" .length ();
402
+ mode .setIsolationLevel (IsolationLevel .ISOLATION_LEVEL_SERIALIZABLE );
403
+ } else if (valueWithSingleSpaces
404
+ .substring (currentIndex )
405
+ .startsWith ("isolation level default" )) {
406
+ currentIndex += "isolation level default" .length ();
407
+ mode .setIsolationLevel (IsolationLevel .ISOLATION_LEVEL_DEFAULT );
408
+ } else {
409
+ return null ;
410
+ }
411
+ // Skip space and/or comma that may separate multiple transaction modes.
412
+ if (currentIndex < valueWithSingleSpaces .length ()
413
+ && valueWithSingleSpaces .charAt (currentIndex ) == ' ' ) {
414
+ currentIndex ++;
415
+ }
416
+ if (currentIndex < valueWithSingleSpaces .length ()
417
+ && valueWithSingleSpaces .charAt (currentIndex ) == ',' ) {
418
+ currentIndex ++;
419
+ }
420
+ if (currentIndex < valueWithSingleSpaces .length ()
421
+ && valueWithSingleSpaces .charAt (currentIndex ) == ' ' ) {
422
+ currentIndex ++;
423
+ }
424
+ }
425
+ return mode ;
313
426
}
314
427
}
315
428
0 commit comments