Skip to content

Commit 76722bb

Browse files
chore: add isFieldNull method to flat record traversal nodes (#754)
1 parent 0afdd80 commit 76722bb

File tree

5 files changed

+76
-1
lines changed

5 files changed

+76
-1
lines changed

hollow/src/main/java/com/netflix/hollow/core/write/HollowObjectWriteRecord.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ public void reset() {
8686
}
8787
}
8888

89+
/**
90+
* !!WARNING!! There is a bug in HollowObjectWriteRecord.writeDataTo(ByteDataArray buf)
91+
* that causes it to incorrectly serialize variable-length fields (e.g. STRING, BYTES) that
92+
* have been set to null using this method. These fields are written out as a byte array of
93+
* `[1, 0x80]` instead of the expected `[0x80]`. In non-null values, the leading byte
94+
* indicates the length of the field. It is incorrectly written as `1` for null values. This
95+
* only affects use case that copy HollowObjectWriteRecord data to another buffer, it DOES NOT
96+
* affect typical Hollow Producer based mechanisms.
97+
*/
8998
public void setNull(String fieldName) {
9099
int fieldIndex = getSchema().getPosition(fieldName);
91100

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordOrdinalReader.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,39 @@ public byte[] readFieldBytes(int ordinal, String field) {
249249
return b;
250250
}
251251

252+
public boolean isNull(int ordinal, String field) {
253+
HollowSchema schema = readSchema(ordinal);
254+
if (schema.getSchemaType() != HollowSchema.SchemaType.OBJECT) {
255+
throw new IllegalArgumentException(String.format("Ordinal %d is not an OBJECT type (found %s)", ordinal, schema.getSchemaType()));
256+
}
257+
258+
HollowObjectSchema.FieldType fieldType = ((HollowObjectSchema) schema).getFieldType(field);
259+
if (fieldType == null) {
260+
return true; // Field does not exist
261+
}
262+
263+
int offset = skipToField(ordinal, fieldType, field);
264+
if (offset == -1) {
265+
return true; // Field does not exist
266+
}
267+
268+
switch (fieldType) {
269+
case BOOLEAN:
270+
case INT:
271+
case LONG:
272+
case REFERENCE:
273+
case BYTES:
274+
case STRING:
275+
return VarInt.readVNull(record.data, offset);
276+
case FLOAT:
277+
return record.data.readIntBits(offset) == HollowObjectWriteRecord.NULL_FLOAT_BITS;
278+
case DOUBLE:
279+
return record.data.readLongBits(offset) == HollowObjectWriteRecord.NULL_DOUBLE_BITS;
280+
default:
281+
throw new IllegalArgumentException("Unsupported field type: " + fieldType);
282+
}
283+
}
284+
252285
private int skipToField(int ordinal, HollowObjectSchema.FieldType fieldType, String field) {
253286
int offset = getOrdinalOffset(ordinal);
254287

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalObjectNode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public FlatRecordTraversalMapNode getMapFieldNode(String field) {
5858
return (FlatRecordTraversalMapNode) getFieldNode(field);
5959
}
6060

61+
public boolean isFieldNull(String field) {
62+
return reader.isNull(ordinal, field);
63+
}
64+
6165
public FlatRecordTraversalNode getFieldNode(String field) {
6266
HollowObjectSchema.FieldType fieldType = schema.getFieldType(field);
6367
if (fieldType == null) {

hollow/src/test/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalNodeTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.netflix.hollow.core.write.objectmapper.flatrecords.traversal;
22

3+
import com.netflix.hollow.core.schema.HollowObjectSchema;
34
import com.netflix.hollow.core.schema.SimpleHollowDataset;
45
import com.netflix.hollow.core.util.HollowWriteStateCreator;
6+
import com.netflix.hollow.core.write.HollowObjectWriteRecord;
57
import com.netflix.hollow.core.write.objectmapper.HollowObjectMapper;
68
import com.netflix.hollow.core.write.objectmapper.flatrecords.FakeHollowSchemaIdentifierMapper;
79
import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecord;
@@ -18,6 +20,7 @@
1820
import org.junit.Test;
1921

2022
import java.util.ArrayList;
23+
import java.util.Collections;
2124
import java.util.HashMap;
2225
import java.util.HashSet;
2326
import java.util.Map;
@@ -455,6 +458,33 @@ public void testWalkKitchenSinkFlatRecordUsingSchemaHints() {
455458
tuple(70, 72, 73L, 74.0f));
456459
}
457460

461+
@Test
462+
public void testObjectNodeFieldNullability() {
463+
HollowObjectSchema schema = new HollowObjectSchema("TestSchema", 8);
464+
schema.addField("f1", HollowObjectSchema.FieldType.REFERENCE, "TestReference");
465+
schema.addField("f2", HollowObjectSchema.FieldType.INT);
466+
schema.addField("f3", HollowObjectSchema.FieldType.LONG);
467+
schema.addField("f4", HollowObjectSchema.FieldType.BOOLEAN);
468+
schema.addField("f5", HollowObjectSchema.FieldType.FLOAT);
469+
schema.addField("f6", HollowObjectSchema.FieldType.DOUBLE);
470+
schema.addField("f7", HollowObjectSchema.FieldType.STRING);
471+
schema.addField("f8", HollowObjectSchema.FieldType.BYTES);
472+
473+
HollowObjectWriteRecord record = new HollowObjectWriteRecord(schema);
474+
475+
SimpleHollowDataset dataset = new SimpleHollowDataset(Collections.singletonList(schema));
476+
FlatRecordWriter flatRecordWriter = new FlatRecordWriter(dataset, new FakeHollowSchemaIdentifierMapper(dataset));
477+
flatRecordWriter.write(schema, record);
478+
FlatRecord flatRecord = flatRecordWriter.generateFlatRecord();
479+
480+
FlatRecordTraversalObjectNode node = new FlatRecordTraversalObjectNode(flatRecord);
481+
for (int i = 0; i < schema.numFields(); i++) {
482+
String fieldName = schema.getFieldName(i);
483+
assertThat(node.isFieldNull(fieldName))
484+
.as("Field '%s' should be null", fieldName)
485+
.isTrue();
486+
}
487+
}
458488

459489
private FlatRecord createMovieFlatRecord() {
460490
Movie movie1 = new Movie();

hollow/src/test/java/com/netflix/hollow/test/dto/kitchensink/KitchenSink.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ public class KitchenSink {
6363
public Map<MapKey, SubType> subTypeMap;
6464
public Map<ComplexMapKey, SubType> complexMapKeyMap;
6565

66-
6766
@HollowHashKey(fields = {"value1"})
6867
public Set<HashableKey> hashableSet;
6968
@HollowHashKey(fields = {"value1"})

0 commit comments

Comments
 (0)