16
16
17
17
package com .google .cloud .spanner .connection ;
18
18
19
+ import static com .google .cloud .spanner .connection .SimpleParser .isValidIdentifierChar ;
20
+ import static com .google .cloud .spanner .connection .StatementHintParser .convertHintsToOptions ;
21
+
19
22
import com .google .api .core .InternalApi ;
20
23
import com .google .cloud .spanner .Dialect ;
21
24
import com .google .cloud .spanner .ErrorCode ;
25
+ import com .google .cloud .spanner .Options .ReadQueryUpdateTransactionOption ;
22
26
import com .google .cloud .spanner .SpannerException ;
23
27
import com .google .cloud .spanner .SpannerExceptionFactory ;
24
28
import com .google .cloud .spanner .Statement ;
@@ -169,6 +173,7 @@ public static class ParsedStatement {
169
173
private final Statement statement ;
170
174
private final String sqlWithoutComments ;
171
175
private final boolean returningClause ;
176
+ private final ReadQueryUpdateTransactionOption [] optionsFromHints ;
172
177
173
178
private static ParsedStatement clientSideStatement (
174
179
ClientSideStatementImpl clientSideStatement ,
@@ -182,15 +187,27 @@ private static ParsedStatement ddl(Statement statement, String sqlWithoutComment
182
187
}
183
188
184
189
private static ParsedStatement query (
185
- Statement statement , String sqlWithoutComments , QueryOptions defaultQueryOptions ) {
190
+ Statement statement ,
191
+ String sqlWithoutComments ,
192
+ QueryOptions defaultQueryOptions ,
193
+ ReadQueryUpdateTransactionOption [] optionsFromHints ) {
186
194
return new ParsedStatement (
187
- StatementType .QUERY , null , statement , sqlWithoutComments , defaultQueryOptions , false );
195
+ StatementType .QUERY ,
196
+ null ,
197
+ statement ,
198
+ sqlWithoutComments ,
199
+ defaultQueryOptions ,
200
+ false ,
201
+ optionsFromHints );
188
202
}
189
203
190
204
private static ParsedStatement update (
191
- Statement statement , String sqlWithoutComments , boolean returningClause ) {
205
+ Statement statement ,
206
+ String sqlWithoutComments ,
207
+ boolean returningClause ,
208
+ ReadQueryUpdateTransactionOption [] optionsFromHints ) {
192
209
return new ParsedStatement (
193
- StatementType .UPDATE , statement , sqlWithoutComments , returningClause );
210
+ StatementType .UPDATE , statement , sqlWithoutComments , returningClause , optionsFromHints );
194
211
}
195
212
196
213
private static ParsedStatement unknown (Statement statement , String sqlWithoutComments ) {
@@ -208,18 +225,20 @@ private ParsedStatement(
208
225
this .statement = statement ;
209
226
this .sqlWithoutComments = Preconditions .checkNotNull (sqlWithoutComments );
210
227
this .returningClause = false ;
228
+ this .optionsFromHints = EMPTY_OPTIONS ;
211
229
}
212
230
213
231
private ParsedStatement (
214
232
StatementType type ,
215
233
Statement statement ,
216
234
String sqlWithoutComments ,
217
- boolean returningClause ) {
218
- this (type , null , statement , sqlWithoutComments , null , returningClause );
235
+ boolean returningClause ,
236
+ ReadQueryUpdateTransactionOption [] optionsFromHints ) {
237
+ this (type , null , statement , sqlWithoutComments , null , returningClause , optionsFromHints );
219
238
}
220
239
221
240
private ParsedStatement (StatementType type , Statement statement , String sqlWithoutComments ) {
222
- this (type , null , statement , sqlWithoutComments , null , false );
241
+ this (type , null , statement , sqlWithoutComments , null , false , EMPTY_OPTIONS );
223
242
}
224
243
225
244
private ParsedStatement (
@@ -228,33 +247,37 @@ private ParsedStatement(
228
247
Statement statement ,
229
248
String sqlWithoutComments ,
230
249
QueryOptions defaultQueryOptions ,
231
- boolean returningClause ) {
250
+ boolean returningClause ,
251
+ ReadQueryUpdateTransactionOption [] optionsFromHints ) {
232
252
Preconditions .checkNotNull (type );
233
253
this .type = type ;
234
254
this .clientSideStatement = clientSideStatement ;
235
255
this .statement = statement == null ? null : mergeQueryOptions (statement , defaultQueryOptions );
236
256
this .sqlWithoutComments = Preconditions .checkNotNull (sqlWithoutComments );
237
257
this .returningClause = returningClause ;
258
+ this .optionsFromHints = optionsFromHints ;
238
259
}
239
260
240
261
private ParsedStatement copy (Statement statement , QueryOptions defaultQueryOptions ) {
241
262
return new ParsedStatement (
242
263
this .type ,
243
264
this .clientSideStatement ,
244
- statement ,
265
+ statement . withReplacedSql ( this . statement . getSql ()) ,
245
266
this .sqlWithoutComments ,
246
267
defaultQueryOptions ,
247
- this .returningClause );
268
+ this .returningClause ,
269
+ this .optionsFromHints );
248
270
}
249
271
250
272
private ParsedStatement forCache () {
251
273
return new ParsedStatement (
252
274
this .type ,
253
275
this .clientSideStatement ,
254
- null ,
276
+ Statement . of ( this . statement . getSql ()) ,
255
277
this .sqlWithoutComments ,
256
278
null ,
257
- this .returningClause );
279
+ this .returningClause ,
280
+ this .optionsFromHints );
258
281
}
259
282
260
283
@ Override
@@ -287,6 +310,11 @@ public boolean hasReturningClause() {
287
310
return this .returningClause ;
288
311
}
289
312
313
+ @ InternalApi
314
+ public ReadQueryUpdateTransactionOption [] getOptionsFromHints () {
315
+ return this .optionsFromHints ;
316
+ }
317
+
290
318
/**
291
319
* @return true if the statement is a query that will return a {@link
292
320
* com.google.cloud.spanner.ResultSet}.
@@ -480,14 +508,23 @@ ParsedStatement parse(Statement statement, QueryOptions defaultQueryOptions) {
480
508
}
481
509
482
510
private ParsedStatement internalParse (Statement statement , QueryOptions defaultQueryOptions ) {
511
+ StatementHintParser statementHintParser =
512
+ new StatementHintParser (getDialect (), statement .getSql ());
513
+ ReadQueryUpdateTransactionOption [] optionsFromHints = EMPTY_OPTIONS ;
514
+ if (statementHintParser .hasStatementHints ()
515
+ && !statementHintParser .getClientSideStatementHints ().isEmpty ()) {
516
+ statement =
517
+ statement .toBuilder ().replace (statementHintParser .getSqlWithoutClientSideHints ()).build ();
518
+ optionsFromHints = convertHintsToOptions (statementHintParser .getClientSideStatementHints ());
519
+ }
483
520
String sql = removeCommentsAndTrim (statement .getSql ());
484
521
ClientSideStatementImpl client = parseClientSideStatement (sql );
485
522
if (client != null ) {
486
523
return ParsedStatement .clientSideStatement (client , statement , sql );
487
524
} else if (isQuery (sql )) {
488
- return ParsedStatement .query (statement , sql , defaultQueryOptions );
525
+ return ParsedStatement .query (statement , sql , defaultQueryOptions , optionsFromHints );
489
526
} else if (isUpdateStatement (sql )) {
490
- return ParsedStatement .update (statement , sql , checkReturningClause (sql ));
527
+ return ParsedStatement .update (statement , sql , checkReturningClause (sql ), optionsFromHints );
491
528
} else if (isDdlStatement (sql )) {
492
529
return ParsedStatement .ddl (statement , sql );
493
530
}
@@ -621,6 +658,10 @@ public String removeCommentsAndTrim(String sql) {
621
658
/** Removes any statement hints at the beginning of the statement. */
622
659
abstract String removeStatementHint (String sql );
623
660
661
+ @ VisibleForTesting
662
+ static final ReadQueryUpdateTransactionOption [] EMPTY_OPTIONS =
663
+ new ReadQueryUpdateTransactionOption [0 ];
664
+
624
665
/** Parameter information with positional parameters translated to named parameters. */
625
666
@ InternalApi
626
667
public static class ParametersInfo {
@@ -697,9 +738,10 @@ public boolean checkReturningClause(String sql) {
697
738
return checkReturningClauseInternal (sql );
698
739
}
699
740
741
+ abstract Dialect getDialect ();
742
+
700
743
/**
701
- * <<<<<<< HEAD Returns true if this dialect supports nested comments. ======= <<<<<<< HEAD
702
- * Returns true if this dialect supports nested comments. >>>>>>> main
744
+ * Returns true if this dialect supports nested comments.
703
745
*
704
746
* <ul>
705
747
* <li>This method should return false for dialects that consider this to be a valid comment:
@@ -757,18 +799,6 @@ public boolean checkReturningClause(String sql) {
757
799
/** Returns the query parameter prefix that should be used for this dialect. */
758
800
abstract String getQueryParameterPrefix ();
759
801
760
- /**
761
- * Returns true for characters that can be used as the first character in unquoted identifiers.
762
- */
763
- boolean isValidIdentifierFirstChar (char c ) {
764
- return Character .isLetter (c ) || c == UNDERSCORE ;
765
- }
766
-
767
- /** Returns true for characters that can be used in unquoted identifiers. */
768
- boolean isValidIdentifierChar (char c ) {
769
- return isValidIdentifierFirstChar (c ) || Character .isDigit (c ) || c == DOLLAR ;
770
- }
771
-
772
802
/** Reads a dollar-quoted string literal from position index in the given sql string. */
773
803
String parseDollarQuotedString (String sql , int index ) {
774
804
// Look ahead to the next dollar sign (if any). Everything in between is the quote tag.
@@ -812,9 +842,9 @@ int skip(String sql, int currentIndex, @Nullable StringBuilder result) {
812
842
} else if (currentChar == HYPHEN
813
843
&& sql .length () > (currentIndex + 1 )
814
844
&& sql .charAt (currentIndex + 1 ) == HYPHEN ) {
815
- return skipSingleLineComment (sql , currentIndex , result );
845
+ return skipSingleLineComment (sql , /* prefixLength = */ 2 , currentIndex , result );
816
846
} else if (currentChar == DASH && supportsHashSingleLineComments ()) {
817
- return skipSingleLineComment (sql , currentIndex , result );
847
+ return skipSingleLineComment (sql , /* prefixLength = */ 1 , currentIndex , result );
818
848
} else if (currentChar == SLASH
819
849
&& sql .length () > (currentIndex + 1 )
820
850
&& sql .charAt (currentIndex + 1 ) == ASTERISK ) {
@@ -826,44 +856,31 @@ int skip(String sql, int currentIndex, @Nullable StringBuilder result) {
826
856
}
827
857
828
858
/** Skips a single-line comment from startIndex and adds it to result if result is not null. */
829
- static int skipSingleLineComment (String sql , int startIndex , @ Nullable StringBuilder result ) {
830
- int endIndex = sql .indexOf ('\n' , startIndex + 2 );
831
- if (endIndex == -1 ) {
832
- endIndex = sql .length ();
833
- } else {
834
- // Include the newline character.
835
- endIndex ++;
859
+ int skipSingleLineComment (
860
+ String sql , int prefixLength , int startIndex , @ Nullable StringBuilder result ) {
861
+ return skipSingleLineComment (getDialect (), sql , prefixLength , startIndex , result );
862
+ }
863
+
864
+ static int skipSingleLineComment (
865
+ Dialect dialect ,
866
+ String sql ,
867
+ int prefixLength ,
868
+ int startIndex ,
869
+ @ Nullable StringBuilder result ) {
870
+ SimpleParser simpleParser = new SimpleParser (dialect , sql , startIndex , false );
871
+ if (simpleParser .skipSingleLineComment (prefixLength )) {
872
+ appendIfNotNull (result , sql .substring (startIndex , simpleParser .getPos ()));
836
873
}
837
- appendIfNotNull (result , sql .substring (startIndex , endIndex ));
838
- return endIndex ;
874
+ return simpleParser .getPos ();
839
875
}
840
876
841
877
/** Skips a multi-line comment from startIndex and adds it to result if result is not null. */
842
878
int skipMultiLineComment (String sql , int startIndex , @ Nullable StringBuilder result ) {
843
- // Current position is start + '/*'.length().
844
- int pos = startIndex + 2 ;
845
- // PostgreSQL allows comments to be nested. That is, the following is allowed:
846
- // '/* test /* inner comment */ still a comment */'
847
- int level = 1 ;
848
- while (pos < sql .length ()) {
849
- if (supportsNestedComments ()
850
- && sql .charAt (pos ) == SLASH
851
- && sql .length () > (pos + 1 )
852
- && sql .charAt (pos + 1 ) == ASTERISK ) {
853
- level ++;
854
- }
855
- if (sql .charAt (pos ) == ASTERISK && sql .length () > (pos + 1 ) && sql .charAt (pos + 1 ) == SLASH ) {
856
- level --;
857
- if (level == 0 ) {
858
- pos += 2 ;
859
- appendIfNotNull (result , sql .substring (startIndex , pos ));
860
- return pos ;
861
- }
862
- }
863
- pos ++;
879
+ SimpleParser simpleParser = new SimpleParser (getDialect (), sql , startIndex , false );
880
+ if (simpleParser .skipMultiLineComment ()) {
881
+ appendIfNotNull (result , sql .substring (startIndex , simpleParser .getPos ()));
864
882
}
865
- appendIfNotNull (result , sql .substring (startIndex ));
866
- return sql .length ();
883
+ return simpleParser .getPos ();
867
884
}
868
885
869
886
/** Skips a quoted string from startIndex. */
0 commit comments