Skip to content

Commit b66035e

Browse files
authored
Fix issue #25 (#26)
- fix result sets and statements closing. - introduce a new behaviour in Liquibase compliance mode to run multiple queries in the same statement synchronously (by default, they are executed asynchronously). - return the schema name instead of null when the method CassandraConnection.getCatalog() is called in Liquibase compliance mode. - does not throw SQLFeatureNotSupportedException when CassandraConnection.rollback() is called in Liquibase compliance mode.
1 parent 2202725 commit b66035e

File tree

12 files changed

+169
-76
lines changed

12 files changed

+169
-76
lines changed

.gitignore

+9-9
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,16 @@ local.properties
9999

100100
# Gradle and Maven with auto-import
101101
# When using Gradle or Maven with auto-import, you should exclude module files,
102-
# since they will be recreated, and may cause churn. Uncomment if using
102+
# since they will be recreated, and may cause churn. Uncomment if using
103103
# auto-import.
104-
# .idea/artifacts
105-
# .idea/compiler.xml
106-
# .idea/jarRepositories.xml
107-
# .idea/modules.xml
108-
# .idea/*.iml
109-
# .idea/modules
110-
# *.iml
111-
# *.ipr
104+
.idea/artifacts
105+
.idea/compiler.xml
106+
.idea/jarRepositories.xml
107+
.idea/modules.xml
108+
.idea/*.iml
109+
.idea/modules
110+
*.iml
111+
*.ipr
112112

113113
# CMake
114114
cmake-build-*/

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to
55
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [4.9.1] - 2023-09-03
8+
### Fixed
9+
- Fix issue [#25](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/25) causing failure when running with
10+
Liquibase. The fix includes several changes:
11+
- fixes result sets and statements closing.
12+
- introduces a new behaviour in Liquibase compliance mode to run multiple queries in the same statement synchronously
13+
(by default, they are executed asynchronously).
14+
- returns the schema name instead of `null` when the method `CassandraConnection.getCatalog()` is called in Liquibase
15+
compliance mode.
16+
- does not throw `SQLFeatureNotSupportedException` when `CassandraConnection.rollback()` is called in Liquibase
17+
compliance mode.
18+
719
## [4.9.0] - 2023-04-15
820
### Added
921
- Add non-JDBC standard [JSON support](https://cassandra.apache.org/doc/latest/cassandra/cql/json.html) with the
@@ -121,6 +133,7 @@ For this version, the changelog lists the main changes comparatively to the late
121133
- Fix logs in `CassandraConnection` constructor.
122134

123135
[original project]: https://github.com/adejanovski/cassandra-jdbc-wrapper/
136+
[4.9.1]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.9.0...v4.9.1
124137
[4.9.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.8.0...v4.9.0
125138
[4.8.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.7.0...v4.8.0
126139
[4.7.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.6.0...v4.7.0

README.md

+14-5
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ For further information about connecting to DBaaS, see [cloud documentation](htt
284284
### Compliance modes
285285

286286
For some specific usages, the default behaviour of some JDBC implementations has to be modified. That's why you can
287-
use the argument `compliancemode` in the JDBC URL to cutomize the behaviour of some methods.
287+
use the argument `compliancemode` in the JDBC URL to customize the behaviour of some methods.
288288

289289
The values currently allowed for this argument are:
290290
* `Default`: mode activated by default if not specified in the JDBC URL. It implements the methods detailed below as
@@ -293,10 +293,19 @@ The values currently allowed for this argument are:
293293

294294
Here are the behaviours defined by the compliance modes listed above:
295295

296-
| Method | Default mode | Liquibase mode |
297-
|--------------------------------------------|---------------------------------------------------------------------------------------------------|----------------|
298-
| `CassandraConnection.getCatalog()` | returns the result of the query`SELECT cluster_name FROM system.local` or `null` if not available | returns `null` |
299-
| `CassandraStatement.executeUpdate(String)` | returns 0 | returns -1 |
296+
| Method | Default mode | Liquibase mode |
297+
|--------------------------------------------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------|
298+
| `CassandraConnection.getCatalog()` | returns the result of the query`SELECT cluster_name FROM system.local` or `null` if not available | returns the schema name if available, `null` otherwise |
299+
| `CassandraConnection.rollback()` | throws a `SQLFeatureNotSupportedException` | do nothing more after checking connection is open |
300+
| `CassandraStatement.executeUpdate(String)` | returns 0 | returns -1 |
301+
302+
For the following methods: `CassandraStatement.execute(String)`, `CassandraStatement.executeQuery(String)` and
303+
`CassandraStatement.executeUpdate(String)`, if the CQL statement includes several queries (separated by semicolons),
304+
the behaviour is the following:
305+
306+
| Default mode | Liquibase mode |
307+
|-------------------------------------|------------------------------------|
308+
| executes the queries asynchronously | executes the queries synchronously |
300309

301310
### Using simple statements
302311

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.ing.data</groupId>
77
<artifactId>cassandra-jdbc-wrapper</artifactId>
8-
<version>4.9.0</version>
8+
<version>4.9.1</version>
99
<packaging>jar</packaging>
1010

1111
<name>Cassandra JDBC Wrapper</name>

src/main/java/com/ing/data/cassandra/jdbc/CassandraConnection.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,9 @@ public CassandraPreparedStatement prepareStatement(final String cql, final int r
547547
@Override
548548
public void rollback() throws SQLException {
549549
checkNotClosed();
550-
throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT);
550+
if (this.optionSet.shouldThrowExceptionOnRollback()) {
551+
throw new SQLFeatureNotSupportedException(ALWAYS_AUTOCOMMIT);
552+
}
551553
}
552554

553555
/**

src/main/java/com/ing/data/cassandra/jdbc/CassandraMetadataResultSet.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas
128128
private int fetchDirection;
129129
private int fetchSize;
130130
private boolean wasNull;
131+
private boolean isClosed;
131132
// Result set from the Cassandra driver.
132133
private MetadataResultSet driverResultSet;
133134

@@ -137,6 +138,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas
137138
CassandraMetadataResultSet() {
138139
this.metadata = new CResultSetMetaData();
139140
this.statement = null;
141+
this.isClosed = false;
140142
}
141143

142144
/**
@@ -156,6 +158,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas
156158
this.fetchSize = statement.getFetchSize();
157159
this.driverResultSet = metadataResultSet;
158160
this.rowsIterator = metadataResultSet.iterator();
161+
this.isClosed = false;
159162

160163
// Initialize the column values from the first row.
161164
// Note that the first call to next() will harmlessly re-write these values for the columns. The row cursor
@@ -250,7 +253,7 @@ public void clearWarnings() throws SQLException {
250253
@Override
251254
public void close() throws SQLException {
252255
if (!isClosed()) {
253-
this.statement.close();
256+
this.isClosed = true;
254257
}
255258
}
256259

@@ -961,10 +964,7 @@ public boolean isBeforeFirst() throws SQLException {
961964

962965
@Override
963966
public boolean isClosed() {
964-
if (this.statement == null) {
965-
return true;
966-
}
967-
return this.statement.isClosed();
967+
return this.isClosed;
968968
}
969969

970970
@Override

src/main/java/com/ing/data/cassandra/jdbc/CassandraResultSet.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public class CassandraResultSet extends AbstractResultSet
176176
private int fetchDirection;
177177
private int fetchSize;
178178
private boolean wasNull;
179+
private boolean isClosed;
179180
// Result set from the Cassandra driver.
180181
private ResultSet driverResultSet;
181182

@@ -185,6 +186,7 @@ public class CassandraResultSet extends AbstractResultSet
185186
CassandraResultSet() {
186187
this.metadata = new CResultSetMetaData();
187188
this.statement = null;
189+
this.isClosed = false;
188190
}
189191

190192
/**
@@ -203,6 +205,7 @@ public class CassandraResultSet extends AbstractResultSet
203205
this.fetchSize = statement.getFetchSize();
204206
this.driverResultSet = resultSet;
205207
this.rowsIterator = resultSet.iterator();
208+
this.isClosed = false;
206209

207210
// Initialize the column values from the first row.
208211
if (hasMoreRows()) {
@@ -225,6 +228,7 @@ public class CassandraResultSet extends AbstractResultSet
225228
this.resultSetType = statement.getResultSetType();
226229
this.fetchDirection = statement.getFetchDirection();
227230
this.fetchSize = statement.getFetchSize();
231+
this.isClosed = false;
228232

229233
// We have several result sets, but we will use only the first one for metadata needs.
230234
this.driverResultSet = resultSets.get(0);
@@ -324,7 +328,7 @@ public void clearWarnings() throws SQLException {
324328
@Override
325329
public void close() throws SQLException {
326330
if (!isClosed()) {
327-
this.statement.close();
331+
this.isClosed = true;
328332
}
329333
}
330334

@@ -1366,10 +1370,7 @@ public boolean isBeforeFirst() throws SQLException {
13661370

13671371
@Override
13681372
public boolean isClosed() {
1369-
if (this.statement == null) {
1370-
return true;
1371-
}
1372-
return this.statement.isClosed();
1373+
return this.isClosed;
13731374
}
13741375

13751376
@Override

src/main/java/com/ing/data/cassandra/jdbc/CassandraStatement.java

+62-47
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public class CassandraStatement extends AbstractStatement
137137
* The consistency level used for the statement.
138138
*/
139139
protected ConsistencyLevel consistencyLevel;
140-
140+
private boolean isClosed;
141141
private DriverExecutionProfile customTimeoutProfile;
142142

143143
/**
@@ -210,6 +210,7 @@ public class CassandraStatement extends AbstractStatement
210210
this.cql = cql;
211211
this.batchQueries = new ArrayList<>();
212212
this.consistencyLevel = connection.getDefaultConsistencyLevel();
213+
this.isClosed = false;
213214

214215
if (!(resultSetType == ResultSet.TYPE_FORWARD_ONLY
215216
|| resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE
@@ -264,7 +265,7 @@ public void clearWarnings() throws SQLException {
264265

265266
@Override
266267
public void close() {
267-
this.connection = null;
268+
this.isClosed = true;
268269
this.cql = null;
269270
}
270271

@@ -284,67 +285,68 @@ private void doExecute(final String cql) throws SQLException {
284285

285286
try {
286287
final String[] cqlQueries = cql.split(STATEMENTS_SEPARATOR_REGEX);
287-
if (cqlQueries.length > 1 && !(cql.trim().toLowerCase().startsWith("begin")
288+
if (cqlQueries.length > 1
289+
&& !(cql.trim().toLowerCase().startsWith("begin")
288290
&& cql.toLowerCase().contains("batch") && cql.toLowerCase().contains("apply"))) {
289-
// Several statements in the query to execute asynchronously...
290-
291291
final ArrayList<com.datastax.oss.driver.api.core.cql.ResultSet> results = new ArrayList<>();
292+
293+
// Several statements in the query to execute asynchronously...
292294
if (cqlQueries.length > MAX_ASYNC_QUERIES * 1.1) {
293295
// Protect the cluster from receiving too many queries at once and force the dev to split the load
294296
throw new SQLNonTransientException("Too many queries at once (" + cqlQueries.length
295297
+ "). You must split your queries into more batches !");
296298
}
297299

298-
StringBuilder prevCqlQuery = new StringBuilder();
299-
for (final String cqlQuery : cqlQueries) {
300-
if ((cqlQuery.contains("'") && ((StringUtils.countMatches(cqlQuery, "'") % 2 == 1
301-
&& prevCqlQuery.length() == 0)
302-
|| (StringUtils.countMatches(cqlQuery, "'") % 2 == 0 && prevCqlQuery.length() > 0)))
303-
|| (prevCqlQuery.toString().length() > 0 && !cqlQuery.contains("'"))) {
304-
prevCqlQuery.append(cqlQuery).append(";");
305-
} else {
306-
prevCqlQuery.append(cqlQuery);
307-
if (LOG.isTraceEnabled() || this.connection.isDebugMode()) {
308-
LOG.debug("CQL: {}", prevCqlQuery);
309-
}
310-
SimpleStatement stmt = SimpleStatement.newInstance(prevCqlQuery.toString())
311-
.setConsistencyLevel(this.connection.getDefaultConsistencyLevel())
312-
.setPageSize(this.fetchSize);
313-
if (this.customTimeoutProfile != null) {
314-
stmt = stmt.setExecutionProfile(this.customTimeoutProfile);
300+
// If we should not execute the queries asynchronously, for example if they must be executed in the
301+
// specified order (e.g. in Liquibase scripts with queries such as CREATE TABLE t, then
302+
// INSERT INTO t ...).
303+
if (!this.connection.getOptionSet().executeMultipleQueriesByStatementAsync()) {
304+
for (final String cqlQuery : cqlQueries) {
305+
final com.datastax.oss.driver.api.core.cql.ResultSet rs = executeSingleStatement(cqlQuery);
306+
results.add(rs);
307+
}
308+
} else {
309+
StringBuilder prevCqlQuery = new StringBuilder();
310+
for (final String cqlQuery : cqlQueries) {
311+
if ((cqlQuery.contains("'") && ((StringUtils.countMatches(cqlQuery, "'") % 2 == 1
312+
&& prevCqlQuery.length() == 0)
313+
|| (StringUtils.countMatches(cqlQuery, "'") % 2 == 0 && prevCqlQuery.length() > 0)))
314+
|| (!prevCqlQuery.toString().isEmpty() && !cqlQuery.contains("'"))) {
315+
prevCqlQuery.append(cqlQuery).append(";");
316+
} else {
317+
prevCqlQuery.append(cqlQuery);
318+
if (LOG.isTraceEnabled() || this.connection.isDebugMode()) {
319+
LOG.debug("CQL: {}", prevCqlQuery);
320+
}
321+
SimpleStatement stmt = SimpleStatement.newInstance(prevCqlQuery.toString())
322+
.setConsistencyLevel(this.connection.getDefaultConsistencyLevel())
323+
.setPageSize(this.fetchSize);
324+
if (this.customTimeoutProfile != null) {
325+
stmt = stmt.setExecutionProfile(this.customTimeoutProfile);
326+
}
327+
final CompletionStage<AsyncResultSet> resultSetFuture =
328+
((CqlSession) this.connection.getSession()).executeAsync(stmt);
329+
futures.add(resultSetFuture);
330+
prevCqlQuery = new StringBuilder();
315331
}
316-
final CompletionStage<AsyncResultSet> resultSetFuture =
317-
((CqlSession) this.connection.getSession()).executeAsync(stmt);
318-
futures.add(resultSetFuture);
319-
prevCqlQuery = new StringBuilder();
320332
}
321-
}
322333

323-
for (final CompletionStage<AsyncResultSet> future : futures) {
324-
final AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(future);
325-
final com.datastax.oss.driver.api.core.cql.ResultSet rows;
326-
if (asyncResultSet.hasMorePages()) {
327-
rows = new MultiPageResultSet(asyncResultSet);
328-
} else {
329-
rows = new SinglePageResultSet(asyncResultSet);
334+
for (final CompletionStage<AsyncResultSet> future : futures) {
335+
final AsyncResultSet asyncResultSet = CompletableFutures.getUninterruptibly(future);
336+
final com.datastax.oss.driver.api.core.cql.ResultSet rows;
337+
if (asyncResultSet.hasMorePages()) {
338+
rows = new MultiPageResultSet(asyncResultSet);
339+
} else {
340+
rows = new SinglePageResultSet(asyncResultSet);
341+
}
342+
results.add(rows);
330343
}
331-
results.add(rows);
332344
}
333345

334346
this.currentResultSet = new CassandraResultSet(this, results);
335347
} else {
336348
// Only one statement to execute, so do it synchronously.
337-
if (LOG.isTraceEnabled() || this.connection.isDebugMode()) {
338-
LOG.debug("CQL: " + cql);
339-
}
340-
SimpleStatement stmt = SimpleStatement.newInstance(cql)
341-
.setConsistencyLevel(this.connection.getDefaultConsistencyLevel())
342-
.setPageSize(this.fetchSize);
343-
if (this.customTimeoutProfile != null) {
344-
stmt = stmt.setExecutionProfile(this.customTimeoutProfile);
345-
}
346-
this.currentResultSet = new CassandraResultSet(this,
347-
((CqlSession) this.connection.getSession()).execute(stmt));
349+
this.currentResultSet = new CassandraResultSet(this, executeSingleStatement(cql));
348350
}
349351
} catch (final Exception e) {
350352
for (final CompletionStage<AsyncResultSet> future : futures) {
@@ -354,6 +356,19 @@ private void doExecute(final String cql) throws SQLException {
354356
}
355357
}
356358

359+
private com.datastax.oss.driver.api.core.cql.ResultSet executeSingleStatement(final String cql) {
360+
if (LOG.isTraceEnabled() || this.connection.isDebugMode()) {
361+
LOG.debug("CQL: " + cql);
362+
}
363+
SimpleStatement stmt = SimpleStatement.newInstance(cql)
364+
.setConsistencyLevel(this.connection.getDefaultConsistencyLevel())
365+
.setPageSize(this.fetchSize);
366+
if (this.customTimeoutProfile != null) {
367+
stmt = stmt.setExecutionProfile(this.customTimeoutProfile);
368+
}
369+
return ((CqlSession) this.connection.getSession()).execute(stmt);
370+
}
371+
357372
@Override
358373
public boolean execute(final String query) throws SQLException {
359374
checkNotClosed();
@@ -647,7 +662,7 @@ public SQLWarning getWarnings() throws SQLException {
647662

648663
@Override
649664
public boolean isClosed() {
650-
return this.connection == null;
665+
return this.isClosed;
651666
}
652667

653668
/**

src/main/java/com/ing/data/cassandra/jdbc/optionset/Default.java

+9
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,13 @@ public int getSQLUpdateResponse() {
5050
return 0;
5151
}
5252

53+
@Override
54+
public boolean shouldThrowExceptionOnRollback() {
55+
return true;
56+
}
57+
58+
@Override
59+
public boolean executeMultipleQueriesByStatementAsync() {
60+
return true;
61+
}
5362
}

0 commit comments

Comments
 (0)