1616
1717package com .google .cloud .spanner .connection ;
1818
19+ import static com .google .cloud .spanner .connection .SimpleParser .isValidIdentifierChar ;
20+ import static com .google .cloud .spanner .connection .StatementHintParser .convertHintsToOptions ;
21+
1922import com .google .api .core .InternalApi ;
2023import com .google .cloud .spanner .Dialect ;
2124import com .google .cloud .spanner .ErrorCode ;
25+ import com .google .cloud .spanner .Options .ReadQueryUpdateTransactionOption ;
2226import com .google .cloud .spanner .SpannerException ;
2327import com .google .cloud .spanner .SpannerExceptionFactory ;
2428import com .google .cloud .spanner .Statement ;
@@ -169,6 +173,7 @@ public static class ParsedStatement {
169173 private final Statement statement ;
170174 private final String sqlWithoutComments ;
171175 private final boolean returningClause ;
176+ private final ReadQueryUpdateTransactionOption [] optionsFromHints ;
172177
173178 private static ParsedStatement clientSideStatement (
174179 ClientSideStatementImpl clientSideStatement ,
@@ -182,15 +187,27 @@ private static ParsedStatement ddl(Statement statement, String sqlWithoutComment
182187 }
183188
184189 private static ParsedStatement query (
185- Statement statement , String sqlWithoutComments , QueryOptions defaultQueryOptions ) {
190+ Statement statement ,
191+ String sqlWithoutComments ,
192+ QueryOptions defaultQueryOptions ,
193+ ReadQueryUpdateTransactionOption [] optionsFromHints ) {
186194 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 );
188202 }
189203
190204 private static ParsedStatement update (
191- Statement statement , String sqlWithoutComments , boolean returningClause ) {
205+ Statement statement ,
206+ String sqlWithoutComments ,
207+ boolean returningClause ,
208+ ReadQueryUpdateTransactionOption [] optionsFromHints ) {
192209 return new ParsedStatement (
193- StatementType .UPDATE , statement , sqlWithoutComments , returningClause );
210+ StatementType .UPDATE , statement , sqlWithoutComments , returningClause , optionsFromHints );
194211 }
195212
196213 private static ParsedStatement unknown (Statement statement , String sqlWithoutComments ) {
@@ -208,18 +225,20 @@ private ParsedStatement(
208225 this .statement = statement ;
209226 this .sqlWithoutComments = Preconditions .checkNotNull (sqlWithoutComments );
210227 this .returningClause = false ;
228+ this .optionsFromHints = EMPTY_OPTIONS ;
211229 }
212230
213231 private ParsedStatement (
214232 StatementType type ,
215233 Statement statement ,
216234 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 );
219238 }
220239
221240 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 );
223242 }
224243
225244 private ParsedStatement (
@@ -228,33 +247,37 @@ private ParsedStatement(
228247 Statement statement ,
229248 String sqlWithoutComments ,
230249 QueryOptions defaultQueryOptions ,
231- boolean returningClause ) {
250+ boolean returningClause ,
251+ ReadQueryUpdateTransactionOption [] optionsFromHints ) {
232252 Preconditions .checkNotNull (type );
233253 this .type = type ;
234254 this .clientSideStatement = clientSideStatement ;
235255 this .statement = statement == null ? null : mergeQueryOptions (statement , defaultQueryOptions );
236256 this .sqlWithoutComments = Preconditions .checkNotNull (sqlWithoutComments );
237257 this .returningClause = returningClause ;
258+ this .optionsFromHints = optionsFromHints ;
238259 }
239260
240261 private ParsedStatement copy (Statement statement , QueryOptions defaultQueryOptions ) {
241262 return new ParsedStatement (
242263 this .type ,
243264 this .clientSideStatement ,
244- statement ,
265+ statement . withReplacedSql ( this . statement . getSql ()) ,
245266 this .sqlWithoutComments ,
246267 defaultQueryOptions ,
247- this .returningClause );
268+ this .returningClause ,
269+ this .optionsFromHints );
248270 }
249271
250272 private ParsedStatement forCache () {
251273 return new ParsedStatement (
252274 this .type ,
253275 this .clientSideStatement ,
254- null ,
276+ Statement . of ( this . statement . getSql ()) ,
255277 this .sqlWithoutComments ,
256278 null ,
257- this .returningClause );
279+ this .returningClause ,
280+ this .optionsFromHints );
258281 }
259282
260283 @ Override
@@ -287,6 +310,11 @@ public boolean hasReturningClause() {
287310 return this .returningClause ;
288311 }
289312
313+ @ InternalApi
314+ public ReadQueryUpdateTransactionOption [] getOptionsFromHints () {
315+ return this .optionsFromHints ;
316+ }
317+
290318 /**
291319 * @return true if the statement is a query that will return a {@link
292320 * com.google.cloud.spanner.ResultSet}.
@@ -480,14 +508,23 @@ ParsedStatement parse(Statement statement, QueryOptions defaultQueryOptions) {
480508 }
481509
482510 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+ }
483520 String sql = removeCommentsAndTrim (statement .getSql ());
484521 ClientSideStatementImpl client = parseClientSideStatement (sql );
485522 if (client != null ) {
486523 return ParsedStatement .clientSideStatement (client , statement , sql );
487524 } else if (isQuery (sql )) {
488- return ParsedStatement .query (statement , sql , defaultQueryOptions );
525+ return ParsedStatement .query (statement , sql , defaultQueryOptions , optionsFromHints );
489526 } else if (isUpdateStatement (sql )) {
490- return ParsedStatement .update (statement , sql , checkReturningClause (sql ));
527+ return ParsedStatement .update (statement , sql , checkReturningClause (sql ), optionsFromHints );
491528 } else if (isDdlStatement (sql )) {
492529 return ParsedStatement .ddl (statement , sql );
493530 }
@@ -621,6 +658,10 @@ public String removeCommentsAndTrim(String sql) {
621658 /** Removes any statement hints at the beginning of the statement. */
622659 abstract String removeStatementHint (String sql );
623660
661+ @ VisibleForTesting
662+ static final ReadQueryUpdateTransactionOption [] EMPTY_OPTIONS =
663+ new ReadQueryUpdateTransactionOption [0 ];
664+
624665 /** Parameter information with positional parameters translated to named parameters. */
625666 @ InternalApi
626667 public static class ParametersInfo {
@@ -697,9 +738,10 @@ public boolean checkReturningClause(String sql) {
697738 return checkReturningClauseInternal (sql );
698739 }
699740
741+ abstract Dialect getDialect ();
742+
700743 /**
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.
703745 *
704746 * <ul>
705747 * <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) {
757799 /** Returns the query parameter prefix that should be used for this dialect. */
758800 abstract String getQueryParameterPrefix ();
759801
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-
772802 /** Reads a dollar-quoted string literal from position index in the given sql string. */
773803 String parseDollarQuotedString (String sql , int index ) {
774804 // 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) {
812842 } else if (currentChar == HYPHEN
813843 && sql .length () > (currentIndex + 1 )
814844 && sql .charAt (currentIndex + 1 ) == HYPHEN ) {
815- return skipSingleLineComment (sql , currentIndex , result );
845+ return skipSingleLineComment (sql , /* prefixLength = */ 2 , currentIndex , result );
816846 } else if (currentChar == DASH && supportsHashSingleLineComments ()) {
817- return skipSingleLineComment (sql , currentIndex , result );
847+ return skipSingleLineComment (sql , /* prefixLength = */ 1 , currentIndex , result );
818848 } else if (currentChar == SLASH
819849 && sql .length () > (currentIndex + 1 )
820850 && sql .charAt (currentIndex + 1 ) == ASTERISK ) {
@@ -826,44 +856,31 @@ int skip(String sql, int currentIndex, @Nullable StringBuilder result) {
826856 }
827857
828858 /** 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 ()));
836873 }
837- appendIfNotNull (result , sql .substring (startIndex , endIndex ));
838- return endIndex ;
874+ return simpleParser .getPos ();
839875 }
840876
841877 /** Skips a multi-line comment from startIndex and adds it to result if result is not null. */
842878 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 ()));
864882 }
865- appendIfNotNull (result , sql .substring (startIndex ));
866- return sql .length ();
883+ return simpleParser .getPos ();
867884 }
868885
869886 /** Skips a quoted string from startIndex. */
0 commit comments