Skip to content

Commit 8377a8d

Browse files
authored
Merge pull request #2815 from ClickHouse/03/31/26/support_geometry_type
[client-v2,jdbc-v2] Support Geometry Type
2 parents 324bc4f + 4d9a794 commit 8377a8d

14 files changed

Lines changed: 4233 additions & 3352 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- **[jdbc-v2]** Added `cluster_name` configuration property to specify a target cluster for statements like `KILL QUERY` that require an `ON CLUSTER` clause to execute across all nodes. (https://github.com/ClickHouse/clickhouse-java/issues/2837)
66

7+
- **[client-v2, jdbc-v2]** Added support for ClickHouse `Geometry` type for ClickHouse `25.11+`, where `Geometry` changed from a `String` alias to `Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)` (client still compatible with older versions). Includes client read/write handling and JDBC type mapping for retrieving and inserting geometry values. Current writes infer the target geometry variant from array nesting depth, so `Ring` vs `LineString` and `Polygon` vs `MultiLineString` are not yet distinguishable through the generic `Geometry` write path. (https://github.com/ClickHouse/clickhouse-java/pull/2815)
8+
79
### Bug Fixes
810

911
- **[client-v2]** Fixed inconsistent use of `executionTimeout` parameter in `Client` component. The timeout was previously set in milliseconds but mistakenly retrieved and used in seconds in some places. Now it correctly uses milliseconds consistently. (https://github.com/ClickHouse/clickhouse-java/issues/2358)

clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseColumn.java

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
/**
5555
* This class represents a column defined in database.
5656
*/
57+
@SuppressWarnings("deprecation")
5758
public final class ClickHouseColumn implements Serializable {
5859
public static final String TYPE_NAME = "Column";
5960
public static final ClickHouseColumn[] EMPTY_ARRAY = new ClickHouseColumn[0];
@@ -105,6 +106,8 @@ public final class ClickHouseColumn implements Serializable {
105106

106107
private Map<Class<?>, Integer> arrayToVariantOrdNumMap;
107108

109+
private Map<Integer, Integer> geometryTypeDimensionsToVariantOrdNumMap;
110+
108111
private Map<Class<?>, Integer> mapKeyToVariantOrdNumMap;
109112
private Map<Class<?>, Integer> mapValueToVariantOrdNumMap;
110113

@@ -293,12 +296,28 @@ private static ClickHouseColumn update(ClickHouseColumn column) {
293296
case Ring:
294297
column.template = ClickHouseGeoRingValue.ofEmpty();
295298
break;
299+
case LineString:
300+
column.template = ClickHouseGeoRingValue.ofEmpty();
301+
break;
296302
case Polygon:
297303
column.template = ClickHouseGeoPolygonValue.ofEmpty();
298304
break;
305+
case MultiLineString:
306+
column.template = ClickHouseGeoPolygonValue.ofEmpty();
307+
break;
299308
case MultiPolygon:
300309
column.template = ClickHouseGeoMultiPolygonValue.ofEmpty();
301310
break;
311+
case Geometry:
312+
ClickHouseColumn geometryVariantHelper = GEOMETRY_VARIANT_COLUMN;
313+
column.template = ClickHouseTupleValue.of();
314+
column.nested = geometryVariantHelper.nested;
315+
column.classToVariantOrdNumMap = geometryVariantHelper.classToVariantOrdNumMap;
316+
column.arrayToVariantOrdNumMap = geometryVariantHelper.arrayToVariantOrdNumMap;
317+
column.geometryTypeDimensionsToVariantOrdNumMap = geometryVariantHelper.geometryTypeDimensionsToVariantOrdNumMap;
318+
column.mapKeyToVariantOrdNumMap = geometryVariantHelper.mapKeyToVariantOrdNumMap;
319+
column.mapValueToVariantOrdNumMap = geometryVariantHelper.mapValueToVariantOrdNumMap;
320+
break;
302321
case Nested:
303322
column.template = ClickHouseNestedValue.ofEmpty(column.nested);
304323
break;
@@ -318,6 +337,28 @@ private static ClickHouseColumn update(ClickHouseColumn column) {
318337
return column;
319338
}
320339

340+
private static ClickHouseColumn createGeometryVariantColumn() {
341+
ClickHouseColumn column = ClickHouseColumn.of("v",
342+
"Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)");
343+
Map<Integer, Integer> map = new HashMap<>();
344+
map.put(1, getVariantOrdNum(column.nested, ClickHouseDataType.Point));
345+
map.put(2, getVariantOrdNum(column.nested, ClickHouseDataType.Ring));
346+
map.put(3, getVariantOrdNum(column.nested, ClickHouseDataType.Polygon));
347+
map.put(4, getVariantOrdNum(column.nested, ClickHouseDataType.MultiPolygon));
348+
column.geometryTypeDimensionsToVariantOrdNumMap = Collections.unmodifiableMap(map);
349+
return column;
350+
}
351+
352+
private static int getVariantOrdNum(List<ClickHouseColumn> nestedColumns, ClickHouseDataType dataType) {
353+
for (int i = 0; i < nestedColumns.size(); i++) {
354+
if (nestedColumns.get(i).getDataType() == dataType) {
355+
return i;
356+
}
357+
}
358+
359+
throw new IllegalArgumentException("Missing nested geometry type: " + dataType);
360+
}
361+
321362
protected static int readColumn(String args, int startIndex, int len, String name, List<ClickHouseColumn> list) {
322363
ClickHouseColumn column = null;
323364

@@ -374,7 +415,7 @@ protected static int readColumn(String args, int startIndex, int len, String nam
374415
nestedColumns.add(ClickHouseColumn.of("", p));
375416
}
376417
}
377-
column = new ClickHouseColumn(ClickHouseDataType.valueOf(matchedKeyword), name,
418+
column = new ClickHouseColumn(ClickHouseDataType.of(matchedKeyword), name,
378419
args.substring(startIndex, i), nullable, lowCardinality, params, nestedColumns);
379420
column.aggFuncType = aggFunc;
380421
if (!nestedColumns.isEmpty()) {
@@ -468,7 +509,7 @@ protected static int readColumn(String args, int startIndex, int len, String nam
468509
variantDataTypes.add(c.dataType);
469510
});
470511
}
471-
column = new ClickHouseColumn(ClickHouseDataType.valueOf(matchedKeyword), name,
512+
column = new ClickHouseColumn(ClickHouseDataType.of(matchedKeyword), name,
472513
args.substring(startIndex, endIndex + 1), nullable, lowCardinality, null, nestedColumns);
473514
for (ClickHouseColumn n : nestedColumns) {
474515
estimatedLength += n.estimatedByteLength;
@@ -821,18 +862,24 @@ public boolean isAggregateFunction() {
821862

822863
public int getVariantOrdNum(Object value) {
823864
if (value != null && value.getClass().isArray()) {
865+
if (arrayToVariantOrdNumMap == null) {
866+
return -1;
867+
}
824868
// TODO: add cache by value class
825869
Class<?> c = value.getClass();
826870
while (c.isArray()) {
827871
c = c.getComponentType();
828872
}
829873
return arrayToVariantOrdNumMap.getOrDefault(c, -1);
830874
} else if (value != null && value instanceof List<?>) {
875+
if (arrayToVariantOrdNumMap == null) {
876+
return -1;
877+
}
831878
// TODO: add cache by instance of the list
832-
Object tmpV = ((List) value).get(0);
879+
Object tmpV = ((List<?>) value).get(0);
833880
Class<?> valueClass = tmpV.getClass();
834881
while (tmpV instanceof List<?>) {
835-
tmpV = ((List) tmpV).get(0);
882+
tmpV = ((List<?>) tmpV).get(0);
836883
valueClass = tmpV.getClass();
837884
}
838885
return arrayToVariantOrdNumMap.getOrDefault(valueClass, -1);
@@ -861,10 +908,33 @@ public int getVariantOrdNum(Object value) {
861908
}
862909
return -1;
863910
} else {
864-
return classToVariantOrdNumMap.getOrDefault(value.getClass(), -1);
911+
return getGeometryVariantOrdNum(value);
865912
}
866913
}
867914

915+
public int getGeometryVariantOrdNum(Object value) {
916+
if (value == null || classToVariantOrdNumMap == null) {
917+
return -1;
918+
}
919+
920+
Integer ordNum = classToVariantOrdNumMap.get(value.getClass());
921+
if (ordNum != null) {
922+
return ordNum;
923+
}
924+
925+
return -1;
926+
}
927+
928+
public int getGeometryVariantOrdNum(int dimensions) {
929+
if (dimensions < 1 || geometryTypeDimensionsToVariantOrdNumMap == null) {
930+
return -1;
931+
}
932+
933+
return geometryTypeDimensionsToVariantOrdNumMap.getOrDefault(dimensions, -1);
934+
}
935+
936+
private static final ClickHouseColumn GEOMETRY_VARIANT_COLUMN = createGeometryVariantColumn();
937+
868938
public boolean isArray() {
869939
return dataType == ClickHouseDataType.Array;
870940
}

clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseDataType.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ public enum ClickHouseDataType implements SQLType {
106106
Ring(Object.class, false, true, true, 0, 0, 0, 0, 0, true), // same as Array(Point)
107107
LineString( Object.class, false, true, true, 0, 0, 0, 0, 0, true), // same as Array(Point)
108108
MultiLineString(Object.class, false, true, true, 0, 0, 0, 0, 0, true), // same as Array(Ring)
109+
Geometry(Object.class, false, true, true, 0, 0, 0, 0, 0, true), // same as Variant(Point, ...)
109110
JSON(Object.class, false, false, false, 0, 0, 0, 0, 0, true, 0x30),
110111
@Deprecated // (since = "CH 25.11")
111112
Object(Object.class, true, true, false, 0, 0, 0, 0, 0, true),
@@ -130,7 +131,6 @@ public enum ClickHouseDataType implements SQLType {
130131
Time(LocalDateTime.class, true, false, false, 4, 9, 0, 0, 9, false, 0x32), // 0x33 for Time(Timezone)
131132
Time64(LocalDateTime.class, true, false, false, 8, 9, 0, 0, 0, false, 0x34), // 0x35 for Time64(P, Timezone)
132133
QBit(Double.class, true, true, false, 0, 0, 0, 0, 0, false, 0x36),
133-
Geometry(Object.class, false, false, false, 0, 0, 0, 0, 0, true),
134134
;
135135

136136
public static final List<ClickHouseDataType> ORDERED_BY_RANGE_INT_TYPES =
@@ -214,8 +214,19 @@ static Map<ClickHouseDataType, Set<Class<?>>> dataTypeClassMap() {
214214

215215
map.put(Point, setOf(double[].class, ClickHouseGeoPointValue.class));
216216
map.put(Ring, setOf(double[][].class, ClickHouseGeoRingValue.class));
217+
map.put(LineString, setOf(double[][].class, ClickHouseGeoRingValue.class));
217218
map.put(Polygon, setOf(double[][][].class, ClickHouseGeoPolygonValue.class));
219+
map.put(MultiLineString, setOf(double[][][].class, ClickHouseGeoPolygonValue.class));
218220
map.put(MultiPolygon, setOf(double[][][][].class, ClickHouseGeoMultiPolygonValue.class));
221+
map.put(Geometry, setOf(
222+
double[].class,
223+
double[][].class,
224+
double[][][].class,
225+
double[][][][].class,
226+
ClickHouseGeoPointValue.class,
227+
ClickHouseGeoRingValue.class,
228+
ClickHouseGeoPolygonValue.class,
229+
ClickHouseGeoMultiPolygonValue.class));
219230

220231
map.put(Date, setOf(LocalDateTime.class, LocalDate.class, ZonedDateTime.class));
221232
map.put(Date32, setOf(LocalDateTime.class, LocalDate.class, ZonedDateTime.class));

0 commit comments

Comments
 (0)