11package com .clickhouse .jdbc ;
22
3+ import com .clickhouse .client .api .metadata .TableSchema ;
4+ import com .clickhouse .data .ClickHouseColumn ;
35import com .clickhouse .data .Tuple ;
46import com .clickhouse .jdbc .internal .ExceptionUtils ;
57import org .slf4j .Logger ;
2628import java .sql .SQLFeatureNotSupportedException ;
2729import java .sql .SQLType ;
2830import java .sql .SQLXML ;
31+ import java .sql .Statement ;
2932import java .sql .Time ;
3033import java .sql .Timestamp ;
3134import java .sql .Types ;
4245import java .util .ArrayList ;
4346import java .util .Calendar ;
4447import java .util .Collection ;
45- import java .util .GregorianCalendar ;
4648import java .util .List ;
4749import java .util .Map ;
4850
@@ -64,11 +66,12 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat
6466 String insertIntoSQL ;
6567
6668 StatementType statementType ;
69+
6770 public PreparedStatementImpl (ConnectionImpl connection , String sql ) throws SQLException {
6871 super (connection );
6972 this .originalSql = sql .trim ();
7073 //Split the sql string into an array of strings around question mark tokens
71- this .sqlSegments = originalSql . split ( " \\ ?" );
74+ this .sqlSegments = splitStatement ( originalSql );
7275 this .statementType = parseStatementType (originalSql );
7376
7477 if (statementType == StatementType .INSERT ) {
@@ -77,17 +80,11 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql) throws SQLEx
7780 }
7881
7982 //Create an array of objects to store the parameters
80- if (originalSql .contains ("?" )) {
81- int count = originalSql .length () - originalSql .replace ("?" , "" ).length ();
82- this .parameters = new Object [count ];
83- } else {
84- this .parameters = new Object [0 ];
85- }
86-
83+ this .parameters = new Object [sqlSegments .length - 1 ];
8784 this .defaultCalendar = connection .defaultCalendar ;
8885 }
8986
90- private String compileSql (String []segments ) {
87+ private String compileSql (String [] segments ) {
9188 StringBuilder sb = new StringBuilder ();
9289 for (int i = 0 ; i < segments .length ; i ++) {
9390 sb .append (segments [i ]);
@@ -98,6 +95,7 @@ private String compileSql(String []segments) {
9895 LOG .trace ("Compiled SQL: {}" , sb );
9996 return sb .toString ();
10097 }
98+
10199 @ Override
102100 public ResultSet executeQuery () throws SQLException {
103101 checkClosed ();
@@ -517,14 +515,14 @@ private static String encodeObject(Object x) throws SQLException {
517515 } else if (x instanceof ZonedDateTime ) {
518516 return encodeObject (((ZonedDateTime ) x ).toInstant ());
519517 } else if (x instanceof Instant ) {
520- return "fromUnixTimestamp64Nano(" + (((Instant ) x ).getEpochSecond () * 1_000_000_000L + ((Instant ) x ).getNano ())+ ")" ;
518+ return "fromUnixTimestamp64Nano(" + (((Instant ) x ).getEpochSecond () * 1_000_000_000L + ((Instant ) x ).getNano ()) + ")" ;
521519 } else if (x instanceof InetAddress ) {
522520 return "'" + ((InetAddress ) x ).getHostAddress () + "'" ;
523521 } else if (x instanceof Array ) {
524522 StringBuilder listString = new StringBuilder ();
525523 listString .append ("[" );
526524 int i = 0 ;
527- for (Object item : (Object [])((Array ) x ).getArray ()) {
525+ for (Object item : (Object []) ((Array ) x ).getArray ()) {
528526 if (i > 0 ) {
529527 listString .append (", " );
530528 }
@@ -616,4 +614,70 @@ private static String encodeObject(Object x) throws SQLException {
616614 private static String escapeString (String x ) {
617615 return x .replace ("\\ " , "\\ \\ " ).replace ("'" , "\\ '" );//Escape single quotes
618616 }
617+
618+ private static String [] splitStatement (String sql ) {
619+ List <String > segments = new ArrayList <>();
620+ char [] chars = sql .toCharArray ();
621+ int segmentStart = 0 ;
622+ for (int i = 0 ; i < chars .length ; i ++) {
623+ char c = chars [i ];
624+ if (c == '\'' || c == '"' || c == '`' ) {
625+ // string literal or identifier
626+ i = skip (chars , i + 1 , c , true );
627+ } else if (c == '/' && lookahead (chars , i ) == '*' ) {
628+ // block comment
629+ int end = sql .indexOf ("*/" , i );
630+ if (end == -1 ) {
631+ // missing comment end
632+ break ;
633+ }
634+ i = end + 1 ;
635+ } else if (c == '#' || (c == '-' && lookahead (chars , i ) == '-' )) {
636+ // line comment
637+ i = skip (chars , i + 1 , '\n' , false );
638+ } else if (c == '?' ) {
639+ // question mark
640+ segments .add (sql .substring (segmentStart , i ));
641+ segmentStart = i + 1 ;
642+ }
643+ }
644+ if (segmentStart < chars .length ) {
645+ segments .add (sql .substring (segmentStart ));
646+ } else {
647+ // add empty segment in case question mark was last char of sql
648+ segments .add ("" );
649+ }
650+ return segments .toArray (new String [0 ]);
651+ }
652+
653+ private static int skip (char [] chars , int from , char until , boolean escape ) {
654+ for (int i = from ; i < chars .length ; i ++) {
655+ char curr = chars [i ];
656+ if (escape ) {
657+ char next = lookahead (chars , i );
658+ if ((curr == '\\' && (next == '\\' || next == until )) || (curr == until && next == until )) {
659+ // should skip:
660+ // 1) double \\ (backslash escaped with backslash)
661+ // 2) \[until] ([until] char, escaped with backslash)
662+ // 3) [until][until] ([until] char, escaped with [until])
663+ i ++;
664+ continue ;
665+ }
666+ }
667+
668+ if (curr == until ) {
669+ return i ;
670+ }
671+ }
672+ return chars .length ;
673+ }
674+
675+ private static char lookahead (char [] chars , int pos ) {
676+ pos = pos + 1 ;
677+ if (pos >= chars .length ) {
678+ return '\0' ;
679+ }
680+ return chars [pos ];
681+ }
682+
619683}
0 commit comments