11package com .clickhouse .jdbc ;
22
3+ import com .clickhouse .client .api .insert .InsertSettings ;
4+ import com .clickhouse .client .api .metadata .TableSchema ;
35import org .slf4j .Logger ;
46import org .slf4j .LoggerFactory ;
57
3335import java .time .format .DateTimeFormatter ;
3436import java .time .format .DateTimeFormatterBuilder ;
3537import java .time .temporal .ChronoField ;
38+ import java .util .ArrayList ;
3639import java .util .Calendar ;
3740import java .util .Collection ;
3841import java .util .GregorianCalendar ;
42+ import java .util .List ;
3943import java .util .Map ;
4044
4145public class PreparedStatementImpl extends StatementImpl implements PreparedStatement , JdbcV2Wrapper {
@@ -48,20 +52,50 @@ public class PreparedStatementImpl extends StatementImpl implements PreparedStat
4852 .appendPattern ("yyyy-MM-dd HH:mm:ss" ).appendFraction (ChronoField .NANO_OF_SECOND , 0 , 9 , true ).toFormatter ();
4953
5054 String originalSql ;
51- String [] sqlSegments ;
52- Object [] parameters ;
53- public PreparedStatementImpl (ConnectionImpl connection , String sql ) {
55+ String [] sqlSegments ;
56+ Object [] parameters ;
57+ int setCalls ;//The number of times set* methods have been called
58+
59+ //For insertRowBinary fanciness
60+ StatementType statementType ;
61+ boolean insertRowBinary ;
62+ List <Object > insertRowBinaryData ;
63+ String tableName ;
64+ TableSchema tableSchema ;
65+
66+
67+ public PreparedStatementImpl (ConnectionImpl connection , String sql ) throws SQLException {
5468 super (connection );
55- this .originalSql = sql ;
56- //Split the sql string into an array of strings around question mark tokens
57- this .sqlSegments = sql .split ("\\ ?" );
58-
59- //Create an array of objects to store the parameters
60- if (originalSql .contains ("?" )) {
61- int count = originalSql .length () - originalSql .replace ("?" , "" ).length ();
62- this .parameters = new Object [count ];
63- } else {
64- this .parameters = new Object [0 ];
69+
70+ try {
71+ this .originalSql = sql ;
72+ this .setCalls = 0 ;
73+ this .statementType = parseStatementType (sql );
74+ this .insertRowBinary = false ;
75+ this .insertRowBinaryData = new ArrayList <>();
76+ this .tableName = null ;
77+ this .tableSchema = null ;
78+
79+ //Split the sql string into an array of strings around question mark tokens
80+ this .sqlSegments = sql .split ("\\ ?" );
81+
82+ //Create an array of objects to store the parameters
83+ if (originalSql .contains ("?" )) {
84+ int count = originalSql .length () - originalSql .replace ("?" , "" ).length ();
85+ this .parameters = new Object [count ];
86+ } else {
87+ if (statementType != StatementType .INSERT ) {
88+ throw new SQLException ("SQL prepared statement does not contain any placeholders." );
89+ }
90+
91+ this .parameters = new Object [0 ];
92+ this .insertRowBinary = true ;//We only use this when no parameters are present and the statement is an insert
93+ this .tableName = parseTableName (sql );
94+ this .tableSchema = connection .client .getTableSchema (tableName );
95+ }
96+ } catch (Exception e ) {
97+ LOG .error ("Error creating prepared statement" , e );
98+ throw new SQLException ("Error creating prepared statement" , e );
6599 }
66100 }
67101
@@ -77,16 +111,91 @@ private String compileSql() {
77111 return sb .toString ();
78112 }
79113
114+ private boolean enoughParameters () {
115+ return this .parameters .length == setCalls || insertRowBinary ;
116+ }
117+
80118 @ Override
81119 public ResultSet executeQuery () throws SQLException {
82120 checkClosed ();
83- return executeQuery (compileSql ());
121+
122+ if (!enoughParameters ()) {
123+ throw new SQLException ("The number of parameters does not match the number of placeholders in the SQL string." );
124+ }
125+
126+ return super .executeQuery (compileSql ());
127+ }
128+
129+ @ Override
130+ public ResultSet executeQuery (String sql ) throws SQLException {
131+ checkClosed ();
132+ throw new SQLException ("executeQuery(String) is not supported in PreparedStatements." );
84133 }
85134
86135 @ Override
87136 public int executeUpdate () throws SQLException {
88137 checkClosed ();
89- return executeUpdate (compileSql ());
138+ if (insertRowBinary ) {
139+ return (int ) super .executeInsert (tableName , insertRowBinaryData , new InsertSettings ());
140+ }
141+
142+ if (!enoughParameters ()) {
143+ throw new SQLException ("The number of parameters does not match the number of placeholders in the SQL string." );
144+ }
145+
146+ return super .executeUpdate (compileSql ());
147+ }
148+
149+ @ Override
150+ public int executeUpdate (String sql ) throws SQLException {
151+ checkClosed ();
152+ throw new SQLException ("executeUpdate(String) is not supported in PreparedStatements." );
153+ }
154+
155+ @ Override
156+ public boolean execute () throws SQLException {
157+ checkClosed ();
158+ if (insertRowBinary ) {
159+ super .executeInsert (tableName , insertRowBinaryData , new InsertSettings ());
160+ return false ;
161+ }
162+
163+ if (!enoughParameters ()) {
164+ throw new SQLException ("The number of parameters does not match the number of placeholders in the SQL string." );
165+ }
166+ return super .execute (compileSql ());
167+ }
168+
169+ @ Override
170+ public boolean execute (String sql ) throws SQLException {
171+ checkClosed ();
172+ throw new SQLException ("execute(String) is not supported in PreparedStatements." );
173+ }
174+
175+ @ Override
176+ public void addBatch () throws SQLException {
177+ checkClosed ();
178+ if (!insertRowBinary ) {//We ignore this for insertRowBinary
179+ if (!enoughParameters ()) {
180+ throw new SQLException ("The number of parameters does not match the number of placeholders in the SQL string." );
181+ }
182+ super .addBatch (compileSql ());
183+ }
184+ }
185+
186+ @ Override
187+ public void addBatch (String sql ) throws SQLException {
188+ checkClosed ();
189+ throw new SQLException ("addBatch(String) is not supported in PreparedStatements." );
190+ }
191+
192+ @ Override
193+ public int [] executeBatch () throws SQLException {
194+ checkClosed ();
195+ if (insertRowBinary ) {
196+ return new int [] { (int ) super .executeInsert (tableName , insertRowBinaryData , new InsertSettings ()) };
197+ }
198+ return super .executeBatch ();
90199 }
91200
92201 @ Override
@@ -152,6 +261,7 @@ public void setString(int parameterIndex, String x) throws SQLException {
152261 @ Override
153262 public void setBytes (int parameterIndex , byte [] x ) throws SQLException {
154263 checkClosed ();
264+
155265 parameters [parameterIndex - 1 ] = encodeObject (x );
156266 }
157267
@@ -194,11 +304,9 @@ public void setBinaryStream(int parameterIndex, InputStream x, int length) throw
194304 @ Override
195305 public void clearParameters () throws SQLException {
196306 checkClosed ();
197- if (originalSql .contains ("?" )) {
198- this .parameters = new Object [sqlSegments .length ];
199- } else {
200- this .parameters = new Object [0 ];
201- }
307+ int paramCount = parameters .length ;
308+ this .parameters = new Object [paramCount ];
309+ this .setCalls = 0 ;
202310 }
203311
204312 @ Override
@@ -213,18 +321,6 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
213321 setObject (parameterIndex , x , Types .OTHER );
214322 }
215323
216- @ Override
217- public boolean execute () throws SQLException {
218- checkClosed ();
219- return execute (compileSql ());
220- }
221-
222- @ Override
223- public void addBatch () throws SQLException {
224- checkClosed ();
225- addBatch (compileSql ());
226- }
227-
228324 @ Override
229325 public void setCharacterStream (int parameterIndex , Reader x , int length ) throws SQLException {
230326 checkClosed ();
@@ -240,13 +336,13 @@ public void setRef(int parameterIndex, Ref x) throws SQLException {
240336 @ Override
241337 public void setBlob (int parameterIndex , Blob x ) throws SQLException {
242338 checkClosed ();
243- parameters [ parameterIndex - 1 ] = encodeObject ( x );
339+ throw new SQLFeatureNotSupportedException ( "Blob is not supported." );
244340 }
245341
246342 @ Override
247343 public void setClob (int parameterIndex , Clob x ) throws SQLException {
248344 checkClosed ();
249- parameters [ parameterIndex - 1 ] = encodeObject ( x );
345+ throw new SQLFeatureNotSupportedException ( "Clob is not supported." );
250346 }
251347
252348 @ Override
@@ -342,31 +438,31 @@ public void setNCharacterStream(int parameterIndex, Reader x, long length) throw
342438 @ Override
343439 public void setNClob (int parameterIndex , NClob x ) throws SQLException {
344440 checkClosed ();
345- parameters [ parameterIndex - 1 ] = encodeObject ( x );
441+ throw new SQLFeatureNotSupportedException ( "NClob is not supported." );
346442 }
347443
348444 @ Override
349445 public void setClob (int parameterIndex , Reader x , long length ) throws SQLException {
350446 checkClosed ();
351- parameters [ parameterIndex - 1 ] = encodeObject ( x );
447+ throw new SQLFeatureNotSupportedException ( "Clob is not supported." );
352448 }
353449
354450 @ Override
355451 public void setBlob (int parameterIndex , InputStream x , long length ) throws SQLException {
356452 checkClosed ();
357- parameters [ parameterIndex - 1 ] = encodeObject ( x );
453+ throw new SQLFeatureNotSupportedException ( "Blob is not supported." );
358454 }
359455
360456 @ Override
361457 public void setNClob (int parameterIndex , Reader x , long length ) throws SQLException {
362458 checkClosed ();
363- parameters [ parameterIndex - 1 ] = encodeObject ( x );
459+ throw new SQLFeatureNotSupportedException ( "NClob is not supported." );
364460 }
365461
366462 @ Override
367463 public void setSQLXML (int parameterIndex , SQLXML x ) throws SQLException {
368464 checkClosed ();
369- parameters [ parameterIndex - 1 ] = encodeObject ( x );
465+ throw new SQLFeatureNotSupportedException ( "SQLXML is not supported." );
370466 }
371467
372468 @ Override
@@ -420,25 +516,40 @@ public void setNCharacterStream(int parameterIndex, Reader x) throws SQLExceptio
420516 @ Override
421517 public void setClob (int parameterIndex , Reader x ) throws SQLException {
422518 checkClosed ();
423- parameters [ parameterIndex - 1 ] = encodeObject ( x );
519+ throw new SQLFeatureNotSupportedException ( "Clob is not supported." );
424520 }
425521
426522 @ Override
427523 public void setBlob (int parameterIndex , InputStream x ) throws SQLException {
428524 checkClosed ();
429- parameters [ parameterIndex - 1 ] = encodeObject ( x );
525+ throw new SQLFeatureNotSupportedException ( "Blob is not supported." );
430526 }
431527
432528 @ Override
433529 public void setNClob (int parameterIndex , Reader x ) throws SQLException {
434530 checkClosed ();
435- parameters [ parameterIndex - 1 ] = encodeObject ( x );
531+ throw new SQLFeatureNotSupportedException ( "NClob is not supported." );
436532 }
437533
438534 @ Override
439535 public void setObject (int parameterIndex , Object x , SQLType targetSqlType , int scaleOrLength ) throws SQLException {
440536 checkClosed ();
441- parameters [parameterIndex - 1 ] = encodeObject (x );
537+
538+ try {
539+ if (!insertRowBinary ) {
540+ parameters [parameterIndex - 1 ] = encodeObject (x );
541+ } else {
542+ if (insertRowBinaryData .isEmpty ()) {//Register the first object
543+ Class <?> clazz = x .getClass ();
544+ connection .client .register (clazz , tableSchema );
545+ }
546+
547+ insertRowBinaryData .add (x );
548+ }
549+ } catch (Exception e ) {
550+ LOG .error ("Error setting object" , e );
551+ throw new SQLException ("Error setting object" , e );
552+ }
442553 }
443554
444555 @ Override
@@ -453,7 +564,14 @@ public long executeLargeUpdate() throws SQLException {
453564 return PreparedStatement .super .executeLargeUpdate ();
454565 }
455566
456- private static String encodeObject (Object x ) throws SQLException {
567+ private String encodeObject (Object x ) throws SQLException {
568+ return encodeObject (x , true );
569+ }
570+ private String encodeObject (Object x , boolean shouldIncrementCount ) throws SQLException {
571+ if (shouldIncrementCount ) {
572+ setCalls ++;
573+ }
574+
457575 try {
458576 if (x == null ) {
459577 return "NULL" ;
@@ -476,8 +594,8 @@ private static String encodeObject(Object x) throws SQLException {
476594 } else if (x instanceof Array ) {
477595 StringBuilder listString = new StringBuilder ();
478596 listString .append ("[" );
479- for (Object item : (Object [])((Array ) x ).getArray ()) {
480- listString .append (encodeObject (item )).append (", " );
597+ for (Object item : (Object []) ((Array ) x ).getArray ()) {
598+ listString .append (encodeObject (item , false )).append (", " );
481599 }
482600 listString .delete (listString .length () - 2 , listString .length ());
483601 listString .append ("]" );
@@ -487,7 +605,7 @@ private static String encodeObject(Object x) throws SQLException {
487605 StringBuilder listString = new StringBuilder ();
488606 listString .append ("[" );
489607 for (Object item : (Collection <?>) x ) {
490- listString .append (encodeObject (item )).append (", " );
608+ listString .append (encodeObject (item , false )).append (", " );
491609 }
492610 listString .delete (listString .length () - 2 , listString .length ());
493611 listString .append ("]" );
@@ -498,7 +616,7 @@ private static String encodeObject(Object x) throws SQLException {
498616 StringBuilder mapString = new StringBuilder ();
499617 mapString .append ("{" );
500618 for (Object key : tmpMap .keySet ()) {
501- mapString .append (encodeObject (key )).append (": " ).append (encodeObject (tmpMap .get (key ))).append (", " );
619+ mapString .append (encodeObject (key , false )).append (": " ).append (encodeObject (tmpMap .get (key ), false )).append (", " );
502620 }
503621 mapString .delete (mapString .length () - 2 , mapString .length ());
504622 mapString .append ("}" );
0 commit comments