From a013c127e8ec147a580baaa85dcd758937e31347 Mon Sep 17 00:00:00 2001 From: ugurtafrali Date: Tue, 24 Feb 2026 22:51:44 +0300 Subject: [PATCH] Fix #2755: hasValue on GenericRecord returns false instead of throwing for missing columns - Add bounds check to hasValue(int colIndex) to return false when colIndex is out of range instead of throwing ArrayIndexOutOfBoundsException - Wrap hasValue(String colName) in try/catch for NoSuchColumnException to return false when column name does not exist instead of propagating the exception - Add unit test testHasValueWithMissingColumns to verify both fixes Co-Authored-By: Claude Sonnet 4.6 --- .../internal/AbstractBinaryFormatReader.java | 9 +++++- .../ClickHouseBinaryFormatReaderTest.java | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java index 23b05c983..6b7f646bd 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/AbstractBinaryFormatReader.java @@ -569,12 +569,19 @@ public Object[] getObjectArray(String colName) { @Override public boolean hasValue(int colIndex) { + if (colIndex < 1 || colIndex > currentRecord.length) { + return false; + } return currentRecord[colIndex - 1] != null; } @Override public boolean hasValue(String colName) { - return hasValue(schema.nameToColumnIndex(colName)); + try { + return hasValue(schema.nameToColumnIndex(colName)); + } catch (NoSuchColumnException e) { + return false; + } } @Override diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java index b72b2ed26..88552a8d0 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java @@ -846,4 +846,35 @@ public void testEmptyArraysByIndex() throws Exception { Assert.assertEquals(reader.getList(2).size(), 0); Assert.assertEquals(reader.getList(3).size(), 0); } + + @Test + public void testHasValueWithMissingColumns() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + String[] names = new String[]{"a", "b"}; + String[] types = new String[]{"UInt32", "UInt32"}; + + BinaryStreamUtils.writeVarInt(out, names.length); + for (String name : names) { + BinaryStreamUtils.writeString(out, name); + } + for (String type : types) { + BinaryStreamUtils.writeString(out, type); + } + + BinaryStreamUtils.writeUnsignedInt32(out, 1L); + BinaryStreamUtils.writeUnsignedInt32(out, 2L); + + InputStream in = new ByteArrayInputStream(out.toByteArray()); + QuerySettings querySettings = new QuerySettings().setUseTimeZone("UTC"); + RowBinaryWithNamesAndTypesFormatReader reader = + new RowBinaryWithNamesAndTypesFormatReader(in, querySettings, new BinaryStreamReader.CachingByteBufferAllocator()); + reader.next(); + + Assert.assertTrue(reader.hasValue("a"), "Expected hasValue(\"a\") to be true"); + Assert.assertTrue(reader.hasValue(1), "Expected hasValue(1) to be true"); + Assert.assertFalse(reader.hasValue("missing"), "Expected hasValue(\"missing\") to return false, not throw"); + Assert.assertFalse(reader.hasValue(100), "Expected hasValue(100) to return false, not throw"); + Assert.assertFalse(reader.hasValue(0), "Expected hasValue(0) to return false, not throw"); + } } \ No newline at end of file