Skip to content

Commit 3d6a219

Browse files
Strengthen nested-Nullable test: place Nullable mid-schema with trailing Float64
Per review feedback: a single-column / leading-Nullable schema cannot detect a byte-misaligned nested-Nullable serialization, because nothing follows the dropped marker. Each present-Nullable data-provider row now puts the Nullable in the MIDDLE of the schema (Tuple(Int32, Nullable(X), Float64)) with a leading non-null column and a trailing fixed-width Float64, and the assertion compares the whole row — so a dropped not-null marker shifts the following bytes and the trailing Float64 reads a wrong value (or hits EOF), positionally detecting the fault. Also covers the Java-array serializeTupleData branch with a null element.
1 parent 4858a6f commit 3d6a219

1 file changed

Lines changed: 51 additions & 42 deletions

File tree

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

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -159,52 +159,61 @@ private Object[][] nestedNullableData() throws Exception {
159159
UUID uuid = UUID.fromString("61f0c404-5cb3-11e7-907b-a6006ad3dba0");
160160
InetAddress ipv4 = InetAddress.getByName("1.2.3.4");
161161
return new Object[][] {
162-
// A present Nullable element of each datatype nested in a Tuple, with a trailing
163-
// non-nullable sibling whose bytes misalign if the present-marker is dropped.
164-
{"Tuple(Nullable(String), String)", Arrays.asList("opt", "tail")},
165-
{"Tuple(Nullable(FixedString(3)), String)", Arrays.asList("abc", "tail")},
166-
{"Tuple(Nullable(Int8), String)", Arrays.asList((byte) -5, "tail")},
167-
{"Tuple(Nullable(UInt8), String)", Arrays.asList((short) 200, "tail")},
168-
{"Tuple(Nullable(Int16), String)", Arrays.asList((short) -1600, "tail")},
169-
{"Tuple(Nullable(UInt16), String)", Arrays.asList(40000, "tail")},
170-
{"Tuple(Nullable(Int32), String)", Arrays.asList(42, "tail")},
171-
{"Tuple(Nullable(UInt32), String)", Arrays.asList(4_000_000_000L, "tail")},
172-
{"Tuple(Nullable(Int64), String)", Arrays.asList(-64L, "tail")},
173-
{"Tuple(Nullable(UInt64), String)", Arrays.asList(BigInteger.valueOf(64), "tail")},
174-
{"Tuple(Nullable(Float32), String)", Arrays.asList(1.5f, "tail")},
175-
{"Tuple(Nullable(Float64), String)", Arrays.asList(2.5d, "tail")},
176-
{"Tuple(Nullable(Bool), String)", Arrays.asList(true, "tail")},
177-
{"Tuple(Nullable(UUID), String)", Arrays.asList(uuid, "tail")},
178-
{"Tuple(Nullable(Date), String)", Arrays.asList(LocalDate.of(2021, 2, 3), "tail")},
179-
{"Tuple(Nullable(Decimal64(4)), String)", Arrays.asList(new BigDecimal("1.2345"), "tail")},
180-
{"Tuple(Nullable(IPv4), String)", Arrays.asList(ipv4, "tail")},
162+
// Each present Nullable element sits in the MIDDLE of the schema: a non-nullable
163+
// leading column, the Nullable, then a trailing non-nullable Float64. If the
164+
// present-marker byte is dropped, every following byte shifts and the trailing
165+
// Float64 reads a wrong value, so a faulty serialization is detected positionally
166+
// rather than only by running out of bytes. The assertion compares the whole row.
167+
{"Tuple(Int32, Nullable(String), Float64)", Arrays.asList(7, "opt", 9.5d)},
168+
{"Tuple(Int32, Nullable(FixedString(3)), Float64)", Arrays.asList(7, "abc", 9.5d)},
169+
{"Tuple(Int32, Nullable(Int8), Float64)", Arrays.asList(7, (byte) -5, 9.5d)},
170+
{"Tuple(Int32, Nullable(UInt8), Float64)", Arrays.asList(7, (short) 200, 9.5d)},
171+
{"Tuple(Int32, Nullable(Int16), Float64)", Arrays.asList(7, (short) -1600, 9.5d)},
172+
{"Tuple(Int32, Nullable(UInt16), Float64)", Arrays.asList(7, 40000, 9.5d)},
173+
{"Tuple(Int32, Nullable(Int32), Float64)", Arrays.asList(7, 42, 9.5d)},
174+
{"Tuple(Int32, Nullable(UInt32), Float64)", Arrays.asList(7, 4_000_000_000L, 9.5d)},
175+
{"Tuple(Int32, Nullable(Int64), Float64)", Arrays.asList(7, -64L, 9.5d)},
176+
{"Tuple(Int32, Nullable(UInt64), Float64)", Arrays.asList(7, BigInteger.valueOf(64), 9.5d)},
177+
{"Tuple(Int32, Nullable(Float32), Float64)", Arrays.asList(7, 1.5f, 9.5d)},
178+
{"Tuple(Int32, Nullable(Float64), Float64)", Arrays.asList(7, 2.5d, 9.5d)},
179+
{"Tuple(Int32, Nullable(Bool), Float64)", Arrays.asList(7, true, 9.5d)},
180+
{"Tuple(Int32, Nullable(UUID), Float64)", Arrays.asList(7, uuid, 9.5d)},
181+
{"Tuple(Int32, Nullable(Date), Float64)", Arrays.asList(7, LocalDate.of(2021, 2, 3), 9.5d)},
182+
{"Tuple(Int32, Nullable(Decimal64(4)), Float64)", Arrays.asList(7, new BigDecimal("1.2345"), 9.5d)},
183+
{"Tuple(Int32, Nullable(IPv4), Float64)", Arrays.asList(7, ipv4, 9.5d)},
181184

182185
// A Tuple value given as a Java array (not a List) takes the other branch of
183-
// serializeTupleData, which is routed through the same nested-marker path.
184-
{"Tuple(Nullable(String), String)", new Object[] {"opt", "tail"}},
185-
186-
// The same marker handling on the Map value path, across a range of widths.
187-
{"Map(String, Nullable(String))", newMap("k", "v")},
188-
{"Map(String, Nullable(Int32))", newMap("k", 32)},
189-
{"Map(String, Nullable(Float64))", newMap("k", 2.5d)},
190-
{"Map(String, Nullable(UUID))", newMap("k", uuid)},
191-
192-
// Null elements/values still serialize a single null-marker byte.
193-
{"Tuple(Nullable(String), String)", Arrays.asList(null, "tail")},
194-
{"Tuple(Nullable(Int32), String)", Arrays.asList(null, "tail")},
195-
{"Tuple(Nullable(Int32), Nullable(String))", Arrays.asList(null, null)},
196-
{"Map(String, Nullable(String))", newMap("k", null)},
186+
// serializeTupleData, which is routed through the same nested-marker path, for
187+
// both a present value and a null.
188+
{"Tuple(Int32, Nullable(String), Float64)", new Object[] {7, "opt", 9.5d}},
189+
{"Tuple(Int32, Nullable(String), Float64)", new Object[] {7, null, 9.5d}},
190+
191+
// The Map value path: the Nullable map value sits between the key and a trailing
192+
// Float64, so a dropped value-marker misaligns the float.
193+
{"Tuple(Int32, Map(String, Nullable(String)), Float64)", Arrays.asList(7, newMap("k", "v"), 9.5d)},
194+
{"Tuple(Int32, Map(String, Nullable(Int32)), Float64)", Arrays.asList(7, newMap("k", 32), 9.5d)},
195+
{"Tuple(Int32, Map(String, Nullable(Float64)), Float64)", Arrays.asList(7, newMap("k", 2.5d), 9.5d)},
196+
{"Tuple(Int32, Map(String, Nullable(UUID)), Float64)", Arrays.asList(7, newMap("k", uuid), 9.5d)},
197+
198+
// Null elements/values still serialize a single null-marker byte; the trailing
199+
// Float64 confirms the following data stays aligned.
200+
{"Tuple(Int32, Nullable(String), Float64)", Arrays.asList(7, null, 9.5d)},
201+
{"Tuple(Int32, Nullable(Int32), Nullable(String), Float64)", Arrays.asList(7, null, null, 9.5d)},
202+
{"Tuple(Int32, Map(String, Nullable(String)), Float64)", Arrays.asList(7, newMap("k", null), 9.5d)},
197203

198204
// Containers compose: marker handling threads through nested Tuple/Map/Array,
199-
// including Array(Tuple(Nullable)) which is how Nested columns are encoded.
200-
{"Tuple(String, Map(String, Nullable(String)))", Arrays.asList("id", newMap("k1", "v1", "k2", null))},
201-
{"Tuple(Nullable(String), Map(String, Nullable(Int32)))", Arrays.asList("opt", newMap("k", 7))},
202-
{"Array(Tuple(Nullable(String), String))", Arrays.asList(Arrays.asList("a", "b"), Arrays.asList(null, "c"))},
203-
{"Tuple(Array(Nullable(Int32)), String)", Arrays.asList(Arrays.asList(1, null, 3), "tail")},
204-
205-
// Contrast: non-nullable nested elements must keep serializing without a marker.
206-
{"Tuple(Int32, String)", Arrays.asList(7, "tail")},
207-
{"Map(String, String)", newMap("k", "v")},
205+
// including Array(Tuple(Nullable)) which is how Nested columns are encoded. A
206+
// trailing Float64 after each nested container detects misalignment.
207+
{"Array(Tuple(Int32, Nullable(String), Float64))",
208+
Arrays.asList(Arrays.asList(7, "a", 9.5d), Arrays.asList(7, null, 8.5d))},
209+
{"Tuple(String, Map(String, Nullable(Int32)), Float64)",
210+
Arrays.asList("id", newMap("k1", 7, "k2", null), 9.5d)},
211+
{"Tuple(Array(Nullable(Int32)), Float64)", Arrays.asList(Arrays.asList(1, null, 3), 9.5d)},
212+
213+
// Contrast: non-nullable nested elements must keep serializing without a marker,
214+
// so these rows round-trip identically with or without the fix.
215+
{"Tuple(Int32, String, Float64)", Arrays.asList(7, "tail", 9.5d)},
216+
{"Tuple(Int32, Map(String, String), Float64)", Arrays.asList(7, newMap("k", "v"), 9.5d)},
208217
};
209218
}
210219

0 commit comments

Comments
 (0)