1111import java .sql .SQLWarning ;
1212import java .util .ArrayList ;
1313import java .util .EnumMap ;
14+ import java .util .HashMap ;
1415import java .util .List ;
16+ import java .util .Locale ;
1517import java .util .Map ;
1618import java .util .TimeZone ;
1719import java .util .UUID ;
3840import ru .yandex .clickhouse .domain .ClickHouseFormat ;
3941import ru .yandex .clickhouse .except .ClickHouseException ;
4042import ru .yandex .clickhouse .except .ClickHouseExceptionSpecifier ;
43+ import ru .yandex .clickhouse .jdbc .parser .ClickHouseSqlParser ;
44+ import ru .yandex .clickhouse .jdbc .parser .ClickHouseSqlStatement ;
45+ import ru .yandex .clickhouse .jdbc .parser .StatementType ;
4146import ru .yandex .clickhouse .response .ClickHouseLZ4Stream ;
4247import ru .yandex .clickhouse .response .ClickHouseResponse ;
4348import ru .yandex .clickhouse .response .ClickHouseResponseSummary ;
@@ -84,16 +89,56 @@ public class ClickHouseStatementImpl extends ConfigurableApi<ClickHouseStatement
8489
8590 private volatile String queryId ;
8691
92+ protected ClickHouseSqlStatement parsedStmt ;
93+
8794 /**
8895 * Current database name may be changed by {@link java.sql.Connection#setCatalog(String)}
8996 * between creation of this object and query execution, but javadoc does not allow
9097 * {@code setCatalog} influence on already created statements.
9198 */
9299 private final String initialDatabase ;
93100
101+ @ Deprecated
94102 private static final String [] selectKeywords = new String []{"SELECT" , "WITH" , "SHOW" , "DESC" , "EXISTS" , "EXPLAIN" };
103+ @ Deprecated
95104 private static final String databaseKeyword = "CREATE DATABASE" ;
96105
106+ @ Deprecated
107+ protected void parseSingleStatement (String sql ) throws SQLException {
108+ this .parsedStmt = null ;
109+ ClickHouseSqlStatement [] stmts = ClickHouseSqlParser .parse (sql , properties );
110+
111+ if (stmts .length == 1 ) {
112+ this .parsedStmt = stmts [0 ];
113+ } else {
114+ this .parsedStmt = new ClickHouseSqlStatement (sql , StatementType .UNKNOWN );
115+ // throw new SQLException("Multiple statements are not supported.");
116+ }
117+
118+ if (this .parsedStmt .isIdemponent ()) {
119+ httpContext .setAttribute ("is_idempotent" , Boolean .TRUE );
120+ } else {
121+ httpContext .removeAttribute ("is_idempotent" );
122+ }
123+ }
124+
125+ @ Deprecated
126+ private void parseSingleStatement (String sql , ClickHouseFormat preferredFormat ) throws SQLException {
127+ parseSingleStatement (sql );
128+
129+ if (parsedStmt .isQuery () && !parsedStmt .hasFormat ()) {
130+ String format = preferredFormat .name ();
131+ Map <String , Integer > positions = new HashMap <>();
132+ positions .putAll (parsedStmt .getPositions ());
133+ positions .put (ClickHouseSqlStatement .KEYWORD_FORMAT , sql .length ());
134+
135+ sql = new StringBuilder (parsedStmt .getSQL ()).append ("\n FORMAT " ).append (format ).append (';' )
136+ .toString ();
137+ parsedStmt = new ClickHouseSqlStatement (sql , parsedStmt .getStatementType (),
138+ parsedStmt .getCluster (), parsedStmt .getDatabase (), parsedStmt .getTable (),
139+ format , parsedStmt .getOutfile (), parsedStmt .getParameters (), positions );
140+ }
141+ }
97142
98143 public ClickHouseStatementImpl (CloseableHttpClient client , ClickHouseConnection connection ,
99144 ClickHouseProperties properties , int resultSetType ) {
@@ -135,16 +180,29 @@ public ResultSet executeQuery(String sql,
135180 }
136181 additionalDBParams .put (ClickHouseQueryParam .EXTREMES , "0" );
137182
138- InputStream is = getInputStream (sql , additionalDBParams , externalData , additionalRequestParams );
183+ parseSingleStatement (sql , ClickHouseFormat .TabSeparatedWithNamesAndTypes );
184+ if (!parsedStmt .isRecognized () && isSelect (sql )) {
185+ Map <String , Integer > positions = new HashMap <>();
186+ String dbName = extractDBName (sql );
187+ String tableName = extractTableName (sql );
188+ if (extractWithTotals (sql )) {
189+ positions .put (ClickHouseSqlStatement .KEYWORD_TOTALS , 1 );
190+ }
191+ parsedStmt = new ClickHouseSqlStatement (sql , StatementType .SELECT ,
192+ null , dbName , tableName , null , null , null , positions );
193+ // httpContext.setAttribute("is_idempotent", Boolean.TRUE);
194+ }
139195
196+ InputStream is = getInputStream (sql , additionalDBParams , externalData , additionalRequestParams );
197+
140198 try {
141- if (isSelect ( sql )) {
199+ if (parsedStmt . isQuery ( )) {
142200 currentUpdateCount = -1 ;
143201 currentResult = createResultSet (properties .isCompress ()
144202 ? new ClickHouseLZ4Stream (is ) : is , properties .getBufferSize (),
145- extractDBName ( sql ),
146- extractTableName ( sql ),
147- extractWithTotals ( sql ),
203+ parsedStmt . getDatabaseOrDefault ( properties . getDatabase () ),
204+ parsedStmt . getTable ( ),
205+ parsedStmt . hasWithTotals ( ),
148206 this ,
149207 getConnection ().getTimeZone (),
150208 properties
@@ -176,8 +234,15 @@ public ClickHouseResponse executeQueryClickhouseResponse(String sql, Map<ClickHo
176234 public ClickHouseResponse executeQueryClickhouseResponse (String sql ,
177235 Map <ClickHouseQueryParam , String > additionalDBParams ,
178236 Map <String , String > additionalRequestParams ) throws SQLException {
237+ parseSingleStatement (sql , ClickHouseFormat .JSONCompact );
238+ if (parsedStmt .isRecognized ()) {
239+ sql = parsedStmt .getSQL ();
240+ } else {
241+ sql = addFormatIfAbsent (sql , ClickHouseFormat .JSONCompact );
242+ }
243+
179244 InputStream is = getInputStream (
180- addFormatIfAbsent ( sql , ClickHouseFormat . JSONCompact ) ,
245+ sql ,
181246 additionalDBParams ,
182247 null ,
183248 additionalRequestParams
@@ -206,14 +271,27 @@ public ClickHouseRowBinaryInputStream executeQueryClickhouseRowBinaryStream(Stri
206271
207272 @ Override
208273 public ClickHouseRowBinaryInputStream executeQueryClickhouseRowBinaryStream (String sql , Map <ClickHouseQueryParam , String > additionalDBParams , Map <String , String > additionalRequestParams ) throws SQLException {
274+ parseSingleStatement (sql , ClickHouseFormat .RowBinary );
275+ if (parsedStmt .isRecognized ()) {
276+ sql = parsedStmt .getSQL ();
277+ } else {
278+ sql = addFormatIfAbsent (sql , ClickHouseFormat .RowBinary );
279+ if (isSelect (sql )) {
280+ parsedStmt = new ClickHouseSqlStatement (sql , StatementType .SELECT );
281+ // httpContext.setAttribute("is_idempotent", Boolean.TRUE);
282+ } else {
283+ parsedStmt = new ClickHouseSqlStatement (sql , StatementType .UNKNOWN );
284+ }
285+ }
286+
209287 InputStream is = getInputStream (
210- addFormatIfAbsent ( sql , ClickHouseFormat . RowBinary ) ,
288+ sql ,
211289 additionalDBParams ,
212290 null ,
213291 additionalRequestParams
214292 );
215293 try {
216- if (isSelect ( sql )) {
294+ if (parsedStmt . isQuery ( )) {
217295 currentUpdateCount = -1 ;
218296 currentRowBinaryResult = new ClickHouseRowBinaryInputStream (properties .isCompress ()
219297 ? new ClickHouseLZ4Stream (is ) : is , getConnection ().getTimeZone (), properties );
@@ -231,6 +309,8 @@ public ClickHouseRowBinaryInputStream executeQueryClickhouseRowBinaryStream(Stri
231309
232310 @ Override
233311 public int executeUpdate (String sql ) throws SQLException {
312+ parseSingleStatement (sql , ClickHouseFormat .TabSeparatedWithNamesAndTypes );
313+
234314 InputStream is = null ;
235315 try {
236316 is = getInputStream (sql , null , null , null );
@@ -245,8 +325,7 @@ public int executeUpdate(String sql) throws SQLException {
245325 @ Override
246326 public boolean execute (String sql ) throws SQLException {
247327 // currentResult is stored here. InputString and currentResult will be closed on this.close()
248- executeQuery (sql );
249- return isSelect (sql );
328+ return executeQuery (sql ) != null ;
250329 }
251330
252331 @ Override
@@ -471,6 +550,7 @@ public ClickHouseResponseSummary getResponseSummary() {
471550 return currentSummary ;
472551 }
473552
553+ @ Deprecated
474554 static String clickhousifySql (String sql ) {
475555 return addFormatIfAbsent (sql , ClickHouseFormat .TabSeparatedWithNamesAndTypes );
476556 }
@@ -479,6 +559,7 @@ static String clickhousifySql(String sql) {
479559 * Adding FORMAT TabSeparatedWithNamesAndTypes if not added
480560 * adds format only to select queries
481561 */
562+ @ Deprecated
482563 private static String addFormatIfAbsent (final String sql , ClickHouseFormat format ) {
483564 String cleanSQL = sql .trim ();
484565 if (!isSelect (cleanSQL )) {
@@ -498,6 +579,7 @@ private static String addFormatIfAbsent(final String sql, ClickHouseFormat forma
498579 return sb .toString ();
499580 }
500581
582+ @ Deprecated
501583 static boolean isSelect (String sql ) {
502584 for (int i = 0 ; i < sql .length (); i ++) {
503585 String nextTwo = sql .substring (i , Math .min (i + 2 , sql .length ()));
@@ -518,6 +600,7 @@ static boolean isSelect(String sql) {
518600 return false ;
519601 }
520602
603+ @ Deprecated
521604 private String extractTableName (String sql ) {
522605 String s = extractDBAndTableName (sql );
523606 if (s .contains ("." )) {
@@ -527,6 +610,7 @@ private String extractTableName(String sql) {
527610 }
528611 }
529612
613+ @ Deprecated
530614 private String extractDBName (String sql ) {
531615 String s = extractDBAndTableName (sql );
532616 if (s .contains ("." )) {
@@ -536,6 +620,7 @@ private String extractDBName(String sql) {
536620 }
537621 }
538622
623+ @ Deprecated
539624 private String extractDBAndTableName (String sql ) {
540625 if (Utils .startsWithIgnoreCase (sql , "select" )) {
541626 String withoutStrings = Utils .retainUnquoted (sql , '\'' );
@@ -558,10 +643,11 @@ private String extractDBAndTableName(String sql) {
558643 return "system.unknown" ;
559644 }
560645
646+ @ Deprecated
561647 private boolean extractWithTotals (String sql ) {
562648 if (Utils .startsWithIgnoreCase (sql , "select" )) {
563649 String withoutStrings = Utils .retainUnquoted (sql , '\'' );
564- return withoutStrings .toLowerCase ().contains (" with totals" );
650+ return withoutStrings .toLowerCase (Locale . ROOT ).contains (" with totals" );
565651 }
566652 return false ;
567653 }
@@ -572,15 +658,23 @@ private InputStream getInputStream(
572658 List <ClickHouseExternalData > externalData ,
573659 Map <String , String > additionalRequestParams
574660 ) throws ClickHouseException {
575- sql = clickhousifySql (sql );
661+ boolean ignoreDatabase = false ;
662+ if (parsedStmt .isRecognized ()) {
663+ sql = parsedStmt .getSQL ();
664+ // TODO consider more scenarios like drop, show etc.
665+ ignoreDatabase = parsedStmt .getStatementType () == StatementType .CREATE
666+ && parsedStmt .containsKeyword (ClickHouseSqlStatement .KEYWORD_DATABASE );
667+ } else {
668+ sql = clickhousifySql (sql );
669+ ignoreDatabase = sql .trim ().regionMatches (true , 0 , databaseKeyword , 0 , databaseKeyword .length ());
670+ }
576671 log .debug ("Executing SQL: {}" , sql );
577672
578673 additionalClickHouseDBParams = addQueryIdTo (
579674 additionalClickHouseDBParams == null
580675 ? new EnumMap <ClickHouseQueryParam , String >(ClickHouseQueryParam .class )
581676 : additionalClickHouseDBParams );
582677
583- boolean ignoreDatabase = sql .trim ().regionMatches (true , 0 , databaseKeyword , 0 , databaseKeyword .length ());
584678 URI uri ;
585679 if (externalData == null || externalData .isEmpty ()) {
586680 uri = buildRequestUri (
0 commit comments