77import java .util .List ;
88
99import tech .ydb .jdbc .YdbConst ;
10- import tech .ydb .jdbc .common .TypeDescription ;
11- import tech .ydb .table .values .PrimitiveType ;
10+ import tech .ydb .jdbc .query .params .JdbcPrm ;
1211
1312
1413/**
1817public class YdbQueryParser {
1918 private final boolean isDetectQueryType ;
2019 private final boolean isDetectJdbcParameters ;
20+ private final boolean isConvertJdbcInToList ;
2121
2222 private final List <QueryStatement > statements = new ArrayList <>();
2323 private final YqlBatcher batcher = new YqlBatcher ();
24+ private final String origin ;
25+ private final StringBuilder parsed ;
2426
25- public YdbQueryParser (boolean isDetectQueryType , boolean isDetectJdbcParameters ) {
27+ private int jdbcPrmIndex = 0 ;
28+
29+ public YdbQueryParser (String origin , boolean isDetectQueryType , boolean isDetectParameters , boolean isConvertIn ) {
2630 this .isDetectQueryType = isDetectQueryType ;
27- this .isDetectJdbcParameters = isDetectJdbcParameters ;
31+ this .isDetectJdbcParameters = isDetectParameters ;
32+ this .isConvertJdbcInToList = isConvertIn ;
33+ this .origin = origin ;
34+ this .parsed = new StringBuilder (origin .length () + 10 );
2835 }
2936
3037 public List <QueryStatement > getStatements () {
@@ -56,7 +63,7 @@ public QueryType detectQueryType() throws SQLException {
5663 }
5764
5865 @ SuppressWarnings ("MethodLength" )
59- public String parseSQL (String origin ) throws SQLException {
66+ public String parseSQL () throws SQLException {
6067 int fragmentStart = 0 ;
6168 boolean detectJdbcArgs = false ;
6269
@@ -65,16 +72,13 @@ public String parseSQL(String origin) throws SQLException {
6572
6673 int parenLevel = 0 ;
6774 int keywordStart = -1 ;
68- boolean lastKeywordIsOffsetLimit = false ;
6975
7076 char [] chars = origin .toCharArray ();
7177
72- StringBuilder parsed = new StringBuilder (origin .length () + 10 );
73- ArgNameGenerator argNameGenerator = new ArgNameGenerator ();
74-
7578 for (int i = 0 ; i < chars .length ; ++i ) {
7679 char ch = chars [i ];
7780 boolean isInsideKeyword = false ;
81+
7882 int keywordEnd = i ; // parseSingleQuotes, parseDoubleQuotes, etc move index so we keep old value
7983 switch (ch ) {
8084 case '\'' : // single-quotes
@@ -102,6 +106,7 @@ public String parseSQL(String origin) throws SQLException {
102106 case '/' : // possibly /* */ style comment
103107 i = parseBlockComment (chars , i );
104108 break ;
109+
105110 case '?' :
106111 if (detectJdbcArgs && statement != null ) {
107112 parsed .append (chars , fragmentStart , i - fragmentStart );
@@ -110,22 +115,15 @@ public String parseSQL(String origin) throws SQLException {
110115 batcher .readIdentifier (chars , i , 1 );
111116 i ++; // make sure the coming ? is not treated as a bind
112117 } else {
113- String binded = argNameGenerator .createArgName (origin );
114- // force type UInt64 for OFFSET and LIMIT parameters
115- TypeDescription forcedType = lastKeywordIsOffsetLimit
116- ? TypeDescription .of (PrimitiveType .Uint64 )
117- : null ;
118- statement .addParameter (binded , forcedType );
119- parsed .append (binded );
120-
118+ String name = nextJdbcPrmName ();
119+ statement .addJdbcPrmFactory (JdbcPrm .simplePrm (name ));
120+ parsed .append (name );
121121 batcher .readParameter ();
122122 }
123123 fragmentStart = i + 1 ;
124124 }
125125 break ;
126126 default :
127- lastKeywordIsOffsetLimit = lastKeywordIsOffsetLimit && Character .isWhitespace (ch );
128-
129127 if (keywordStart >= 0 ) {
130128 isInsideKeyword = Character .isJavaIdentifierPart (ch );
131129 break ;
@@ -140,7 +138,6 @@ public String parseSQL(String origin) throws SQLException {
140138
141139
142140 if (keywordStart >= 0 && (!isInsideKeyword || (i == chars .length - 1 ))) {
143- lastKeywordIsOffsetLimit = false ;
144141 int keywordLength = (isInsideKeyword ? i + 1 : keywordEnd ) - keywordStart ;
145142
146143 if (statement != null ) {
@@ -151,9 +148,23 @@ public String parseSQL(String origin) throws SQLException {
151148 statement .setHasReturning (true );
152149 }
153150
154- if (parseOffsetKeyword (chars , keywordStart , keywordLength )
155- || parseLimitKeyword (chars , keywordStart , keywordLength )) {
156- lastKeywordIsOffsetLimit = Character .isWhitespace (ch );
151+ // Process ? after OFFSET and LIMIT
152+ if (i < chars .length && detectJdbcArgs && Character .isWhitespace (ch )) {
153+ if (parseOffsetKeyword (chars , keywordStart , keywordLength )
154+ || parseLimitKeyword (chars , keywordStart , keywordLength )) {
155+ parsed .append (chars , fragmentStart , i - fragmentStart );
156+ i = parseOffsetLimitParameter (chars , i , statement );
157+ fragmentStart = i ;
158+ }
159+ }
160+
161+ // Process IN (?, ?, ... )
162+ if (i < chars .length && detectJdbcArgs && isConvertJdbcInToList ) {
163+ if (parseInKeyword (chars , keywordStart , keywordLength )) {
164+ parsed .append (chars , fragmentStart , i - fragmentStart );
165+ i = parseInListParameters (chars , i , statement );
166+ fragmentStart = i ;
167+ }
157168 }
158169 } else {
159170 boolean skipped = false ;
@@ -272,18 +283,102 @@ public String parseSQL(String origin) throws SQLException {
272283 return parsed .toString ();
273284 }
274285
275- private static class ArgNameGenerator {
276- private int index = 0 ;
286+ private String nextJdbcPrmName () {
287+ while (true ) {
288+ jdbcPrmIndex += 1 ;
289+ String name = YdbConst .AUTO_GENERATED_PARAMETER_PREFIX + jdbcPrmIndex ;
290+ if (!origin .contains (name )) {
291+ return name ;
292+ }
293+ }
294+ }
277295
278- public String createArgName (String origin ) {
279- while (true ) {
280- index += 1 ;
281- String name = YdbConst .AUTO_GENERATED_PARAMETER_PREFIX + index ;
282- if (!origin .contains (name )) {
283- return name ;
284- }
296+ private int parseOffsetLimitParameter (char [] query , int offset , QueryStatement st ) {
297+ int start = offset ;
298+ while (++offset < query .length ) {
299+ char ch = query [offset ];
300+ switch (ch ) {
301+ case '?' :
302+ if (offset + 1 < query .length && query [offset + 1 ] == '?' ) {
303+ return start ;
304+ }
305+ String name = nextJdbcPrmName ();
306+ parsed .append (query , start , offset - start );
307+ parsed .append (name );
308+ st .addJdbcPrmFactory (JdbcPrm .uint64Prm (name ));
309+ return offset + 1 ;
310+ case '-' : // possibly -- style comment
311+ offset = parseLineComment (query , offset );
312+ break ;
313+ case '/' : // possibly /* */ style comment
314+ offset = parseBlockComment (query , offset );
315+ break ;
316+ default :
317+ if (!Character .isWhitespace (query [offset ])) {
318+ return start ;
319+ }
320+ break ;
321+ }
322+ }
323+
324+ return start ;
325+ }
326+
327+ private int parseInListParameters (char [] query , int offset , QueryStatement st ) {
328+ int start = offset ;
329+ int listStartedAt = -1 ;
330+ int listSize = 0 ;
331+ boolean waitPrm = false ;
332+ while (offset < query .length ) {
333+ char ch = query [offset ];
334+ switch (ch ) {
335+ case '(' : // start of list
336+ if (listStartedAt >= 0 ) {
337+ return start ;
338+ }
339+ listStartedAt = offset ;
340+ waitPrm = true ;
341+ break ;
342+ case ',' :
343+ if (listStartedAt < 0 || waitPrm ) {
344+ return start ;
345+ }
346+ waitPrm = true ;
347+ break ;
348+ case '?' :
349+ if (!waitPrm || (offset + 1 < query .length && query [offset + 1 ] == '?' )) {
350+ return start ;
351+ }
352+ listSize ++;
353+ waitPrm = false ;
354+ break ;
355+ case ')' :
356+ if (waitPrm || listSize == 0 || listStartedAt < 0 ) {
357+ return start ;
358+ }
359+
360+ String name = nextJdbcPrmName ();
361+ parsed .append (query , start , listStartedAt - start );
362+ parsed .append (' ' ); // add extra space to avoid IN$jpN
363+ parsed .append (name );
364+ st .addJdbcPrmFactory (JdbcPrm .inListOrm (name , listSize ));
365+ return offset + 1 ;
366+ case '-' : // possibly -- style comment
367+ offset = parseLineComment (query , offset );
368+ break ;
369+ case '/' : // possibly /* */ style comment
370+ offset = parseBlockComment (query , offset );
371+ break ;
372+ default :
373+ if (!Character .isWhitespace (query [offset ])) {
374+ return start ;
375+ }
376+ break ;
285377 }
378+ offset ++;
286379 }
380+
381+ return start ;
287382 }
288383
289384 private static int parseSingleQuotes (final char [] query , int offset ) {
@@ -553,4 +648,13 @@ private static boolean parseLimitKeyword(char[] query, int offset, int length) {
553648 && (query [offset + 3 ] | 32 ) == 'i'
554649 && (query [offset + 4 ] | 32 ) == 't' ;
555650 }
651+
652+ private static boolean parseInKeyword (char [] query , int offset , int length ) {
653+ if (length != 2 ) {
654+ return false ;
655+ }
656+
657+ return (query [offset ] | 32 ) == 'i'
658+ && (query [offset + 1 ] | 32 ) == 'n' ;
659+ }
556660}
0 commit comments