Skip to content

Commit 7a1b1b8

Browse files
committed
Fix issue #25
- 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 5f0c831 commit 7a1b1b8

File tree

12 files changed

+164
-71
lines changed

12 files changed

+164
-71
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ 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.
104104
.idea/artifacts
105105
.idea/compiler.xml

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1515
- Update Jackson dependencies to version 2.15.2.
1616
- Packages refactoring: utility classes, types and database metadata management have been moved to dedicated packages.
1717

18+
## [4.9.1] - 2023-09-03
19+
### Fixed
20+
- Fix issue [#25](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/25) causing failure when running with
21+
Liquibase. The fix includes several changes:
22+
- fixes result sets and statements closing.
23+
- introduces a new behaviour in Liquibase compliance mode to run multiple queries in the same statement synchronously
24+
(by default, they are executed asynchronously).
25+
- returns the schema name instead of `null` when the method `CassandraConnection.getCatalog()` is called in Liquibase
26+
compliance mode.
27+
- does not throw `SQLFeatureNotSupportedException` when `CassandraConnection.rollback()` is called in Liquibase
28+
compliance mode.
29+
1830
## [4.9.0] - 2023-04-15
1931
### Added
2032
- Add non-JDBC standard [JSON support](https://cassandra.apache.org/doc/latest/cassandra/cql/json.html) with the
@@ -132,6 +144,7 @@ For this version, the changelog lists the main changes comparatively to the late
132144
- Fix logs in `CassandraConnection` constructor.
133145

134146
[original project]: https://github.com/adejanovski/cassandra-jdbc-wrapper/
147+
[4.9.1]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.9.0...v4.9.1
135148
[4.9.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.8.0...v4.9.0
136149
[4.8.0]: https://github.com/ing-bank/cassandra-jdbc-wrapper/compare/v4.7.0...v4.8.0
137150
[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

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
44
<modelVersion>4.0.0</modelVersion>
55

6-
<groupId>com.ing.data</groupId>
7-
<artifactId>cassandra-jdbc-wrapper</artifactId>
8-
<version>4.9.0</version>
9-
<packaging>jar</packaging>
6+
<groupId>com.ing.data</groupId>
7+
<artifactId>cassandra-jdbc-wrapper</artifactId>
8+
<version>4.9.1</version>
9+
<packaging>jar</packaging>
1010

1111
<name>Cassandra JDBC Wrapper</name>
1212
<description>JDBC wrapper of the DataStax Java Driver for Apache Cassandra.</description>

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
@@ -135,6 +135,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas
135135
private int fetchDirection;
136136
private int fetchSize;
137137
private boolean wasNull;
138+
private boolean isClosed;
138139
// Result set from the Cassandra driver.
139140
private MetadataResultSet driverResultSet;
140141

@@ -144,6 +145,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas
144145
CassandraMetadataResultSet() {
145146
this.metadata = new CResultSetMetaData();
146147
this.statement = null;
148+
this.isClosed = false;
147149
}
148150

149151
/**
@@ -163,6 +165,7 @@ public class CassandraMetadataResultSet extends AbstractResultSet implements Cas
163165
this.fetchSize = statement.getFetchSize();
164166
this.driverResultSet = metadataResultSet;
165167
this.rowsIterator = metadataResultSet.iterator();
168+
this.isClosed = false;
166169

167170
// Initialize the column values from the first row.
168171
// Note that the first call to next() will harmlessly re-write these values for the columns. The row cursor
@@ -271,7 +274,7 @@ public void clearWarnings() throws SQLException {
271274
@Override
272275
public void close() throws SQLException {
273276
if (!isClosed()) {
274-
this.statement.close();
277+
this.isClosed = true;
275278
}
276279
}
277280

@@ -992,10 +995,7 @@ public boolean isBeforeFirst() throws SQLException {
992995

993996
@Override
994997
public boolean isClosed() {
995-
if (this.statement == null) {
996-
return true;
997-
}
998-
return this.statement.isClosed();
998+
return this.isClosed;
999999
}
10001000

10011001
@Override

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ public class CassandraResultSet extends AbstractResultSet
184184
private int fetchDirection;
185185
private int fetchSize;
186186
private boolean wasNull;
187+
private boolean isClosed;
187188
// Result set from the Cassandra driver.
188189
private ResultSet driverResultSet;
189190

@@ -193,6 +194,7 @@ public class CassandraResultSet extends AbstractResultSet
193194
CassandraResultSet() {
194195
this.metadata = new CResultSetMetaData();
195196
this.statement = null;
197+
this.isClosed = false;
196198
}
197199

198200
/**
@@ -211,6 +213,7 @@ public class CassandraResultSet extends AbstractResultSet
211213
this.fetchSize = statement.getFetchSize();
212214
this.driverResultSet = resultSet;
213215
this.rowsIterator = resultSet.iterator();
216+
this.isClosed = false;
214217

215218
// Initialize the column values from the first row.
216219
if (hasMoreRows()) {
@@ -233,6 +236,7 @@ public class CassandraResultSet extends AbstractResultSet
233236
this.resultSetType = statement.getResultSetType();
234237
this.fetchDirection = statement.getFetchDirection();
235238
this.fetchSize = statement.getFetchSize();
239+
this.isClosed = false;
236240

237241
// We have several result sets, but we will use only the first one for metadata needs.
238242
this.driverResultSet = resultSets.get(0);
@@ -332,7 +336,7 @@ public void clearWarnings() throws SQLException {
332336
@Override
333337
public void close() throws SQLException {
334338
if (!isClosed()) {
335-
this.statement.close();
339+
this.isClosed = true;
336340
}
337341
}
338342

@@ -1422,10 +1426,7 @@ public boolean isBeforeFirst() throws SQLException {
14221426

14231427
@Override
14241428
public boolean isClosed() {
1425-
if (this.statement == null) {
1426-
return true;
1427-
}
1428-
return this.statement.isClosed();
1429+
return this.isClosed;
14291430
}
14301431

14311432
@Override

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

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

144144
/**
@@ -211,6 +211,7 @@ public class CassandraStatement extends AbstractStatement
211211
this.cql = cql;
212212
this.batchQueries = new ArrayList<>();
213213
this.consistencyLevel = connection.getDefaultConsistencyLevel();
214+
this.isClosed = false;
214215

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

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

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

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

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

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

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

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

659674
@Override
660675
public boolean isClosed() {
661-
return this.connection == null;
676+
return this.isClosed;
662677
}
663678

664679
/**

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)