com.ing.data.cassandra.jdbc.CassandraMetadataResultSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-jdbc-wrapper Show documentation
Show all versions of cassandra-jdbc-wrapper Show documentation
JDBC wrapper of the Java Driver for Apache Cassandra®.
The newest version!
/*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ing.data.cassandra.jdbc;
import com.datastax.oss.driver.api.core.data.CqlDuration;
import com.datastax.oss.driver.api.core.data.CqlVector;
import com.datastax.oss.driver.api.core.type.DataType;
import com.datastax.oss.driver.api.core.type.ListType;
import com.datastax.oss.driver.api.core.type.MapType;
import com.datastax.oss.driver.api.core.type.SetType;
import com.ing.data.cassandra.jdbc.metadata.MetadataResultSet;
import com.ing.data.cassandra.jdbc.metadata.MetadataRow;
import com.ing.data.cassandra.jdbc.types.AbstractJdbcType;
import com.ing.data.cassandra.jdbc.types.DataTypeEnum;
import com.ing.data.cassandra.jdbc.types.TypesMap;
import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.sql.Blob;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLNonTransientException;
import java.sql.SQLRecoverableException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_PRECISION;
import static com.ing.data.cassandra.jdbc.types.AbstractJdbcType.DEFAULT_SCALE;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_DIR;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.BAD_FETCH_SIZE;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.FORWARD_ONLY;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.ILLEGAL_FETCH_DIRECTION_FOR_FORWARD_ONLY;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.MALFORMED_URL;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.MUST_BE_POSITIVE;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NOT_SUPPORTED;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.NO_INTERFACE;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.UNABLE_TO_RETRIEVE_METADATA;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.VALID_LABELS;
import static com.ing.data.cassandra.jdbc.utils.ErrorConstants.WAS_CLOSED_RS;
/**
* Cassandra metadata result set. This is an implementation of {@link ResultSet} for database metadata.
*
* It also implements {@link CassandraResultSetExtras} interface providing extra methods not defined in JDBC API to
* better handle some CQL data types.
*
*
* The supported data types in CQL are:
*
* CQL Type Java type Description
* ascii {@link String} US-ASCII character string
* bigint {@link Long} 64-bit signed long
* blob {@link ByteBuffer} Arbitrary bytes (no validation)
* boolean {@link Boolean} Boolean value: true or false
* counter {@link Long} Counter column (64-bit long)
* date {@link Date} A date with no corresponding time value; encoded date
* as a 32-bit integer representing days since epoch (January 1, 1970)
* decimal {@link BigDecimal} Variable-precision decimal
* double {@link Double} 64-bit IEEE-754 floating point
* duration {@link CqlDuration} A duration with nanosecond precision
* float {@link Float} 32-bit IEEE-754 floating point
* inet {@link InetAddress} IP address string in IPv4 or IPv6 format
* int {@link Integer} 32-bit signed integer
* list {@link List} A collection of one or more ordered elements:
* [literal, literal, literal]
* map {@link Map} A JSON-style array of literals:
* { literal : literal, literal : literal ... }
* set {@link Set} A collection of one or more elements:
* { literal, literal, literal }
* smallint {@link Short} 16-bit signed integer
* text {@link String} UTF-8 encoded string
* time {@link Time} A value encoded as a 64-bit signed integer
* representing the number of nanoseconds since midnight
* timestamp {@link Timestamp} Date and time with millisecond precision, encoded as
* 8 bytes since epoch
* timeuuid {@link UUID} Version 1 UUID only
* tinyint {@link Byte} 8-bits signed integer
* uuid {@link UUID} A UUID in standard UUID format
* varchar {@link String} UTF-8 encoded string
* varint {@link BigInteger} Arbitrary-precision integer
*
* See:
* CQL data types reference and
*
* CQL temporal types reference.
*
*
* @see java.sql.DatabaseMetaData
* @see CassandraDatabaseMetaData
*/
public class CassandraMetadataResultSet extends AbstractResultSet implements CassandraResultSetExtras {
int rowNumber = 0;
// Metadata of this result set.
private final CResultSetMetaData metadata;
private final CassandraStatement statement;
private MetadataRow currentRow;
private Iterator rowsIterator;
private int resultSetType;
private int fetchDirection;
private int fetchSize;
private boolean wasNull;
private boolean isClosed;
// Result set from the Cassandra driver.
private MetadataResultSet driverResultSet;
/**
* No argument constructor.
*/
CassandraMetadataResultSet() {
this.metadata = new CResultSetMetaData();
this.statement = null;
this.isClosed = false;
}
/**
* Constructor. It instantiates a new Cassandra metadata result set from a {@link MetadataResultSet}.
*
* @param statement The statement.
* @param metadataResultSet The metadata result set from the Cassandra driver.
* @throws SQLException if a database access error occurs or this constructor is called with a closed
* {@link Statement}.
*/
CassandraMetadataResultSet(final CassandraStatement statement, final MetadataResultSet metadataResultSet)
throws SQLException {
this.metadata = new CResultSetMetaData();
this.statement = statement;
this.resultSetType = statement.getResultSetType();
this.fetchDirection = statement.getFetchDirection();
this.fetchSize = statement.getFetchSize();
this.driverResultSet = metadataResultSet;
this.rowsIterator = metadataResultSet.iterator();
this.isClosed = false;
// Initialize the column values from the first row.
// Note that the first call to next() will harmlessly re-write these values for the columns. The row cursor
// is not moved forward and stays before the first row.
if (hasMoreRows()) {
populateColumns();
}
}
/**
* Builds a new instance of Cassandra metadata result set from a {@link MetadataResultSet}.
*
* @param statement The statement.
* @param metadataResultSet The metadata result set from the Cassandra driver.
* @return A new instance of Cassandra metadata result set.
* @throws SQLException if a database access error occurs or this constructor is called with a closed
* {@link Statement}.
*/
public static CassandraMetadataResultSet buildFrom(final CassandraStatement statement,
final MetadataResultSet metadataResultSet) throws SQLException {
return new CassandraMetadataResultSet(statement, metadataResultSet);
}
private void populateColumns() {
this.currentRow = this.rowsIterator.next();
}
@Override
DataType getCqlDataType(final int columnIndex) throws SQLException {
if (this.currentRow != null && this.currentRow.getColumnDefinitions() != null) {
return this.currentRow.getColumnDefinitions().getType(columnIndex - 1);
}
if (this.driverResultSet != null && this.driverResultSet.getColumnDefinitions() != null) {
return this.driverResultSet.getColumnDefinitions().getType(columnIndex - 1);
}
throw new SQLException(UNABLE_TO_RETRIEVE_METADATA);
}
@Override
DataType getCqlDataType(final String columnLabel) throws SQLException {
if (this.currentRow != null && this.currentRow.getColumnDefinitions() != null) {
return this.currentRow.getColumnDefinitions().getType(columnLabel);
}
if (this.driverResultSet != null && this.driverResultSet.getColumnDefinitions() != null) {
return this.driverResultSet.getColumnDefinitions().getType(columnLabel);
}
throw new SQLException(UNABLE_TO_RETRIEVE_METADATA);
}
@Override
public void afterLast() throws SQLException {
if (this.resultSetType == TYPE_FORWARD_ONLY) {
throw new SQLNonTransientException(FORWARD_ONLY);
}
throw new SQLFeatureNotSupportedException(NOT_SUPPORTED);
}
@Override
public void beforeFirst() throws SQLException {
if (this.resultSetType == TYPE_FORWARD_ONLY) {
throw new SQLNonTransientException(FORWARD_ONLY);
}
throw new SQLFeatureNotSupportedException(NOT_SUPPORTED);
}
private void checkIndex(final int index) throws SQLException {
if (this.currentRow != null) {
if (this.currentRow.getColumnDefinitions() != null) {
if (index < 1 || index > this.currentRow.getColumnDefinitions().asList().size()) {
throw new SQLSyntaxErrorException(String.format(MUST_BE_POSITIVE, index) + StringUtils.SPACE
+ this.currentRow.getColumnDefinitions().asList().size());
}
}
this.wasNull = this.currentRow.isNull(index - 1);
} else if (this.driverResultSet != null) {
if (this.driverResultSet.getColumnDefinitions() != null) {
if (index < 1 || index > this.driverResultSet.getColumnDefinitions().asList().size()) {
throw new SQLSyntaxErrorException(String.format(MUST_BE_POSITIVE, index) + StringUtils.SPACE
+ this.driverResultSet.getColumnDefinitions().asList().size());
}
}
}
}
private void checkName(final String name) throws SQLException {
if (this.currentRow != null) {
if (!this.currentRow.getColumnDefinitions().contains(name)) {
throw new SQLSyntaxErrorException(String.format(VALID_LABELS, name));
}
this.wasNull = this.currentRow.isNull(name);
} else if (this.driverResultSet != null) {
if (this.driverResultSet.getColumnDefinitions() != null) {
if (!this.driverResultSet.getColumnDefinitions().contains(name)) {
throw new SQLSyntaxErrorException(String.format(VALID_LABELS, name));
}
}
}
}
private void checkNotClosed() throws SQLException {
if (isClosed()) {
throw new SQLRecoverableException(WAS_CLOSED_RS);
}
}
@Override
public void clearWarnings() throws SQLException {
// This implementation does not support the collection of warnings so clearing is a no-op, but it still throws
// an exception when called on a closed result set.
checkNotClosed();
}
@Override
public void close() throws SQLException {
if (!isClosed()) {
this.isClosed = true;
}
}
@Override
public int findColumn(final String columnLabel) throws SQLException {
checkNotClosed();
checkName(columnLabel);
if (this.currentRow != null) {
return this.currentRow.getColumnDefinitions().getIndexOf(columnLabel) + 1;
} else if (this.driverResultSet != null && this.driverResultSet.getColumnDefinitions() != null) {
return this.driverResultSet.getColumnDefinitions().getIndexOf(columnLabel) + 1;
}
throw new SQLSyntaxErrorException(String.format(VALID_LABELS, columnLabel));
}
@Override
public BigDecimal getBigDecimal(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getDecimal(columnIndex - 1);
}
/**
* @deprecated use {@link #getBigDecimal(int)}.
*/
@Override
@Deprecated
public BigDecimal getBigDecimal(final int columnIndex, final int scale) throws SQLException {
checkIndex(columnIndex);
final BigDecimal decimalValue = this.currentRow.getDecimal(columnIndex - 1);
if (decimalValue == null) {
return null;
} else {
return decimalValue.setScale(scale, RoundingMode.HALF_UP);
}
}
@Override
public BigDecimal getBigDecimal(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getDecimal(columnLabel);
}
/**
* @deprecated use {@link #getBigDecimal(String)}.
*/
@Override
@Deprecated
public BigDecimal getBigDecimal(final String columnLabel, final int scale) throws SQLException {
checkName(columnLabel);
final BigDecimal decimalValue = this.currentRow.getDecimal(columnLabel);
if (decimalValue == null) {
return null;
} else {
return decimalValue.setScale(scale, RoundingMode.HALF_UP);
}
}
@Override
public BigInteger getBigInteger(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getVarint(columnIndex - 1);
}
@Override
public BigInteger getBigInteger(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getVarint(columnLabel);
}
@Override
public InputStream getBinaryStream(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final ByteBuffer byteBuffer = this.currentRow.getBytes(columnIndex - 1);
if (byteBuffer != null) {
final byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes, 0, bytes.length);
return new ByteArrayInputStream(bytes);
} else {
return null;
}
}
@Override
public InputStream getBinaryStream(final String columnLabel) throws SQLException {
checkName(columnLabel);
final ByteBuffer byteBuffer = this.currentRow.getBytes(columnLabel);
if (byteBuffer != null) {
final byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes, 0, bytes.length);
return new ByteArrayInputStream(bytes);
} else {
return null;
}
}
@Override
public Blob getBlob(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final ByteBuffer byteBuffer = this.currentRow.getBytes(columnIndex - 1);
if (byteBuffer != null) {
return new javax.sql.rowset.serial.SerialBlob(byteBuffer.array());
} else {
return null;
}
}
@Override
public Blob getBlob(final String columnLabel) throws SQLException {
checkName(columnLabel);
final ByteBuffer byteBuffer = this.currentRow.getBytes(columnLabel);
if (byteBuffer != null) {
return new javax.sql.rowset.serial.SerialBlob(byteBuffer.array());
} else {
return null;
}
}
@Override
public boolean getBoolean(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getBool(columnIndex - 1);
}
@Override
public boolean getBoolean(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getBool(columnLabel);
}
@Override
public byte getByte(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getByte(columnIndex - 1);
}
@Override
public byte getByte(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getByte(columnLabel);
}
@Override
public byte[] getBytes(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final ByteBuffer byteBuffer = this.currentRow.getBytes(columnIndex - 1);
if (byteBuffer != null) {
return byteBuffer.array();
}
return null;
}
@Override
public byte[] getBytes(final String columnLabel) throws SQLException {
checkName(columnLabel);
final ByteBuffer byteBuffer = this.currentRow.getBytes(columnLabel);
if (byteBuffer != null) {
return byteBuffer.array();
}
return null;
}
@Override
public int getConcurrency() throws SQLException {
checkNotClosed();
return this.statement.getResultSetConcurrency();
}
@Override
public Date getDate(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final java.util.Date dateValue = this.currentRow.getDate(columnIndex - 1);
if (dateValue == null) {
return null;
}
return new Date(dateValue.getTime());
}
@Override
public Date getDate(final int columnIndex, final Calendar calendar) throws SQLException {
// silently ignore the Calendar argument; it's a hint we do not need
return getDate(columnIndex);
}
@Override
public Date getDate(final String columnLabel) throws SQLException {
checkName(columnLabel);
final java.util.Date dateValue = this.currentRow.getDate(columnLabel);
if (dateValue == null) {
return null;
}
return new Date(dateValue.getTime());
}
@Override
public Date getDate(final String columnLabel, final Calendar calendar) throws SQLException {
// silently ignore the Calendar argument; it's a hint we do not need
return getDate(columnLabel);
}
@Override
public double getDouble(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
if (isCqlType(columnIndex, DataTypeEnum.FLOAT)) {
return this.currentRow.getFloat(columnIndex - 1);
}
return this.currentRow.getDouble(columnIndex - 1);
}
@Override
public double getDouble(final String columnLabel) throws SQLException {
checkName(columnLabel);
if (isCqlType(columnLabel, DataTypeEnum.FLOAT)) {
return this.currentRow.getFloat(columnLabel);
}
return this.currentRow.getDouble(columnLabel);
}
@Override
public CqlDuration getDuration(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getDuration(columnIndex - 1);
}
@Override
public CqlDuration getDuration(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getDuration(columnLabel);
}
@Override
public int getFetchDirection() throws SQLException {
checkNotClosed();
return this.fetchDirection;
}
@Override
public void setFetchDirection(final int direction) throws SQLException {
checkNotClosed();
if (direction == FETCH_FORWARD || direction == FETCH_REVERSE || direction == FETCH_UNKNOWN) {
if (getType() == TYPE_FORWARD_ONLY && direction != FETCH_FORWARD) {
throw new SQLSyntaxErrorException(String.format(ILLEGAL_FETCH_DIRECTION_FOR_FORWARD_ONLY, direction));
}
this.fetchDirection = direction;
}
throw new SQLSyntaxErrorException(String.format(BAD_FETCH_DIR, direction));
}
@Override
public int getFetchSize() throws SQLException {
checkNotClosed();
return this.fetchSize;
}
@Override
public void setFetchSize(final int size) throws SQLException {
checkNotClosed();
if (size < 0) {
throw new SQLException(String.format(BAD_FETCH_SIZE, size));
}
this.fetchSize = size;
}
@Override
public float getFloat(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getFloat(columnIndex - 1);
}
@Override
public float getFloat(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getFloat(columnLabel);
}
@SuppressWarnings("MagicConstant")
@Override
public int getHoldability() throws SQLException {
checkNotClosed();
return this.statement.getResultSetHoldability();
}
@Override
public int getInt(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getInt(columnIndex - 1);
}
@Override
public int getInt(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getInt(columnLabel);
}
@Override
public List> getList(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final DataType cqlDataType = getCqlDataType(columnIndex);
if (DataTypeEnum.fromCqlTypeName(cqlDataType.asCql(false, false)).isCollection()) {
final ListType listType = (ListType) cqlDataType;
final Class> itemsClass = DataTypeEnum.fromCqlTypeName(listType.getElementType()
.asCql(false, false)).javaType;
final List> resultList = this.currentRow.getList(columnIndex - 1, itemsClass);
if (resultList == null) {
return null;
}
return new ArrayList<>(resultList);
}
return this.currentRow.getList(columnIndex - 1, String.class);
}
@Override
public List> getList(final String columnLabel) throws SQLException {
checkName(columnLabel);
final DataType cqlDataType = getCqlDataType(columnLabel);
if (DataTypeEnum.fromCqlTypeName(cqlDataType.asCql(false, false)).isCollection()) {
final ListType listType = (ListType) cqlDataType;
final Class> itemsClass = DataTypeEnum.fromCqlTypeName(listType.getElementType()
.asCql(false, false)).javaType;
final List> resultList = this.currentRow.getList(columnLabel, itemsClass);
if (resultList == null) {
return null;
}
return new ArrayList<>(resultList);
}
return this.currentRow.getList(columnLabel, String.class);
}
@Override
public long getLong(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
if (isCqlType(columnIndex, DataTypeEnum.INT)) {
return this.currentRow.getInt(columnIndex - 1);
} else if (isCqlType(columnIndex, DataTypeEnum.VARINT)) {
return this.currentRow.getVarint(columnIndex - 1).longValue();
} else {
return this.currentRow.getLong(columnIndex - 1);
}
}
@Override
public long getLong(final String columnLabel) throws SQLException {
checkName(columnLabel);
if (isCqlType(columnLabel, DataTypeEnum.INT)) {
return this.currentRow.getInt(columnLabel);
} else if (isCqlType(columnLabel, DataTypeEnum.VARINT)) {
return this.currentRow.getVarint(columnLabel).longValue();
} else {
return this.currentRow.getLong(columnLabel);
}
}
@Override
public Map, ?> getMap(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final DataType cqlDataType = getCqlDataType(columnIndex);
if (DataTypeEnum.fromCqlTypeName(cqlDataType.asCql(false, false)).isCollection()) {
final MapType mapType = (MapType) cqlDataType;
final Class> keysClass = DataTypeEnum.fromCqlTypeName(mapType.getKeyType()
.asCql(false, false)).javaType;
final Class> valuesClass = DataTypeEnum.fromCqlTypeName(mapType.getValueType()
.asCql(false, false)).javaType;
return new LinkedHashMap<>(this.currentRow.getMap(columnIndex - 1, keysClass, valuesClass));
}
return this.currentRow.getMap(columnIndex - 1, String.class, String.class);
}
@Override
public Map, ?> getMap(final String columnLabel) throws SQLException {
checkName(columnLabel);
final DataType cqlDataType = getCqlDataType(columnLabel);
if (DataTypeEnum.fromCqlTypeName(cqlDataType.asCql(false, false)).isCollection()) {
final MapType mapType = (MapType) cqlDataType;
final Class> keysClass = DataTypeEnum.fromCqlTypeName(mapType.getKeyType()
.asCql(false, false)).javaType;
final Class> valuesClass = DataTypeEnum.fromCqlTypeName(mapType.getValueType()
.asCql(false, false)).javaType;
return new LinkedHashMap<>(this.currentRow.getMap(columnLabel, keysClass, valuesClass));
}
return this.currentRow.getMap(columnLabel, String.class, String.class);
}
@Override
public ResultSetMetaData getMetaData() throws SQLException {
checkNotClosed();
return this.metadata;
}
@Override
public Object getObject(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final DataTypeEnum dataType = DataTypeEnum.fromCqlTypeName(getCqlDataType(columnIndex).asCql(false, false));
switch (dataType) {
case VARCHAR:
case ASCII:
case TEXT:
return this.currentRow.getString(columnIndex - 1);
case VARINT:
return this.currentRow.getVarint(columnIndex - 1);
case INT:
return this.currentRow.getInt(columnIndex - 1);
case SMALLINT:
return this.currentRow.getShort(columnIndex - 1);
case TINYINT:
return this.currentRow.getByte(columnIndex - 1);
case BIGINT:
case COUNTER:
return this.currentRow.getLong(columnIndex - 1);
case BLOB:
return this.currentRow.getBytes(columnIndex - 1);
case BOOLEAN:
return this.currentRow.getBool(columnIndex - 1);
case DATE:
return this.currentRow.getDate(columnIndex - 1);
case TIME:
return this.currentRow.getTime(columnIndex - 1);
case DECIMAL:
return this.currentRow.getDecimal(columnIndex - 1);
case DOUBLE:
return this.currentRow.getDouble(columnIndex - 1);
case FLOAT:
return this.currentRow.getFloat(columnIndex - 1);
case INET:
return this.currentRow.getInet(columnIndex - 1);
case TIMESTAMP:
return new Timestamp((currentRow.getDate(columnIndex - 1)).getTime());
case DURATION:
return this.currentRow.getDuration(columnIndex - 1);
case UUID:
case TIMEUUID:
return this.currentRow.getUUID(columnIndex - 1);
default:
return null;
}
}
@Override
public Object getObject(final String columnLabel) throws SQLException {
checkName(columnLabel);
final DataTypeEnum dataType = DataTypeEnum.fromCqlTypeName(getCqlDataType(columnLabel).asCql(false, false));
switch (dataType) {
case VARCHAR:
case ASCII:
case TEXT:
return this.currentRow.getString(columnLabel);
case VARINT:
return this.currentRow.getVarint(columnLabel);
case INT:
return this.currentRow.getInt(columnLabel);
case SMALLINT:
return this.currentRow.getShort(columnLabel);
case TINYINT:
return this.currentRow.getByte(columnLabel);
case BIGINT:
case COUNTER:
return this.currentRow.getLong(columnLabel);
case BLOB:
return this.currentRow.getBytes(columnLabel);
case BOOLEAN:
return this.currentRow.getBool(columnLabel);
case DATE:
return this.currentRow.getDate(columnLabel);
case TIME:
return this.currentRow.getTime(columnLabel);
case DECIMAL:
return this.currentRow.getDecimal(columnLabel);
case DOUBLE:
return this.currentRow.getDouble(columnLabel);
case FLOAT:
return this.currentRow.getFloat(columnLabel);
case INET:
return this.currentRow.getInet(columnLabel);
case TIMESTAMP:
return new Timestamp((this.currentRow.getDate(columnLabel)).getTime());
case DURATION:
return this.currentRow.getDuration(columnLabel);
case UUID:
case TIMEUUID:
return this.currentRow.getUUID(columnLabel);
default:
return null;
}
}
private String getObjectAsString(final int columnIndex) throws SQLException {
final Object o = getObject(columnIndex);
if (o != null) {
return String.valueOf(o);
}
return null;
}
private String getObjectAsString(final String columnLabel) throws SQLException {
final Object o = getObject(columnLabel);
if (o != null) {
return String.valueOf(o);
}
return null;
}
@Override
public int getRow() throws SQLException {
checkNotClosed();
return this.rowNumber;
}
@Override
public RowId getRowId(final int columnIndex) {
return null;
}
@Override
public RowId getRowId(final String columnLabel) {
return null;
}
@Override
public Set> getSet(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
try {
final SetType setType = (SetType) getCqlDataType(columnIndex);
return this.currentRow.getSet(columnIndex - 1,
Class.forName(DataTypeEnum.fromCqlTypeName(setType.getElementType().asCql(false, false)).asJavaClass()
.getCanonicalName()));
} catch (final ClassNotFoundException e) {
throw new SQLNonTransientException(e);
}
}
@Override
public Set> getSet(final String columnLabel) throws SQLException {
checkName(columnLabel);
try {
final SetType setType = (SetType) getCqlDataType(columnLabel);
return this.currentRow.getSet(columnLabel,
Class.forName(DataTypeEnum.fromCqlTypeName(setType.getElementType().asCql(false, false)).asJavaClass()
.getCanonicalName()));
} catch (final ClassNotFoundException e) {
throw new SQLNonTransientException(e);
}
}
@Override
public short getShort(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getShort(columnIndex - 1);
}
@Override
public short getShort(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getShort(columnLabel);
}
@Override
public Statement getStatement() throws SQLException {
checkNotClosed();
return this.statement;
}
@Override
public String getString(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
try {
if (DataTypeEnum.fromCqlTypeName(getCqlDataType(columnIndex).asCql(false, false)).isCollection()) {
return getObjectAsString(columnIndex);
}
return this.currentRow.getString(columnIndex - 1);
} catch (final Exception e) {
return getObjectAsString(columnIndex);
}
}
@Override
public String getString(final String columnLabel) throws SQLException {
checkName(columnLabel);
try {
if (DataTypeEnum.fromCqlTypeName(getCqlDataType(columnLabel).asCql(false, false)).isCollection()) {
return getObjectAsString(columnLabel);
}
return this.currentRow.getString(columnLabel);
} catch (final Exception e) {
return getObjectAsString(columnLabel);
}
}
@Override
public Time getTime(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
return this.currentRow.getTime(columnIndex - 1);
}
@Override
public Time getTime(final int columnIndex, final Calendar calendar) throws SQLException {
// silently ignore the Calendar argument; it's a hint we do not need
return getTime(columnIndex);
}
@Override
public Time getTime(final String columnLabel) throws SQLException {
checkName(columnLabel);
return this.currentRow.getTime(columnLabel);
}
@Override
public Time getTime(final String columnLabel, final Calendar calendar) throws SQLException {
// silently ignore the Calendar argument; it's a hint we do not need
return getTime(columnLabel);
}
@Override
public Timestamp getTimestamp(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
final java.util.Date date = this.currentRow.getDate(columnIndex - 1);
if (date == null) {
return null;
}
return new Timestamp(this.currentRow.getDate(columnIndex - 1).getTime());
}
@Override
public Timestamp getTimestamp(final int columnIndex, final Calendar calendar) throws SQLException {
// silently ignore the Calendar argument; it's a hint we do not need
return getTimestamp(columnIndex);
}
@Override
public Timestamp getTimestamp(final String columnLabel) throws SQLException {
checkName(columnLabel);
final java.util.Date date = this.currentRow.getDate(columnLabel);
if (date == null) {
return null;
}
return new Timestamp(this.currentRow.getDate(columnLabel).getTime());
}
@Override
public Timestamp getTimestamp(final String columnLabel, final Calendar calendar) throws SQLException {
// silently ignore the Calendar argument; it's a hint we do not need
return getTimestamp(columnLabel);
}
@Override
public int getType() throws SQLException {
checkNotClosed();
return this.resultSetType;
}
@Override
public URL getURL(final int columnIndex) throws SQLException {
checkIndex(columnIndex);
// Handle URL data type as a String.
final String storedUrl = this.currentRow.getString(columnIndex - 1);
if (storedUrl == null) {
return null;
} else {
try {
return new URL(storedUrl);
} catch (final MalformedURLException e) {
throw new SQLException(String.format(MALFORMED_URL, storedUrl), e);
}
}
}
@Override
public URL getURL(final String columnLabel) throws SQLException {
checkName(columnLabel);
// Handle URL data type as a String.
final String storedUrl = this.currentRow.getString(columnLabel);
if (storedUrl == null) {
return null;
} else {
try {
return new URL(storedUrl);
} catch (final MalformedURLException e) {
throw new SQLException(String.format(MALFORMED_URL, storedUrl), e);
}
}
}
@Override
public CqlVector> getVector(final int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException(NOT_SUPPORTED);
}
@Override
public CqlVector> getVector(final String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException(NOT_SUPPORTED);
}
@Override
public SQLWarning getWarnings() throws SQLException {
// The rationale is there are no warnings to return in this implementation, but it still throws an exception
// when called on a closed result set.
checkNotClosed();
return null;
}
private boolean hasMoreRows() {
return this.rowsIterator != null
&& (this.rowsIterator.hasNext() || (this.rowNumber == 0 && this.currentRow != null));
}
@Override
public boolean isAfterLast() throws SQLException {
checkNotClosed();
return this.rowNumber == Integer.MAX_VALUE;
}
@Override
public boolean isBeforeFirst() throws SQLException {
checkNotClosed();
return this.rowNumber == 0;
}
@Override
public boolean isClosed() {
return this.isClosed;
}
@Override
public boolean isFirst() throws SQLException {
checkNotClosed();
return this.rowNumber == 1;
}
@Override
public boolean isLast() throws SQLException {
checkNotClosed();
return !this.rowsIterator.hasNext();
}
@Override
public synchronized boolean next() {
if (hasMoreRows()) {
// 'populateColumns()' is called upon init to set up the metadata fields; so skip the first call.
if (this.rowNumber != 0) {
populateColumns();
}
this.rowNumber++;
return true;
}
this.rowNumber = Integer.MAX_VALUE;
return false;
}
@Override
public boolean wasNull() {
return this.wasNull;
}
/**
* Implementation class for {@link ResultSetMetaData}. The metadata returned refers to the column values, not the
* column names.
*/
class CResultSetMetaData implements ResultSetMetaData {
@Override
public String getCatalogName(final int column) throws SQLException {
if (statement == null) {
return null;
}
return statement.connection.getCatalog();
}
@Override
public String getColumnClassName(final int column) throws SQLException {
if (currentRow != null) {
return DataTypeEnum.fromCqlTypeName(getCqlDataType(column).asCql(false, false)).asJavaClass()
.getCanonicalName();
}
if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
return DataTypeEnum.fromCqlTypeName(
driverResultSet.getColumnDefinitions().asList().get(column - 1).getType().asCql(false, false))
.asJavaClass().getCanonicalName();
}
throw new SQLException(UNABLE_TO_RETRIEVE_METADATA);
}
@Override
public int getColumnCount() {
if (currentRow != null) {
return currentRow.getColumnDefinitions().size();
}
if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
return driverResultSet.getColumnDefinitions().size();
}
return 0;
}
@Override
public String getColumnLabel(final int column) throws SQLException {
return getColumnName(column);
}
@Override
public String getColumnName(final int column) throws SQLException {
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
return currentRow.getColumnDefinitions().getName(column - 1);
}
if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
return driverResultSet.getColumnDefinitions().asList().get(column - 1).getName();
}
throw new SQLException(UNABLE_TO_RETRIEVE_METADATA);
}
@Override
public int getColumnDisplaySize(final int column) {
try {
final AbstractJdbcType> jdbcEquivalentType;
final ColumnDefinitions.Definition columnDefinition;
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
columnDefinition = currentRow.getColumnDefinitions().asList().get(column - 1);
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
columnDefinition = driverResultSet.getColumnDefinitions().asList().get(column - 1);
} else {
return DEFAULT_PRECISION;
}
jdbcEquivalentType = TypesMap.getTypeForComparator(columnDefinition.getType().toString());
int length = DEFAULT_PRECISION;
if (jdbcEquivalentType != null) {
length = jdbcEquivalentType.getPrecision(null);
}
return length;
} catch (final Exception e) {
return DEFAULT_PRECISION;
}
}
@Override
public int getColumnType(final int column) throws SQLException {
final DataType type;
if (currentRow != null) {
type = getCqlDataType(column);
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
type = driverResultSet.getColumnDefinitions().asList().get(column - 1).getType();
} else {
return Types.OTHER;
}
return TypesMap.getTypeForComparator(type.toString()).getJdbcType();
}
@Override
public String getColumnTypeName(final int column) throws SQLException {
// Specification says "database specific type name"; for Cassandra this means the AbstractType.
final DataType type;
if (currentRow != null) {
type = getCqlDataType(column);
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
type = driverResultSet.getColumnDefinitions().getType(column - 1);
} else {
return StringUtils.EMPTY;
}
return type.toString();
}
@Override
public int getPrecision(final int column) {
return Math.max(getColumnDisplaySize(column), 0);
}
@Override
public int getScale(final int column) {
try {
final AbstractJdbcType> jdbcEquivalentType;
final ColumnDefinitions.Definition columnDefinition;
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
columnDefinition = currentRow.getColumnDefinitions().asList().get(column - 1);
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
columnDefinition = driverResultSet.getColumnDefinitions().asList().get(column - 1);
} else {
return DEFAULT_SCALE;
}
jdbcEquivalentType = TypesMap.getTypeForComparator(columnDefinition.getType().toString());
int scale = DEFAULT_SCALE;
if (jdbcEquivalentType != null) {
scale = jdbcEquivalentType.getScale(null);
}
return scale;
} catch (final Exception e) {
return DEFAULT_SCALE;
}
}
@Override
public String getSchemaName(final int column) throws SQLException {
if (statement == null) {
return null;
}
return statement.connection.getSchema();
}
@Override
public String getTableName(final int column) {
final String tableName;
if (currentRow != null && currentRow.getColumnDefinitions() != null) {
tableName = currentRow.getColumnDefinitions().getTable(column - 1);
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
tableName = driverResultSet.getColumnDefinitions().getTable(column - 1);
} else {
return StringUtils.EMPTY;
}
return tableName;
}
@Override
public boolean isAutoIncrement(final int column) {
return false;
}
@Override
public boolean isCaseSensitive(final int column) {
return true;
}
@Override
public boolean isCurrency(final int column) {
return false;
}
@Override
public boolean isDefinitelyWritable(final int column) {
return isWritable(column);
}
@Override
public int isNullable(final int column) {
// Note: absence is the equivalent of null in Cassandra
return ResultSetMetaData.columnNullable;
}
@Override
public boolean isReadOnly(final int column) {
return column == 0;
}
/**
* Indicates whether the designated column can be used in a where clause.
*
* Using Cassandra database, we consider that only the columns in a primary key (partitioning keys and
* clustering columns) or in an index are searchable.
* See:
* WHERE clause in CQL SELECT statements
*
*
* @param column The column index (the first column is 1, the second is 2, ...).
* @return {@code true} if so, {@code false} otherwise.
* @throws SQLException if a database access error occurs.
*/
@Override
public boolean isSearchable(final int column) throws SQLException {
if (statement == null) {
return false;
}
final String columnName = getColumnName(column);
final String schemaName = getSchemaName(column);
final String tableName = getTableName(column);
// If the schema or table name is not defined, always returns false since we cannot determine if the column
// is searchable in this context.
if (StringUtils.isEmpty(schemaName) || StringUtils.isEmpty(tableName)) {
return false;
}
final AtomicBoolean searchable = new AtomicBoolean(false);
statement.connection.getSession().getMetadata().getKeyspace(schemaName)
.flatMap(metadata -> metadata.getTable(tableName))
.ifPresent(tableMetadata -> {
boolean result;
// Check first if the column is a clustering column or in a partitioning key.
result = tableMetadata.getPrimaryKey().stream()
.anyMatch(columnMetadata -> columnMetadata.getName().asInternal().equals(columnName));
// If not, check if the column is used in an index.
if (!result) {
result = tableMetadata.getIndexes().values().stream()
.anyMatch(indexMetadata -> indexMetadata.getTarget().contains(columnName));
}
searchable.set(result);
});
return searchable.get();
}
@Override
public boolean isSigned(final int column) throws SQLException {
final DataType type;
if (currentRow != null) {
type = getCqlDataType(column);
} else if (driverResultSet != null && driverResultSet.getColumnDefinitions() != null) {
type = driverResultSet.getColumnDefinitions().asList().get(column - 1).getType();
} else {
return false;
}
return TypesMap.getTypeForComparator(type.toString()).isSigned();
}
@Override
public boolean isWritable(final int column) {
return column > 0;
}
@Override
public boolean isWrapperFor(final Class> iface) throws SQLException {
return iface != null && iface.isAssignableFrom(this.getClass());
}
@Override
public T unwrap(final Class iface) throws SQLException {
if (isWrapperFor(iface)) {
return iface.cast(this);
} else {
throw new SQLException(String.format(NO_INTERFACE, iface.getSimpleName()));
}
}
}
}