Skip to content

[JDBC] Connection#createArray & Connection#createStruct #2523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
Expand Down Expand Up @@ -667,7 +668,7 @@ public static class ArrayValue {

int nextPos = 0;

ArrayValue(Class<?> itemType, int length) {
public ArrayValue(Class<?> itemType, int length) {
this.itemType = itemType;
this.length = length;

Expand Down Expand Up @@ -721,6 +722,34 @@ public synchronized <T> List<T> asList() {
}
return (List<T>) list;
}

/**
* Returns internal array. This method is only useful to work with array of primitives (int[], boolean[]).
* Otherwise use {@link #getArrayOfObjects()}
*
* @return
*/
public Object getArray() {
return array;
}

/**
* Returns array of objects.
* If item type is primitive then all elements will be converted into objects.
*
* @return
*/
public Object[] getArrayOfObjects() {
if (itemType.isPrimitive()) {
Object[] result = new Object[length];
for (int i = 0; i < length; i++) {
result[i] = Array.get(array, i);
}
return result;
} else {
return (Object[]) array;
}
}
}

public static class EnumValue extends Number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.TimeZone;

import org.testng.Assert;
Expand Down Expand Up @@ -176,4 +178,16 @@ private Object[][] provideDateTimeTestData() {
};
}

@Test
public void testArrayValue() throws Exception {
BinaryStreamReader.ArrayValue array = new BinaryStreamReader.ArrayValue(int.class, 10);

for (int i = 0; i < array.length(); i++) {
array.set(i, i);
}

int[] array1 = (int[]) array.getArray();
Object[] array2 = array.getArrayOfObjects();
Assert.assertEquals(array1.length, array2.length);
}
}
62 changes: 46 additions & 16 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.client.api.query.GenericRecord;
import com.clickhouse.client.api.query.QuerySettings;
import com.clickhouse.data.ClickHouseColumn;
import com.clickhouse.data.ClickHouseDataType;
import com.clickhouse.jdbc.internal.ExceptionUtils;
import com.clickhouse.jdbc.internal.JdbcConfiguration;
Expand All @@ -22,6 +23,7 @@
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.JDBCType;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
Expand Down Expand Up @@ -59,6 +61,8 @@ public class ConnectionImpl implements Connection, JdbcV2Wrapper {
private String schema;
private String appName;
private QuerySettings defaultQuerySettings;
private boolean readOnly;
private int holdability;

private final DatabaseMetaDataImpl metadata;
protected final Calendar defaultCalendar;
Expand All @@ -73,6 +77,8 @@ public ConnectionImpl(String url, Properties info) throws SQLException {
this.onCluster = false;
this.cluster = null;
this.appName = "";
this.readOnly = false;
this.holdability = ResultSet.HOLD_CURSORS_OVER_COMMIT;
String clientName = "ClickHouse JDBC Driver V2/" + Driver.driverVersion;

Map<String, String> clientProperties = config.getClientProperties();
Expand Down Expand Up @@ -229,15 +235,16 @@ public DatabaseMetaData getMetaData() throws SQLException {
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
ensureOpen();
if (!config.isIgnoreUnsupportedRequests() && readOnly) {
throw new SQLFeatureNotSupportedException("read-only=true unsupported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
}
// This method is just a hint for the driver. Documentation doesn't tell to block update operations.
// Currently, we do not use this hint but some connection pools may use this property.
// So we just save and return
this.readOnly = readOnly;
}

@Override
public boolean isReadOnly() throws SQLException {
ensureOpen();
return false;
return readOnly;
}

@Override
Expand Down Expand Up @@ -279,13 +286,13 @@ public void clearWarnings() throws SQLException {
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
ensureOpen();
return createStatement(resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);
return createStatement(resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT);
}

@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
ensureOpen();
return prepareStatement(sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);
return prepareStatement(sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT);
}

@Override
Expand Down Expand Up @@ -319,13 +326,19 @@ public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
@Override
public void setHoldability(int holdability) throws SQLException {
ensureOpen();
//TODO: Should this be supported?
if (holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT && holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {
throw new SQLException("Only ResultSet.HOLD_CURSORS_OVER_COMMIT and ResultSet.CLOSE_CURSORS_AT_COMMIT allowed for holdability");
}
// we do not support transactions and almost always use auto-commit.
// holdability regulates is result set is open or closed on commit.
// currently we ignore value and always set what we support.
this.holdability = ResultSet.HOLD_CURSORS_OVER_COMMIT;
}

@Override
public int getHoldability() throws SQLException {
ensureOpen();
return ResultSet.HOLD_CURSORS_OVER_COMMIT;//TODO: Check if this is correct
return holdability;
}

@Override
Expand Down Expand Up @@ -563,25 +576,42 @@ public Properties getClientInfo() throws SQLException {

@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
ensureOpen();
if (typeName == null) {
throw new SQLFeatureNotSupportedException("typeName cannot be null");
}


int parentPos = typeName.indexOf('(');
int endPos = parentPos == -1 ? typeName.length() : parentPos;
String clickhouseDataTypeName = (typeName.substring(0, endPos)).trim();
ClickHouseDataType dataType = ClickHouseDataType.valueOf(clickhouseDataTypeName);
if (dataType.equals(ClickHouseDataType.Array)) {
throw new SQLFeatureNotSupportedException("Array cannot be a base type. In case of nested array provide most deep element type name.");
}
try {
List<Object> list =
(elements == null || elements.length == 0) ? Collections.emptyList() : Arrays.stream(elements, 0, elements.length).collect(Collectors.toList());
return new com.clickhouse.jdbc.types.Array(list, typeName, JdbcUtils.convertToSqlType(ClickHouseDataType.valueOf(typeName)).getVendorTypeNumber());
return new com.clickhouse.jdbc.types.Array(elements, typeName,
JdbcUtils.CLICKHOUSE_TO_SQL_TYPE_MAP.getOrDefault(dataType, JDBCType.OTHER).getVendorTypeNumber());
} catch (Exception e) {
throw new SQLException("Failed to create array", ExceptionUtils.SQL_STATE_CLIENT_ERROR, e);
}
}

@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
//TODO: Should this be supported?
if (!config.isIgnoreUnsupportedRequests()) {
throw new SQLFeatureNotSupportedException("createStruct not supported", ExceptionUtils.SQL_STATE_FEATURE_NOT_SUPPORTED);
ensureOpen();
if (typeName == null) {
throw new SQLFeatureNotSupportedException("typeName cannot be null");
}
ClickHouseColumn column = ClickHouseColumn.of("v", typeName);
if (column.getDataType().equals(ClickHouseDataType.Tuple)) {
return new com.clickhouse.jdbc.types.Struct(column, attributes);
} else {
throw new SQLException("Only Tuple datatype is supported for Struct", ExceptionUtils.SQL_STATE_CLIENT_ERROR);
}

return null;
}


@Override
public void setSchema(String schema) throws SQLException {
ensureOpen();
Expand Down
Loading
Loading