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