Skip to content

Commit 7cbe271

Browse files
committed
Covered arrays
1 parent aba5ac3 commit 7cbe271

5 files changed

Lines changed: 530 additions & 34 deletions

File tree

client-v2/src/main/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReader.java

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.clickhouse.client.api.data_formats;
22

3+
import com.clickhouse.client.api.ClientException;
4+
import com.clickhouse.client.api.data_formats.internal.NumberConverter;
35
import com.clickhouse.client.api.internal.SchemaUtils;
46
import com.clickhouse.client.api.metadata.TableSchema;
57
import com.clickhouse.data.ClickHouseColumn;
@@ -9,6 +11,7 @@
911
import com.clickhouse.data.value.ClickHouseGeoPolygonValue;
1012
import com.clickhouse.data.value.ClickHouseGeoRingValue;
1113

14+
import java.lang.reflect.Array;
1215
import java.math.BigDecimal;
1316
import java.math.BigInteger;
1417
import java.net.Inet4Address;
@@ -151,9 +154,21 @@ public double getDouble(String colName) {
151154
@Override
152155
public boolean getBoolean(String colName) {
153156
Object val = currentRow.get(colName);
154-
if (val instanceof Boolean) return (Boolean) val;
155-
if (val instanceof Number) return ((Number) val).intValue() != 0;
156-
return Boolean.parseBoolean(val.toString());
157+
if (val instanceof Boolean) {
158+
return (Boolean) val;
159+
}
160+
if (val instanceof Number) {
161+
// Match AbstractBinaryFormatReader (SerializerUtils.convertToBoolean):
162+
// any non-zero integral value is true, zero is false. Fractional
163+
// values keep the same behavior because they are truncated by
164+
// longValue() before the zero check.
165+
return ((Number) val).longValue() != 0;
166+
}
167+
if (val == null) {
168+
throw new ClientException("Column '" + colName + "' has null value and cannot be converted to boolean");
169+
}
170+
throw new ClientException("Cannot convert value of type " + val.getClass().getName()
171+
+ " in column '" + colName + "' to boolean");
157172
}
158173

159174
@Override
@@ -224,52 +239,150 @@ public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) {
224239

225240
@Override
226241
public <T> List<T> getList(String colName) {
227-
return (List<T>) currentRow.get(colName);
242+
Object val = currentRow.get(colName);
243+
if (val == null) {
244+
return null;
245+
}
246+
if (!(val instanceof List<?>)) {
247+
throw new ClientException("Column '" + colName + "' is not of array type (actual: "
248+
+ val.getClass().getName() + ")");
249+
}
250+
return (List<T>) val;
228251
}
229252

230253
@Override
231254
public byte[] getByteArray(String colName) {
232-
throw new UnsupportedOperationException();
255+
return getPrimitiveArray(colName, byte.class);
233256
}
234257

235258
@Override
236259
public int[] getIntArray(String colName) {
237-
throw new UnsupportedOperationException();
260+
return getPrimitiveArray(colName, int.class);
238261
}
239262

240263
@Override
241264
public long[] getLongArray(String colName) {
242-
throw new UnsupportedOperationException();
265+
return getPrimitiveArray(colName, long.class);
243266
}
244267

245268
@Override
246269
public float[] getFloatArray(String colName) {
247-
throw new UnsupportedOperationException();
270+
return getPrimitiveArray(colName, float.class);
248271
}
249272

250273
@Override
251274
public double[] getDoubleArray(String colName) {
252-
throw new UnsupportedOperationException();
275+
return getPrimitiveArray(colName, double.class);
253276
}
254277

255278
@Override
256279
public boolean[] getBooleanArray(String colName) {
257-
throw new UnsupportedOperationException();
280+
return getPrimitiveArray(colName, boolean.class);
258281
}
259282

260283
@Override
261284
public short[] getShortArray(String colName) {
262-
throw new UnsupportedOperationException();
285+
return getPrimitiveArray(colName, short.class);
263286
}
264287

265288
@Override
266289
public String[] getStringArray(String colName) {
267-
throw new UnsupportedOperationException();
290+
List<?> list = asArrayList(colName);
291+
if (list == null) {
292+
return null;
293+
}
294+
String[] out = new String[list.size()];
295+
for (int i = 0; i < list.size(); i++) {
296+
Object el = list.get(i);
297+
out[i] = el == null ? null : el.toString();
298+
}
299+
return out;
268300
}
269301

270302
@Override
271303
public Object[] getObjectArray(String colName) {
272-
throw new UnsupportedOperationException();
304+
List<?> list = asArrayList(colName);
305+
return list == null ? null : list.toArray(new Object[0]);
306+
}
307+
308+
/**
309+
* Returns the value of the given column as a {@code List}, or {@code null}
310+
* if the value is missing. Throws {@link ClientException} when the column
311+
* exists but is not an array.
312+
*/
313+
private List<?> asArrayList(String colName) {
314+
Object val = currentRow.get(colName);
315+
if (val == null) {
316+
return null;
317+
}
318+
if (!(val instanceof List<?>)) {
319+
throw new ClientException("Column '" + colName + "' is not of array type (actual: "
320+
+ val.getClass().getName() + ")");
321+
}
322+
return (List<?>) val;
323+
}
324+
325+
@SuppressWarnings("unchecked")
326+
private <T> T getPrimitiveArray(String colName, Class<?> componentType) {
327+
List<?> list = asArrayList(colName);
328+
if (list == null) {
329+
return null;
330+
}
331+
try {
332+
Object array = Array.newInstance(componentType, list.size());
333+
for (int i = 0; i < list.size(); i++) {
334+
Object el = list.get(i);
335+
if (el == null) {
336+
throw new ClientException("Column '" + colName
337+
+ "' contains a null element which cannot fit into an array of primitive "
338+
+ componentType.getName());
339+
}
340+
Array.set(array, i, coerceToComponent(el, componentType));
341+
}
342+
return (T) array;
343+
} catch (ClassCastException | IllegalArgumentException e) {
344+
throw new ClientException("Value of column '" + colName
345+
+ "' cannot be converted to an array of " + componentType.getName(), e);
346+
}
347+
}
348+
349+
/**
350+
* Coerces a parsed JSON element to a boxed primitive type. JSON parsers
351+
* may materialize numeric array elements as different boxed types
352+
* (e.g. {@code Integer}, {@code Long}, {@code Double}, {@code BigDecimal}),
353+
* so element-level conversion is necessary before populating a typed
354+
* primitive array.
355+
*/
356+
private static Object coerceToComponent(Object value, Class<?> componentType) {
357+
if (componentType == byte.class) {
358+
return NumberConverter.toByte(value);
359+
}
360+
if (componentType == short.class) {
361+
return NumberConverter.toShort(value);
362+
}
363+
if (componentType == int.class) {
364+
return NumberConverter.toInt(value);
365+
}
366+
if (componentType == long.class) {
367+
return NumberConverter.toLong(value);
368+
}
369+
if (componentType == float.class) {
370+
return NumberConverter.toFloat(value);
371+
}
372+
if (componentType == double.class) {
373+
return NumberConverter.toDouble(value);
374+
}
375+
if (componentType == boolean.class) {
376+
if (value instanceof Boolean) {
377+
return value;
378+
}
379+
if (value instanceof Number) {
380+
return ((Number) value).longValue() != 0;
381+
}
382+
throw new IllegalArgumentException(
383+
"Cannot convert " + value.getClass().getName() + " to boolean array element");
384+
}
385+
return value;
273386
}
274387

275388
@Override

0 commit comments

Comments
 (0)