2727import com .google .cloud .spanner .SpannerExceptionFactory ;
2828import com .google .cloud .spanner .Statement ;
2929import com .google .cloud .spanner .connection .AbstractBaseUnitOfWork .InterceptorsUsage ;
30+ import com .google .cloud .spanner .connection .SimpleParser .Result ;
3031import com .google .cloud .spanner .connection .StatementResult .ClientSideStatementType ;
3132import com .google .cloud .spanner .connection .UnitOfWork .CallType ;
3233import com .google .common .annotations .VisibleForTesting ;
3334import com .google .common .base .Preconditions ;
35+ import com .google .common .base .Suppliers ;
3436import com .google .common .cache .Cache ;
3537import com .google .common .cache .CacheBuilder ;
3638import com .google .common .cache .CacheStats ;
3739import com .google .common .cache .Weigher ;
3840import com .google .common .collect .ImmutableMap ;
3941import com .google .common .collect .ImmutableSet ;
4042import com .google .spanner .v1 .ExecuteSqlRequest .QueryOptions ;
43+ import java .nio .CharBuffer ;
4144import java .util .Collection ;
4245import java .util .Collections ;
4346import java .util .HashMap ;
4447import java .util .Map ;
4548import java .util .Objects ;
4649import java .util .Set ;
4750import java .util .concurrent .Callable ;
51+ import java .util .function .Supplier ;
4852import java .util .logging .Level ;
4953import java .util .logging .Logger ;
5054import javax .annotation .Nullable ;
@@ -179,24 +183,24 @@ public static class ParsedStatement {
179183 private final StatementType type ;
180184 private final ClientSideStatementImpl clientSideStatement ;
181185 private final Statement statement ;
182- private final String sqlWithoutComments ;
183- private final boolean returningClause ;
186+ private final Supplier < String > sqlWithoutComments ;
187+ private final Supplier < Boolean > returningClause ;
184188 private final ReadQueryUpdateTransactionOption [] optionsFromHints ;
185189
186190 private static ParsedStatement clientSideStatement (
187191 ClientSideStatementImpl clientSideStatement ,
188192 Statement statement ,
189- String sqlWithoutComments ) {
193+ Supplier < String > sqlWithoutComments ) {
190194 return new ParsedStatement (clientSideStatement , statement , sqlWithoutComments );
191195 }
192196
193- private static ParsedStatement ddl (Statement statement , String sqlWithoutComments ) {
197+ private static ParsedStatement ddl (Statement statement , Supplier < String > sqlWithoutComments ) {
194198 return new ParsedStatement (StatementType .DDL , statement , sqlWithoutComments );
195199 }
196200
197201 private static ParsedStatement query (
198202 Statement statement ,
199- String sqlWithoutComments ,
203+ Supplier < String > sqlWithoutComments ,
200204 QueryOptions defaultQueryOptions ,
201205 ReadQueryUpdateTransactionOption [] optionsFromHints ) {
202206 return new ParsedStatement (
@@ -205,57 +209,66 @@ private static ParsedStatement query(
205209 statement ,
206210 sqlWithoutComments ,
207211 defaultQueryOptions ,
208- false ,
212+ Suppliers . ofInstance ( false ) ,
209213 optionsFromHints );
210214 }
211215
212216 private static ParsedStatement update (
213217 Statement statement ,
214- String sqlWithoutComments ,
215- boolean returningClause ,
218+ Supplier < String > sqlWithoutComments ,
219+ Supplier < Boolean > returningClause ,
216220 ReadQueryUpdateTransactionOption [] optionsFromHints ) {
217221 return new ParsedStatement (
218222 StatementType .UPDATE , statement , sqlWithoutComments , returningClause , optionsFromHints );
219223 }
220224
221- private static ParsedStatement unknown (Statement statement , String sqlWithoutComments ) {
225+ private static ParsedStatement unknown (
226+ Statement statement , Supplier <String > sqlWithoutComments ) {
222227 return new ParsedStatement (StatementType .UNKNOWN , statement , sqlWithoutComments );
223228 }
224229
225230 private ParsedStatement (
226231 ClientSideStatementImpl clientSideStatement ,
227232 Statement statement ,
228- String sqlWithoutComments ) {
233+ Supplier < String > sqlWithoutComments ) {
229234 Preconditions .checkNotNull (clientSideStatement );
230235 Preconditions .checkNotNull (statement );
231236 this .type = StatementType .CLIENT_SIDE ;
232237 this .clientSideStatement = clientSideStatement ;
233238 this .statement = statement ;
234- this .sqlWithoutComments = Preconditions . checkNotNull ( sqlWithoutComments ) ;
235- this .returningClause = false ;
239+ this .sqlWithoutComments = sqlWithoutComments ;
240+ this .returningClause = Suppliers . ofInstance ( false ) ;
236241 this .optionsFromHints = EMPTY_OPTIONS ;
237242 }
238243
239244 private ParsedStatement (
240245 StatementType type ,
241246 Statement statement ,
242- String sqlWithoutComments ,
243- boolean returningClause ,
247+ Supplier < String > sqlWithoutComments ,
248+ Supplier < Boolean > returningClause ,
244249 ReadQueryUpdateTransactionOption [] optionsFromHints ) {
245250 this (type , null , statement , sqlWithoutComments , null , returningClause , optionsFromHints );
246251 }
247252
248- private ParsedStatement (StatementType type , Statement statement , String sqlWithoutComments ) {
249- this (type , null , statement , sqlWithoutComments , null , false , EMPTY_OPTIONS );
253+ private ParsedStatement (
254+ StatementType type , Statement statement , Supplier <String > sqlWithoutComments ) {
255+ this (
256+ type ,
257+ null ,
258+ statement ,
259+ sqlWithoutComments ,
260+ null ,
261+ Suppliers .ofInstance (false ),
262+ EMPTY_OPTIONS );
250263 }
251264
252265 private ParsedStatement (
253266 StatementType type ,
254267 ClientSideStatementImpl clientSideStatement ,
255268 Statement statement ,
256- String sqlWithoutComments ,
269+ Supplier < String > sqlWithoutComments ,
257270 QueryOptions defaultQueryOptions ,
258- boolean returningClause ,
271+ Supplier < Boolean > returningClause ,
259272 ReadQueryUpdateTransactionOption [] optionsFromHints ) {
260273 Preconditions .checkNotNull (type );
261274 this .type = type ;
@@ -315,7 +328,7 @@ public StatementType getType() {
315328 /** @return whether the statement has a returning clause or not. */
316329 @ InternalApi
317330 public boolean hasReturningClause () {
318- return this .returningClause ;
331+ return this .returningClause . get () ;
319332 }
320333
321334 @ InternalApi
@@ -413,7 +426,7 @@ Statement mergeQueryOptions(Statement statement, QueryOptions defaultQueryOption
413426 /** @return the SQL statement with all comments removed from the SQL string. */
414427 @ InternalApi
415428 public String getSqlWithoutComments () {
416- return sqlWithoutComments ;
429+ return sqlWithoutComments . get () ;
417430 }
418431
419432 ClientSideStatement getClientSideStatement () {
@@ -464,7 +477,7 @@ private static boolean isRecordStatementCacheStats() {
464477 // We do length*2 because Java uses 2 bytes for each char.
465478 .weigher (
466479 (Weigher <String , ParsedStatement >)
467- (key , value ) -> 2 * key .length () + 2 * value .sqlWithoutComments .length ())
480+ (key , value ) -> 2 * key .length () + 2 * value .statement . getSql () .length ())
468481 .concurrencyLevel (Runtime .getRuntime ().availableProcessors ());
469482 if (isRecordStatementCacheStats ()) {
470483 cacheBuilder .recordStats ();
@@ -511,28 +524,56 @@ ParsedStatement parse(Statement statement, QueryOptions defaultQueryOptions) {
511524 return parsedStatement .copy (statement , defaultQueryOptions );
512525 }
513526
514- private ParsedStatement internalParse (Statement statement , QueryOptions defaultQueryOptions ) {
515- StatementHintParser statementHintParser =
516- new StatementHintParser (getDialect (), statement . getSql () );
527+ ParsedStatement internalParse (Statement statement , QueryOptions defaultQueryOptions ) {
528+ String sql = statement . getSql ();
529+ StatementHintParser statementHintParser = new StatementHintParser (getDialect (), sql );
517530 ReadQueryUpdateTransactionOption [] optionsFromHints = EMPTY_OPTIONS ;
518531 if (statementHintParser .hasStatementHints ()
519532 && !statementHintParser .getClientSideStatementHints ().isEmpty ()) {
520533 statement =
521534 statement .toBuilder ().replace (statementHintParser .getSqlWithoutClientSideHints ()).build ();
522535 optionsFromHints = convertHintsToOptions (statementHintParser .getClientSideStatementHints ());
523536 }
524- String sql = removeCommentsAndTrim (statement .getSql ());
525- ClientSideStatementImpl client = parseClientSideStatement (sql );
537+ // Create a supplier that will actually remove all comments and hints from the SQL string to be
538+ // backwards compatible with anything that really needs the SQL string without comments.
539+ Supplier <String > sqlWithoutCommentsSupplier =
540+ Suppliers .memoize (() -> removeCommentsAndTrim (sql ));
541+
542+ // Get rid of any spaces/comments at the start of the string.
543+ SimpleParser simpleParser = new SimpleParser (getDialect (), sql );
544+ simpleParser .skipWhitespaces ();
545+ // Create a wrapper around the SQL string from the point after the first whitespace.
546+ CharBuffer charBuffer = CharBuffer .wrap (sql , simpleParser .getPos (), sql .length ());
547+ ClientSideStatementImpl client = parseClientSideStatement (charBuffer );
548+
526549 if (client != null ) {
527- return ParsedStatement .clientSideStatement (client , statement , sql );
528- } else if (isQuery (sql )) {
529- return ParsedStatement .query (statement , sql , defaultQueryOptions , optionsFromHints );
530- } else if (isUpdateStatement (sql )) {
531- return ParsedStatement .update (statement , sql , checkReturningClause (sql ), optionsFromHints );
532- } else if (isDdlStatement (sql )) {
533- return ParsedStatement .ddl (statement , sql );
550+ return ParsedStatement .clientSideStatement (client , statement , sqlWithoutCommentsSupplier );
551+ } else {
552+ Result keywordResult = simpleParser .eatNextKeyword ();
553+ if (keywordResult .isValid ()) {
554+ String keyword = keywordResult .getValue ().toUpperCase ();
555+ if (keywordResult .isInParenthesis ()) {
556+ if (SELECT_STATEMENTS_ALLOWING_PRECEDING_BRACKETS .contains (keyword )) {
557+ return ParsedStatement .query (
558+ statement , sqlWithoutCommentsSupplier , defaultQueryOptions , optionsFromHints );
559+ }
560+ } else {
561+ if (selectStatements .contains (keyword )) {
562+ return ParsedStatement .query (
563+ statement , sqlWithoutCommentsSupplier , defaultQueryOptions , optionsFromHints );
564+ } else if (dmlStatements .contains (keyword )) {
565+ return ParsedStatement .update (
566+ statement ,
567+ sqlWithoutCommentsSupplier ,
568+ Suppliers .memoize (() -> checkReturningClause (sqlWithoutCommentsSupplier .get ())),
569+ optionsFromHints );
570+ } else if (ddlStatements .contains (keyword )) {
571+ return ParsedStatement .ddl (statement , sqlWithoutCommentsSupplier );
572+ }
573+ }
574+ }
534575 }
535- return ParsedStatement .unknown (statement , sql );
576+ return ParsedStatement .unknown (statement , sqlWithoutCommentsSupplier );
536577 }
537578
538579 /**
@@ -546,7 +587,7 @@ private ParsedStatement internalParse(Statement statement, QueryOptions defaultQ
546587 * statement.
547588 */
548589 @ VisibleForTesting
549- ClientSideStatementImpl parseClientSideStatement (String sql ) {
590+ ClientSideStatementImpl parseClientSideStatement (CharSequence sql ) {
550591 for (ClientSideStatementImpl css : statements ) {
551592 if (css .matches (sql )) {
552593 return css ;
0 commit comments