Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -1554,4 +1555,36 @@ public void testVariantDataType() throws SQLException {
}
}
}

@Test(groups = "integration")
public void testSpatialData() {
final String spatialQuery = "select \n" +
"\tcast(arrayJoin([(4.837388, 52.38795),\n" +
"\t\t\t(4.951513, 52.354582),\n" +
"\t\t\t(4.961987, 52.371763),\n" +
"\t\t\t(4.870017, 52.334932),\n" +
"\t\t\t(4.89813, 52.357238),\n" +
"\t\t\t(4.852437, 52.370315),\n" +
"\t\t\t(4.901712, 52.369567),\n" +
"\t\t\t(4.874112, 52.339823),\n" +
"\t\t\t(4.856942, 52.339122),\n" +
"\t\t\t(4.870253, 52.360353)]\n" +
"\t\t\t)\n" +
"\t\tas Point) as Point";
try (ClickHouseConnection conn = newConnection();
ClickHouseStatement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(spatialQuery);
rs.next();
ResultSetMetaData metaData = rs.getMetaData();
String columnTypeName = metaData.getColumnTypeName(1);
int columnType = metaData.getColumnType(1);
Array asArray = rs.getArray(1);
Object asObject = rs.getObject(1);
String asString = rs.getString(1);
Assert.assertEquals(metaData.getColumnCount(), 7);
} catch (Exception e) {
e.printStackTrace();
Assert.fail("Failed to create connection", e);
}
}
}
95 changes: 53 additions & 42 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;
import com.clickhouse.client.api.metadata.TableSchema;
import com.clickhouse.client.api.query.QueryResponse;
import com.clickhouse.data.ClickHouseDataType;
import com.clickhouse.data.ClickHouseColumn;
import com.clickhouse.jdbc.internal.ExceptionUtils;
import com.clickhouse.jdbc.internal.FeatureManager;
import com.clickhouse.jdbc.internal.JdbcUtils;
Expand All @@ -16,7 +16,6 @@
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
Expand All @@ -36,6 +35,7 @@
import java.sql.Timestamp;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.function.Consumer;

Expand Down Expand Up @@ -465,16 +465,6 @@ protected void setMetaData(ResultSetMetaDataImpl metaData) {
this.metaData = metaData;
}

@Override
public Object getObject(int columnIndex) throws SQLException {
return getObject(columnIndex, JdbcUtils.convertToJavaClass(getSchema().getColumnByIndex(columnIndex).getDataType()));
}

@Override
public Object getObject(String columnLabel) throws SQLException {
return getObject(columnLabel, JdbcUtils.convertToJavaClass(getSchema().getColumnByName(columnLabel).getDataType()));
}

