Skip to content

Commit 28eb4c0

Browse files
committed
Fix issue #39 and other fixes
Return false when the method isSearchable(int) is called on the metadata of a result set without table or schema name. Also, fix issue preventing to retrieve the metadata of an empty CassandraMetadataResultSet, and add null safety on some methods of CassandraResultSet and CassandraMetadataResultSet.
1 parent eeea9a7 commit 28eb4c0

15 files changed

+517
-205
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
3838
### Fixed
3939
- Fix `NullPointerException` issue [#38](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/38) when a null
4040
type name pattern is specified in a call to `CassandraDatabaseMetaData.getUDTs(String, String, String, int[])`.
41-
- Add null safety on some methods of `CassandraMetadataResultSet`.
41+
- Fix issue [#39](https://github.com/ing-bank/cassandra-jdbc-wrapper/issues/39): return `false` when the method
42+
`isSearchable(int)` is called on the metadata of a result set without table or schema name (typically on
43+
`CassandraMetadataResultSet`s).
44+
- Fix issue preventing to retrieve the metadata of an empty `CassandraMetadataResultSet`.
45+
- Add null safety on some methods of `CassandraResultSet` and `CassandraMetadataResultSet`.
4246

4347
## [4.10.2] - 2023-11-01
4448
### Fixed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
<lombok.version>1.18.30</lombok.version>
120120
<mockito.version>3.12.4</mockito.version>
121121
<slf4j.version>1.7.36</slf4j.version>
122-
<testcontainers.version>1.19.1</testcontainers.version>
122+
<testcontainers.version>1.19.2</testcontainers.version>
123123
<astra-sdk.version>0.6.11</astra-sdk.version>
124124
<!-- Versions for plugins -->
125125
<maven-checkstyle-plugin.version>3.3.0</maven-checkstyle-plugin.version>

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

+8-4
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ abstract class AbstractResultSet implements Wrapper {
5555
* @param columnIndex The column index (first column is 1).
5656
* @param type The data type to check.
5757
* @return {@code true} if the column CQL data type is the given one, {@code false} otherwise.
58+
* @throws SQLException when the CQL type cannot be determined for the given column.
5859
*/
59-
boolean isCqlType(final int columnIndex, @Nonnull final DataTypeEnum type) {
60+
boolean isCqlType(final int columnIndex, @Nonnull final DataTypeEnum type) throws SQLException {
6061
final String columnType = StringUtils.substringBefore(DataTypeEnum.cqlName(getCqlDataType(columnIndex)), "<");
6162
return type.cqlType.equalsIgnoreCase(columnType);
6263
}
@@ -67,8 +68,9 @@ boolean isCqlType(final int columnIndex, @Nonnull final DataTypeEnum type) {
6768
* @param columnLabel The column name.
6869
* @param type The data type to check.
6970
* @return {@code true} if the column CQL data type is the given one, {@code false} otherwise.
71+
* @throws SQLException when the CQL type cannot be determined for the given column.
7072
*/
71-
boolean isCqlType(final String columnLabel, @Nonnull final DataTypeEnum type) {
73+
boolean isCqlType(final String columnLabel, @Nonnull final DataTypeEnum type) throws SQLException {
7274
final String columnType = StringUtils.substringBefore(DataTypeEnum.cqlName(getCqlDataType(columnLabel)), "<");
7375
return type.cqlType.equalsIgnoreCase(columnType);
7476
}
@@ -78,16 +80,18 @@ boolean isCqlType(final String columnLabel, @Nonnull final DataTypeEnum type) {
7880
*
7981
* @param columnIndex The column index (first column is 1).
8082
* @return The CQL data type of the column.
83+
* @throws SQLException when the CQL type cannot be determined for the given column.
8184
*/
82-
abstract DataType getCqlDataType(int columnIndex);
85+
abstract DataType getCqlDataType(int columnIndex) throws SQLException;
8386

8487
/**
8588
* Gets the CQL type of the column with the given name.
8689
*
8790
* @param columnLabel The column name.
8891
* @return The CQL data type of the column.
92+
* @throws SQLException when the CQL type cannot be determined for the given column.
8993
*/
90-
abstract DataType getCqlDataType(String columnLabel);
94+
abstract DataType getCqlDataType(String columnLabel) throws SQLException;
9195

9296
public boolean absolute(final int row) throws SQLException {
9397
throw new SQLFeatureNotSupportedException(NOT_SUPPORTED);

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

+32-13
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,25 @@ private void populateColumns() {
196196
}
197197

198198
@Override
199-
DataType getCqlDataType(final int columnIndex) {
200-
return this.currentRow.getColumnDefinitions().getType(columnIndex - 1);
199+
DataType getCqlDataType(final int columnIndex) throws SQLException {
200+
if (this.currentRow != null && this.currentRow.getColumnDefinitions() != null) {
201+
return this.currentRow.getColumnDefinitions().getType(columnIndex - 1);
202+
}
203+
if (this.driverResultSet != null && this.driverResultSet.getColumnDefinitions() != null) {
204+
return this.driverResultSet.getColumnDefinitions().getType(columnIndex - 1);
205+
}
206+
throw new SQLException(UNABLE_TO_RETRIEVE_METADATA);
201207
}
202208

203209
@Override
204-
DataType getCqlDataType(final String columnLabel) {
205-
return this.currentRow.getColumnDefinitions().getType(columnLabel);
210+
DataType getCqlDataType(final String columnLabel) throws SQLException {
211+
if (this.currentRow != null && this.currentRow.getColumnDefinitions() != null) {
212+
return this.currentRow.getColumnDefinitions().getType(columnLabel);
213+
}
214+
if (this.driverResultSet != null && this.driverResultSet.getColumnDefinitions() != null) {
215+
return this.driverResultSet.getColumnDefinitions().getType(columnLabel);
216+
}
217+
throw new SQLException(UNABLE_TO_RETRIEVE_METADATA);
206218
}
207219

208220
@Override
@@ -1071,7 +1083,7 @@ public String getColumnLabel(final int column) throws SQLException {
10711083

10721084
@Override
10731085
public String getColumnName(final int column) throws SQLException {
1074-
if (currentRow != null) {
1086+
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
10751087
return currentRow.getColumnDefinitions().getName(column - 1);
10761088
}
10771089
if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
@@ -1085,7 +1097,7 @@ public int getColumnDisplaySize(final int column) {
10851097
try {
10861098
final AbstractJdbcType<?> jdbcEquivalentType;
10871099
final ColumnDefinitions.Definition columnDefinition;
1088-
if (currentRow != null) {
1100+
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
10891101
columnDefinition = currentRow.getColumnDefinitions().asList().get(column - 1);
10901102
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
10911103
columnDefinition = driverResultSet.getColumnDefinitions().asList().get(column - 1);
@@ -1105,7 +1117,7 @@ public int getColumnDisplaySize(final int column) {
11051117
}
11061118

11071119
@Override
1108-
public int getColumnType(final int column) {
1120+
public int getColumnType(final int column) throws SQLException {
11091121
final DataType type;
11101122
if (currentRow != null) {
11111123
type = getCqlDataType(column);
@@ -1118,7 +1130,7 @@ public int getColumnType(final int column) {
11181130
}
11191131

11201132
@Override
1121-
public String getColumnTypeName(final int column) {
1133+
public String getColumnTypeName(final int column) throws SQLException {
11221134
// Specification says "database specific type name"; for Cassandra this means the AbstractType.
11231135
final DataType type;
11241136
if (currentRow != null) {
@@ -1141,7 +1153,7 @@ public int getScale(final int column) {
11411153
try {
11421154
final AbstractJdbcType<?> jdbcEquivalentType;
11431155
final ColumnDefinitions.Definition columnDefinition;
1144-
if (currentRow != null) {
1156+
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
11451157
columnDefinition = currentRow.getColumnDefinitions().asList().get(column - 1);
11461158
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
11471159
columnDefinition = driverResultSet.getColumnDefinitions().asList().get(column - 1);
@@ -1171,7 +1183,7 @@ public String getSchemaName(final int column) throws SQLException {
11711183
@Override
11721184
public String getTableName(final int column) {
11731185
final String tableName;
1174-
if (currentRow != null) {
1186+
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
11751187
tableName = currentRow.getColumnDefinitions().getTable(column - 1);
11761188
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
11771189
tableName = driverResultSet.getColumnDefinitions().getTable(column - 1);
@@ -1231,9 +1243,16 @@ public boolean isSearchable(final int column) throws SQLException {
12311243
return false;
12321244
}
12331245
final String columnName = getColumnName(column);
1246+
final String schemaName = getSchemaName(column);
1247+
final String tableName = getTableName(column);
1248+
// If the schema or table name is not defined, always returns false since we cannot determine if the column
1249+
// is searchable in this context.
1250+
if (StringUtils.isEmpty(schemaName) || StringUtils.isEmpty(tableName)) {
1251+
return false;
1252+
}
12341253
final AtomicBoolean searchable = new AtomicBoolean(false);
1235-
statement.connection.getSession().getMetadata().getKeyspace(getSchemaName(column))
1236-
.flatMap(metadata -> metadata.getTable(getTableName(column)))
1254+
statement.connection.getSession().getMetadata().getKeyspace(schemaName)
1255+
.flatMap(metadata -> metadata.getTable(tableName))
12371256
.ifPresent(tableMetadata -> {
12381257
boolean result;
12391258
// Check first if the column is a clustering column or in a partitioning key.
@@ -1250,7 +1269,7 @@ public boolean isSearchable(final int column) throws SQLException {
12501269
}
12511270

12521271
@Override
1253-
public boolean isSigned(final int column) {
1272+
public boolean isSigned(final int column) throws SQLException {
12541273
final DataType type;
12551274
if (currentRow != null) {
12561275
type = getCqlDataType(column);

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED;
101101
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE;
102102
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.UNABLE_TO_READ_VALUE;
103+
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.UNABLE_TO_RETRIEVE_METADATA;
103104
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.UNSUPPORTED_JSON_TYPE_CONVERSION;
104105
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.UNSUPPORTED_TYPE_CONVERSION;
105106
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.VALID_LABELS;
@@ -270,12 +271,18 @@ private void populateColumns() {
270271

271272
@Override
272273
DataType getCqlDataType(final int columnIndex) {
273-
return this.currentRow.getColumnDefinitions().get(columnIndex - 1).getType();
274+
if (this.currentRow != null) {
275+
return this.currentRow.getColumnDefinitions().get(columnIndex - 1).getType();
276+
}
277+
return this.driverResultSet.getColumnDefinitions().get(columnIndex - 1).getType();
274278
}
275279

276280
@Override
277281
DataType getCqlDataType(final String columnLabel) {
278-
return this.currentRow.getColumnDefinitions().get(columnLabel).getType();
282+
if (this.currentRow != null) {
283+
return this.currentRow.getColumnDefinitions().get(columnLabel).getType();
284+
}
285+
return this.driverResultSet.getColumnDefinitions().get(columnLabel).getType();
279286
}
280287

281288
@Override
@@ -1780,9 +1787,16 @@ public boolean isSearchable(final int column) throws SQLException {
17801787
return false;
17811788
}
17821789
final String columnName = getColumnName(column);
1790+
final String schemaName = getSchemaName(column);
1791+
final String tableName = getTableName(column);
1792+
// If the schema or table name is not defined (this should not happen here, but better to be careful),
1793+
// always returns false since we cannot determine if the column is searchable in this context.
1794+
if (StringUtils.isEmpty(schemaName) || StringUtils.isEmpty(tableName)) {
1795+
return false;
1796+
}
17831797
final AtomicBoolean searchable = new AtomicBoolean(false);
1784-
statement.connection.getSession().getMetadata().getKeyspace(getSchemaName(column))
1785-
.flatMap(metadata -> metadata.getTable(getTableName(column)))
1798+
statement.connection.getSession().getMetadata().getKeyspace(schemaName)
1799+
.flatMap(metadata -> metadata.getTable(tableName))
17861800
.ifPresent(tableMetadata -> {
17871801
boolean result;
17881802
// Check first if the column is a clustering column or in a partitioning key.

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

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.ing.data.cassandra.jdbc;
1717

1818
import com.datastax.oss.driver.api.core.type.DataType;
19+
import org.apache.commons.lang3.StringUtils;
1920

2021
import javax.annotation.Nonnull;
2122
import java.util.Arrays;
@@ -310,6 +311,17 @@ public Definition(final String keyspace, final String table, final String name,
310311
this.type = type;
311312
}
312313

314+
/**
315+
* Builds a column definition in an anonymous table (useful for metadata result sets built programmatically).
316+
*
317+
* @param name The column name.
318+
* @param type The column type.
319+
* @return A new column definition instance.
320+
*/
321+
public static Definition buildDefinitionInAnonymousTable(final String name, final DataType type) {
322+
return new Definition(StringUtils.EMPTY, StringUtils.EMPTY, name, type);
323+
}
324+
313325
/**
314326
* Gets the name of the keyspace this column is part of.
315327
*

src/main/java/com/ing/data/cassandra/jdbc/metadata/CatalogMetadataResultSetBuilder.java

+11-4
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515

1616
package com.ing.data.cassandra.jdbc.metadata;
1717

18+
import com.datastax.oss.driver.api.core.type.DataTypes;
1819
import com.ing.data.cassandra.jdbc.CassandraMetadataResultSet;
1920
import com.ing.data.cassandra.jdbc.CassandraStatement;
2021

2122
import java.sql.DatabaseMetaData;
2223
import java.sql.SQLException;
2324
import java.util.ArrayList;
2425

26+
import static com.ing.data.cassandra.jdbc.ColumnDefinitions.Definition.buildDefinitionInAnonymousTable;
27+
2528
/**
2629
* Utility class building metadata result sets ({@link CassandraMetadataResultSet} objects) related to catalogs.
2730
*/
@@ -52,10 +55,14 @@ public CatalogMetadataResultSetBuilder(final CassandraStatement statement) throw
5255
*/
5356
public CassandraMetadataResultSet buildCatalogs() throws SQLException {
5457
final ArrayList<MetadataRow> catalogs = new ArrayList<>();
55-
final MetadataRow row = new MetadataRow().addEntry(TABLE_CATALOG_SHORTNAME,
56-
this.statement.getConnection().getCatalog());
57-
catalogs.add(row);
58-
return CassandraMetadataResultSet.buildFrom(this.statement, new MetadataResultSet().setRows(catalogs));
58+
final MetadataRow.MetadataRowTemplate rowTemplate = new MetadataRow.MetadataRowTemplate(
59+
buildDefinitionInAnonymousTable(TABLE_CATALOG_SHORTNAME, DataTypes.TEXT)
60+
);
61+
62+
catalogs.add(new MetadataRow().withTemplate(rowTemplate, this.statement.getConnection().getCatalog()));
63+
64+
return CassandraMetadataResultSet.buildFrom(this.statement,
65+
new MetadataResultSet(rowTemplate).setRows(catalogs));
5966
}
6067

6168
}

0 commit comments

Comments
 (0)