Skip to content

Commit ed814b1

Browse files
committed
covered getters by index
1 parent 7cbe271 commit ed814b1

3 files changed

Lines changed: 192 additions & 8 deletions

File tree

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,9 @@ private <T> T getPrimitiveArray(String colName, Class<?> componentType) {
351351
* may materialize numeric array elements as different boxed types
352352
* (e.g. {@code Integer}, {@code Long}, {@code Double}, {@code BigDecimal}),
353353
* so element-level conversion is necessary before populating a typed
354-
* primitive array.
354+
* primitive array. The {@code componentType} is always one of the eight
355+
* Java primitives passed by {@link #getPrimitiveArray}; unsupported
356+
* component types are rejected explicitly to keep the helper total.
355357
*/
356358
private static Object coerceToComponent(Object value, Class<?> componentType) {
357359
if (componentType == byte.class) {
@@ -382,7 +384,7 @@ private static Object coerceToComponent(Object value, Class<?> componentType) {
382384
throw new IllegalArgumentException(
383385
"Cannot convert " + value.getClass().getName() + " to boolean array element");
384386
}
385-
return value;
387+
throw new IllegalArgumentException("Unsupported component type: " + componentType.getName());
386388
}
387389

388390
@Override

client-v2/src/test/java/com/clickhouse/client/api/data_formats/AbstractJSONEachRowFormatReaderTests.java

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -450,20 +450,33 @@ public void testArrayAccessors() throws Exception {
450450
Assert.assertNotNull(intList);
451451
Assert.assertEquals(intList.size(), 3);
452452

453+
// Each typed accessor is exercised by name first and then by 1-based
454+
// column index so the index-based overloads are also covered against
455+
// the inferred schema produced from the first row.
453456
Assert.assertEquals(reader.getIntArray("col_int_arr"), new int[] {1, 2, 3});
454457
Assert.assertEquals(reader.getIntArray(1), new int[] {1, 2, 3});
455458
Assert.assertEquals(reader.getLongArray("col_long_arr"), new long[] {10L, 20L, 30L});
456459
Assert.assertEquals(reader.getLongArray(2), new long[] {10L, 20L, 30L});
457-
Assert.assertEquals(reader.getShortArray("col_short_arr"), new short[] {(short) 1, (short) 2});
460+
Assert.assertEquals(reader.getShortArray("col_short_arr"),
461+
new short[] {(short) 1, (short) 2});
462+
Assert.assertEquals(reader.getShortArray(3), new short[] {(short) 1, (short) 2});
458463
Assert.assertEquals(reader.getByteArray("col_byte_arr"), new byte[] {(byte) 7, (byte) 8});
459-
Assert.assertEquals(reader.getDoubleArray("col_double_arr"), new double[] {1.5d, 2.5d}, 1e-9);
460-
Assert.assertEquals(reader.getFloatArray("col_float_arr"), new float[] {1.0f, 2.0f}, 1e-6f);
461-
Assert.assertEquals(reader.getStringArray("col_string_arr"), new String[] {"a", "b", "c"});
464+
Assert.assertEquals(reader.getByteArray(4), new byte[] {(byte) 7, (byte) 8});
465+
Assert.assertEquals(reader.getDoubleArray("col_double_arr"),
466+
new double[] {1.5d, 2.5d}, 1e-9);
467+
Assert.assertEquals(reader.getDoubleArray(5), new double[] {1.5d, 2.5d}, 1e-9);
468+
Assert.assertEquals(reader.getFloatArray("col_float_arr"),
469+
new float[] {1.0f, 2.0f}, 1e-6f);
470+
Assert.assertEquals(reader.getFloatArray(6), new float[] {1.0f, 2.0f}, 1e-6f);
471+
Assert.assertEquals(reader.getStringArray("col_string_arr"),
472+
new String[] {"a", "b", "c"});
473+
Assert.assertEquals(reader.getStringArray(7), new String[] {"a", "b", "c"});
462474
Assert.assertEquals(reader.getBooleanArray("col_bool_arr"),
463475
new boolean[] {true, false, true});
476+
Assert.assertEquals(reader.getBooleanArray(8), new boolean[] {true, false, true});
464477

465-
Object[] objs = reader.getObjectArray("col_int_arr");
466-
Assert.assertEquals(objs.length, 3);
478+
Assert.assertEquals(reader.getObjectArray("col_int_arr").length, 3);
479+
Assert.assertEquals(reader.getObjectArray(1).length, 3);
467480

468481
// Non-array columns must surface a RuntimeException rather than
469482
// silently returning null or a malformed array.
@@ -482,6 +495,55 @@ public void testArrayAccessors() throws Exception {
482495
}
483496
}
484497

498+
/**
499+
* Locks in the contract that a {@code null} element coming from the server
500+
* (e.g. through {@code Array(Nullable(...))}) cannot be silently turned
501+
* into a zero/false slot of a Java primitive array. The reader must throw
502+
* a {@link RuntimeException} so callers don't lose the distinction between
503+
* "missing value" and "actual zero".
504+
*/
505+
@Test(groups = {"integration"})
506+
public void testArrayAccessorsRejectNullElementsFromServer() throws Exception {
507+
String sql = "SELECT "
508+
+ "[1, NULL, 3]::Array(Nullable(Int32)) AS col_int_arr, "
509+
+ "[true, NULL]::Array(Nullable(Bool)) AS col_bool_arr, "
510+
+ "['x', NULL, 'z']::Array(Nullable(String)) AS col_string_arr";
511+
512+
try (QueryResponse response =
513+
client.query(sql, newJsonEachRowSettingsForPrimitives()).get();
514+
ClickHouseTextFormatReader reader = createReader(response)) {
515+
516+
Assert.assertNotNull(reader.next());
517+
518+
try {
519+
reader.getIntArray("col_int_arr");
520+
Assert.fail("Expected exception on null element in primitive array");
521+
} catch (RuntimeException expected) {
522+
// ok
523+
}
524+
try {
525+
reader.getBooleanArray("col_bool_arr");
526+
Assert.fail("Expected exception on null element in primitive array");
527+
} catch (RuntimeException expected) {
528+
// ok
529+
}
530+
531+
// getStringArray and getObjectArray preserve nulls because the
532+
// resulting array can hold null references.
533+
Assert.assertEquals(reader.getStringArray("col_string_arr"),
534+
new String[] {"x", null, "z"});
535+
Object[] objs = reader.getObjectArray("col_string_arr");
536+
Assert.assertEquals(objs.length, 3);
537+
Assert.assertNull(objs[1]);
538+
539+
// getList must surface the raw list with the null element intact.
540+
List<?> intList = reader.getList("col_int_arr");
541+
Assert.assertNotNull(intList);
542+
Assert.assertEquals(intList.size(), 3);
543+
Assert.assertNull(intList.get(1));
544+
}
545+
}
546+
485547
// ------------------------------------------------------------------
486548
// Test case definitions for primitive types
487549
// ------------------------------------------------------------------

client-v2/src/test/java/com/clickhouse/client/api/data_formats/JSONEachRowFormatReaderTest.java

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,13 +501,63 @@ public void testArrayAccessorsReturnNullForNullValue() throws Exception {
501501
try (JSONEachRowFormatReader reader = new JSONEachRowFormatReader(
502502
new StubJsonParser(Collections.singletonList(r)))) {
503503
reader.next();
504+
505+
// By name: every array accessor must propagate the null cleanly
506+
// rather than throw NullPointerException or fabricate an empty array.
504507
Assert.assertNull(reader.getList("v"));
508+
Assert.assertNull(reader.getByteArray("v"));
509+
Assert.assertNull(reader.getShortArray("v"));
505510
Assert.assertNull(reader.getIntArray("v"));
506511
Assert.assertNull(reader.getLongArray("v"));
512+
Assert.assertNull(reader.getFloatArray("v"));
507513
Assert.assertNull(reader.getDoubleArray("v"));
508514
Assert.assertNull(reader.getBooleanArray("v"));
509515
Assert.assertNull(reader.getStringArray("v"));
510516
Assert.assertNull(reader.getObjectArray("v"));
517+
518+
// By 1-based index: the same null-propagation guarantee must hold
519+
// for the index-based overloads, which delegate to the name-based
520+
// implementations through the inferred schema.
521+
Assert.assertNull(reader.getList(1));
522+
Assert.assertNull(reader.getByteArray(1));
523+
Assert.assertNull(reader.getShortArray(1));
524+
Assert.assertNull(reader.getIntArray(1));
525+
Assert.assertNull(reader.getLongArray(1));
526+
Assert.assertNull(reader.getFloatArray(1));
527+
Assert.assertNull(reader.getDoubleArray(1));
528+
Assert.assertNull(reader.getBooleanArray(1));
529+
Assert.assertNull(reader.getStringArray(1));
530+
Assert.assertNull(reader.getObjectArray(1));
531+
}
532+
}
533+
534+
@Test
535+
public void testArrayAccessorsRejectNullElement() throws Exception {
536+
// A null element in a JSON array cannot be stored into a Java
537+
// primitive array slot, so getPrimitiveArray must surface this rather
538+
// than substitute a zero/false value silently. The String and Object
539+
// overloads are allowed to keep the null element.
540+
try (JSONEachRowFormatReader reader = readerOf(row(
541+
"ints", Arrays.asList(1, null, 3),
542+
"bools", Arrays.asList(Boolean.TRUE, null),
543+
"strs", Arrays.asList("a", null, "c"),
544+
"objs", Arrays.asList("x", null)))) {
545+
reader.next();
546+
547+
assertThrowsRuntime(() -> reader.getIntArray("ints"));
548+
assertThrowsRuntime(() -> reader.getIntArray(1));
549+
assertThrowsRuntime(() -> reader.getLongArray("ints"));
550+
assertThrowsRuntime(() -> reader.getShortArray("ints"));
551+
assertThrowsRuntime(() -> reader.getByteArray("ints"));
552+
assertThrowsRuntime(() -> reader.getFloatArray("ints"));
553+
assertThrowsRuntime(() -> reader.getDoubleArray("ints"));
554+
assertThrowsRuntime(() -> reader.getBooleanArray("bools"));
555+
556+
// The non-primitive container accessors keep nulls.
557+
Assert.assertEquals(reader.getStringArray("strs"), new String[] {"a", null, "c"});
558+
Object[] objs = reader.getObjectArray("objs");
559+
Assert.assertEquals(objs.length, 2);
560+
Assert.assertNull(objs[1]);
511561
}
512562
}
513563

@@ -538,6 +588,76 @@ public void testArrayAccessorsRejectNonArrayValues() throws Exception {
538588
}
539589
}
540590

591+
@Test
592+
public void testArrayAccessorsByIndex() throws Exception {
593+
// Mirrors testArrayAccessorsCoercePrimitiveElements but addresses every
594+
// typed array accessor through its 1-based column index. This ensures
595+
// the index-based overloads delegate correctly through the inferred
596+
// schema, even for arrays where the schema only records "Array" without
597+
// a specific element type.
598+
try (JSONEachRowFormatReader reader = readerOf(row(
599+
"ints", Arrays.asList(1, 2, 3),
600+
"longs", Arrays.asList(10L, 20L, 30L),
601+
"shorts", Arrays.asList((short) 4, (short) 5),
602+
"bytes", Arrays.asList((byte) 6, (byte) 7),
603+
"doubles", Arrays.asList(1.5d, 2.5d),
604+
"floats", Arrays.asList(1.0f, 2.0f),
605+
"bools", Arrays.asList(Boolean.TRUE, Boolean.FALSE),
606+
"strs", Arrays.asList("a", "b")))) {
607+
reader.next();
608+
609+
Assert.assertEquals(reader.getIntArray(1), new int[] {1, 2, 3});
610+
Assert.assertEquals(reader.getLongArray(2), new long[] {10L, 20L, 30L});
611+
Assert.assertEquals(reader.getShortArray(3), new short[] {(short) 4, (short) 5});
612+
Assert.assertEquals(reader.getByteArray(4), new byte[] {(byte) 6, (byte) 7});
613+
Assert.assertEquals(reader.getDoubleArray(5), new double[] {1.5d, 2.5d}, 1e-9);
614+
Assert.assertEquals(reader.getFloatArray(6), new float[] {1.0f, 2.0f}, 1e-6f);
615+
Assert.assertEquals(reader.getBooleanArray(7), new boolean[] {true, false});
616+
Assert.assertEquals(reader.getStringArray(8), new String[] {"a", "b"});
617+
618+
// getList and getObjectArray by index, on the first column.
619+
List<Integer> list = reader.getList(1);
620+
Assert.assertEquals(list, Arrays.asList(1, 2, 3));
621+
Object[] objs = reader.getObjectArray(1);
622+
Assert.assertEquals(objs.length, 3);
623+
}
624+
}
625+
626+
@Test
627+
public void testGetBooleanArrayFromNumericList() throws Exception {
628+
// Exercises the Number branch in coerceToComponent for boolean[]: the
629+
// reader treats 0 as false and any non-zero value as true, regardless
630+
// of whether the parser materialized the element as Integer, Long, or
631+
// BigDecimal.
632+
try (JSONEachRowFormatReader reader = readerOf(row(
633+
"as_int", Arrays.asList(0, 1, 2, -1),
634+
"as_long", Arrays.asList(0L, 5L),
635+
"as_big_decimal", Arrays.asList(BigDecimal.ZERO, new BigDecimal("3"))))) {
636+
reader.next();
637+
638+
Assert.assertEquals(reader.getBooleanArray("as_int"),
639+
new boolean[] {false, true, true, true});
640+
Assert.assertEquals(reader.getBooleanArray("as_long"),
641+
new boolean[] {false, true});
642+
Assert.assertEquals(reader.getBooleanArray("as_big_decimal"),
643+
new boolean[] {false, true});
644+
}
645+
}
646+
647+
@Test
648+
public void testGetBooleanArrayRejectsNonBooleanNonNumberElements() throws Exception {
649+
// String elements cannot be coerced into boolean[] (we don't accept
650+
// string-to-boolean parsing on the scalar getBoolean accessor either),
651+
// so getBooleanArray must surface a RuntimeException sourced from the
652+
// illegal-coerce branch in coerceToComponent.
653+
try (JSONEachRowFormatReader reader = readerOf(row(
654+
"v", Arrays.asList("true", "false")))) {
655+
reader.next();
656+
assertThrowsRuntime(() -> reader.getBooleanArray("v"));
657+
assertThrowsRuntime(() -> reader.getBooleanArray(1));
658+
}
659+
}
660+
541661
@Test
542662
public void testArrayAccessorsCoercePrimitiveElements() throws Exception {
543663
// Different parsers materialize numbers as different boxed types

0 commit comments

Comments
 (0)