@Override
public int findColumn(String columnLabel) throws SQLException {
checkClosed();
Expand Down Expand Up @@ -945,12 +935,6 @@ public Statement getStatement() throws SQLException {
return this.parentStatement;
}

@Override
public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException {
ClickHouseDataType type = getSchema().getColumnByIndex(columnIndex).getDataType();
return getObject(columnIndex, map.get(JdbcUtils.convertToSqlType(type).getName()));
}

@Override
public Ref getRef(int columnIndex) throws SQLException {
return getRef(columnIndexToName(columnIndex));
Expand All @@ -971,12 +955,6 @@ public java.sql.Array getArray(int columnIndex) throws SQLException {
return getObject(columnIndex, java.sql.Array.class);
}

@Override
public Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException {
checkClosed();
return getObject(columnLabel, map.get(JdbcUtils.convertToSqlType(getSchema().getColumnByName(columnLabel).getDataType()).getName()));
}

@Override
public Ref getRef(String columnLabel) throws SQLException {
checkClosed();
Expand Down Expand Up @@ -1420,38 +1398,71 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException {
}

@Override
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
public Object getObject(int columnIndex) throws SQLException {
return getObject(columnIndexToName(columnIndex));
}

@Override
public Object getObject(String columnLabel) throws SQLException {
return getObjectImpl(columnLabel, null, Collections.emptyMap());
}

@Override
public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException {
return getObject(columnIndexToName(columnIndex), map);
}

@Override
public Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException {
checkClosed();
try {
if (reader.hasValue(columnIndex)) {
wasNull = false;
if (type == null) {//As a fallback, try to get the value as is
return reader.readValue(columnIndex);
}
return getObjectImpl(columnLabel, null, map);
}

return (T) JdbcUtils.convert(reader.readValue(columnIndex), type, type == java.sql.Array.class ? getSchema().getColumnByIndex(columnIndex) : null);
} else {
wasNull = true;
return null;
}
} catch (Exception e) {
throw ExceptionUtils.toSqlState(String.format("Method: getObject(\"%s\", %s) encountered an exception.",
reader.getSchema().columnIndexToName(columnIndex), type),
String.format("SQL: [%s]", parentStatement.getLastStatementSql()), e);
}
@Override
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
checkClosed();
return getObject(columnIndexToName(columnIndex), type);
}

@Override
public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
checkClosed();
return getObjectImpl(columnLabel, type, Collections.emptyMap());
}

@SuppressWarnings("unchecked")
public <T> T getObjectImpl(String columnLabel, Class<?> type, Map<String, Class<?>> typeMap) throws SQLException {
try {
ClickHouseColumn column = getSchema().getColumnByName(columnLabel);
if (column == null) {
throw new SQLException("Column \"" + columnLabel + "\" does not exist.");
}

if (reader.hasValue(columnLabel)) {
wasNull = false;

if (type == null) {
switch (column.getDataType()) {
case Point:
case Ring:
case LineString:
case MultiPolygon:
case MultiLineString:
break; // read as is
default:
if (typeMap == null || typeMap.isEmpty()) {
type = JdbcUtils.convertToJavaClass(column.getDataType());
} else {
type = typeMap.get(JdbcUtils.convertToSqlType(column.getDataType()).getName());
}
}
}

if (type == null) {//As a fallback, try to get the value as is
return reader.readValue(columnLabel);
}

return (T) JdbcUtils.convert(reader.readValue(columnLabel), type, type == java.sql.Array.class ? getSchema().getColumnByName(columnLabel) : null);
return (T) JdbcUtils.convert(reader.readValue(columnLabel), type, column);
} else {
wasNull = true;
return null;
Expand Down
86 changes: 80 additions & 6 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.time.chrono.ChronoZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
Expand Down Expand Up @@ -78,12 +79,12 @@ private static Map<ClickHouseDataType, SQLType> generateTypeMap() {
map.put(ClickHouseDataType.Array, JDBCType.ARRAY);
map.put(ClickHouseDataType.Nested, JDBCType.ARRAY);
map.put(ClickHouseDataType.Map, JDBCType.JAVA_OBJECT);
map.put(ClickHouseDataType.Point, JDBCType.OTHER);
map.put(ClickHouseDataType.Ring, JDBCType.OTHER);
map.put(ClickHouseDataType.Polygon, JDBCType.OTHER);
map.put(ClickHouseDataType.LineString, JDBCType.OTHER);
map.put(ClickHouseDataType.MultiPolygon, JDBCType.OTHER);
map.put(ClickHouseDataType.MultiLineString, JDBCType.OTHER);
map.put(ClickHouseDataType.Point, JDBCType.ARRAY);
map.put(ClickHouseDataType.Ring, JDBCType.ARRAY);
map.put(ClickHouseDataType.Polygon, JDBCType.ARRAY);
map.put(ClickHouseDataType.LineString, JDBCType.ARRAY);
map.put(ClickHouseDataType.MultiPolygon, JDBCType.ARRAY);
map.put(ClickHouseDataType.MultiLineString, JDBCType.ARRAY);
return ImmutableMap.copyOf(map);
}

Expand Down Expand Up @@ -281,6 +282,8 @@ public static Object convert(Object value, Class<?> type, ClickHouseColumn colum
}
// base type is unknown. all objects should be converted
return new Array(column, ((List<?>) value).toArray());
} else if (type == java.sql.Array.class && value.getClass().isArray()) {
return new Array(column, arrayToObjectArray(value));
} else if (type == Inet4Address.class && value instanceof Inet6Address) {
// Convert Inet6Address to Inet4Address
return InetAddressConverter.convertToIpv4((InetAddress) value);
Expand Down Expand Up @@ -322,4 +325,75 @@ public static Object[] convertArray(Object[] values, Class<?> type) throws SQLEx
}
return convertedValues;
}

private static Object[] arrayToObjectArray(Object array) {
if (array == null) {
return null;
}
if (array instanceof Object[]) {
return (Object[]) array;
}
if (!array.getClass().isArray()) {
throw new IllegalArgumentException("Not an array: " + array.getClass().getName());
}

if (array instanceof byte[]) {
byte[] src = (byte[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
} else if (array instanceof short[]) {
short[] src = (short[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
} else if (array instanceof int[]) {
int[] src = (int[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
} else if (array instanceof long[]) {
long[] src = (long[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
} else if (array instanceof float[]) {
float[] src = (float[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
} else if (array instanceof double[]) {
double[] src = (double[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
} else if (array instanceof char[]) {
char[] src = (char[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
} else if (array instanceof boolean[]) {
boolean[] src = (boolean[]) array;
Object[] dst = new Object[src.length];
for (int i = 0; i < src.length; i++) {
dst[i] = src[i];
}
return dst;
}
throw new IllegalArgumentException("Cannot convert " + array.getClass().getName() + " to an Object[]");
}
}
12 changes: 12 additions & 0 deletions jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,16 @@ private void ensureValid() throws SQLException {
throw ExceptionUtils.toSqlState(new SQLFeatureNotSupportedException("Array is not valid. Possible free() was called."));
}
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Array other = (Array) obj;
return type == other.type && java.util.Arrays.equals(array, other.array);
}
}
50 changes: 50 additions & 0 deletions jdbc-v2/src/test/java/com/clickhouse/jdbc/DataTypeTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.DecimalFormat;
import java.time.Instant;
import java.time.LocalDate;
Expand Down Expand Up @@ -1651,4 +1652,53 @@ public void testVariantTypesSimpleStatement() throws SQLException {
}
}
}

@Test(groups = { "integration" })
public void testSpatialData() throws Exception {
final Double[][] spatialArrayData = new Double[][] {
{4.837388, 52.38795},
{4.951513, 52.354582},
{4.961987, 52.371763},
{4.870017, 52.334932},
{4.89813, 52.357238},
{4.852437, 52.370315},
{4.901712, 52.369567},
{4.874112, 52.339823},
{4.856942, 52.339122},
{4.870253, 52.360353},
};

StringBuilder sql = new StringBuilder();
sql.append("SELECT \n");
sql.append("\tcast(arrayJoin([");
for (int i = 0; i < spatialArrayData.length; i++) {
sql.append("(" + spatialArrayData[i][0] + ", " + spatialArrayData[i][1] + ")").append(',');
}
sql.setLength(sql.length() - 1);
sql.append("])");
sql.append("as Point) as Point");



try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery(sql.toString())) {

ResultSetMetaData metaData = rs.getMetaData();
assertEquals(metaData.getColumnCount(), 1);
assertEquals(metaData.getColumnTypeName(1), "Point");
assertEquals(metaData.getColumnType(1), Types.ARRAY);

int rowCount = 0;
while (rs.next()) {
Object asObject = rs.getObject(1);
assertTrue(asObject instanceof double[]);
Array asArray = rs.getArray(1);
assertEquals(asArray.getArray(), spatialArrayData[rowCount]);
assertEquals(asObject, asArray.getArray());
rowCount++;
}
assertTrue(rowCount > 0);
}
}
}
}
Loading