-
Notifications
You must be signed in to change notification settings - Fork 235
Impro/type resolver #760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Impro/type resolver #760
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Adds support for mapping additional Java time/utility types (Instant, UUID, LocalDate) in HollowObjectTypeMapper along with corresponding read/write logic and tests. Introduces new MappedFieldType enum entries and special handling for multi-field reconstruction plus test coverage for serialization round-trips.
- Add special wrapper handling for Instant, UUID, LocalDate (schema mapping, copy, parse).
- Extend tests to validate new types individually and inside a composite type.
- Introduce dedicated test POJO TypeWithSpecialTypes.
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| HollowObjectMapperTest.java | Adds tests for Instant, UUID, and composite type containing special types. |
| HollowObjectMapperHollowRecordParserTest.java | Extends wrapper types test to include Instant. |
| TypeWithSpecialTypes.java | New test POJO holding Date, Instant, UUID, LocalDate. |
| HollowObjectTypeMapper.java | Core changes: new special wrapper set, parsing/copy logic, new enum constants for multi-field types. |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| int nanoPosInPojoSchema = schema.getPosition(MappedFieldType.INSTANT_SECONDS.getSpecialFieldName()); | ||
| if(nanoPosInPojoSchema != -1) { | ||
| int nanoValue = objectNode.getFieldValueInt(MappedFieldType.INSTANT_SECONDS.getSpecialFieldName()); | ||
| if(nanoValue != Long.MIN_VALUE) { |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nanos branch incorrectly uses INSTANT_SECONDS for both the position lookup and field access, and compares an int to Long.MIN_VALUE instead of Integer.MIN_VALUE. Use INSTANT_NANOS and compare against Integer.MIN_VALUE.
| int nanoPosInPojoSchema = schema.getPosition(MappedFieldType.INSTANT_SECONDS.getSpecialFieldName()); | |
| if(nanoPosInPojoSchema != -1) { | |
| int nanoValue = objectNode.getFieldValueInt(MappedFieldType.INSTANT_SECONDS.getSpecialFieldName()); | |
| if(nanoValue != Long.MIN_VALUE) { | |
| int nanoPosInPojoSchema = schema.getPosition(MappedFieldType.INSTANT_NANOS.getSpecialFieldName()); | |
| if(nanoPosInPojoSchema != -1) { | |
| int nanoValue = objectNode.getFieldValueInt(MappedFieldType.INSTANT_NANOS.getSpecialFieldName()); | |
| if(nanoValue != Integer.MIN_VALUE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah this seems like a bug
| return Long.valueOf(((Date)obj).getTime()); | ||
| //TODO figure out when it's used | ||
| case UUID_LEAST_SIG_BITS: | ||
| return Long.valueOf(((UUID)obj).getLeastSignificantBits()); |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only UUID_LEAST_SIG_BITS is handled in retrieveFieldValue; missing cases for UUID_MOST_SIG_BITS, INSTANT_SECONDS, INSTANT_NANOS, LOCAL_DATE_* leading to inconsistent behavior when these fields are requested (they will fall through to default). Add symmetrical handling (or document why not needed) for all newly introduced MappedFieldType entries.
| return Long.valueOf(((UUID)obj).getLeastSignificantBits()); | |
| return Long.valueOf(((UUID)obj).getLeastSignificantBits()); | |
| case UUID_MOST_SIG_BITS: | |
| return Long.valueOf(((UUID)obj).getMostSignificantBits()); | |
| case INSTANT_SECONDS: | |
| return Long.valueOf(((Instant)obj).getEpochSecond()); | |
| case INSTANT_NANOS: | |
| return Integer.valueOf(((Instant)obj).getNano()); | |
| case LOCAL_DATE_YEAR: | |
| return Integer.valueOf(((LocalDate)obj).getYear()); | |
| case LOCAL_DATE_MONTH: | |
| return Integer.valueOf(((LocalDate)obj).getMonthValue()); | |
| case LOCAL_DATE_DAY: | |
| return Integer.valueOf(((LocalDate)obj).getDayOfMonth()); |
| } | ||
| else if (SPECIAL_WRAPPERS.contains(clazz)) { |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The special wrapper reconstruction logic for UUID, Instant, and LocalDate is duplicated later in parseFlatRecord; consider extracting a shared helper (e.g., rebuildSpecialWrapper(clazz, schema, accessor)) to reduce duplication and risk of divergence (already evident in the Instant nanos bug in the flat record path).
| if (year != null && month != null && day != null) { | ||
| obj = LocalDate.of(year, month, day); | ||
| } | ||
| } |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The special wrapper reconstruction logic for UUID, Instant, and LocalDate is duplicated later in parseFlatRecord; consider extracting a shared helper (e.g., rebuildSpecialWrapper(clazz, schema, accessor)) to reduce duplication and risk of divergence (already evident in the Instant nanos bug in the flat record path).
|
|
||
| long time = System.currentTimeMillis(); | ||
| Date date = new Date(time); | ||
| // what if the DateTimeException is thrown? |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The comment is misleading—Instant.now() does not throw DateTimeException under normal circumstances. Remove or clarify to avoid confusion.
| // what if the DateTimeException is thrown? |
| String schemeText = baos.toString(); | ||
| Assert.assertTrue(schemeText.contains("date")); | ||
| Assert.assertTrue(schemeText.contains("instant")); | ||
| Assert.assertTrue(schemeText.contains("uuid")); | ||
| Assert.assertTrue(schemeText.contains("localDate")); |
Copilot
AI
Sep 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] schemeText appears to be a misspelling of schemaText used to hold schema contents; rename to schemaText for accuracy.
| String schemeText = baos.toString(); | |
| Assert.assertTrue(schemeText.contains("date")); | |
| Assert.assertTrue(schemeText.contains("instant")); | |
| Assert.assertTrue(schemeText.contains("uuid")); | |
| Assert.assertTrue(schemeText.contains("localDate")); | |
| String schemaText = baos.toString(); | |
| Assert.assertTrue(schemaText.contains("date")); | |
| Assert.assertTrue(schemaText.contains("instant")); | |
| Assert.assertTrue(schemaText.contains("uuid")); | |
| Assert.assertTrue(schemaText.contains("localDate")); |
| } | ||
| int leastSigBitsPosInPojoSchema = schema.getPosition(MappedFieldType.UUID_LEAST_SIG_BITS.getSpecialFieldName()); | ||
| if(leastSigBitsPosInPojoSchema != -1) { | ||
| long value = hollowObject.getLong(MappedFieldType.UUID_LEAST_SIG_BITS.getSpecialFieldName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not specifically for this PR, but a note on performance: since we already have a field position here I wonder if we can avoid the string match on field name again cc @eduardoramirez
| leastSigBits = value; | ||
| } | ||
| } | ||
| if (mostSigBits != null && leastSigBits != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there are 2 long components, having Long.MIN_VALUE as a sentinel effectively means certain UUIDs where one of the long blocks is 2^64 are usable. Effecitvely, 2^64 + 2^64 UUIDs will fall into this edge case, unlikely to ever hit this edge case but wonder if it can be handled better
| ENUM_NAME(FieldType.STRING, "_name"), | ||
| DATE_TIME(FieldType.LONG, "value"); | ||
| DATE_TIME(FieldType.LONG, "value"), | ||
| UUID_MOST_SIG_BITS(FieldType.LONG, "mostSigBits"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably namespace the long components of UUID into their own custom type name, otherwise it will undo the compression benefits for all other long values in the dataset.. since encoding a long normally only #bits required to encode the largest long value, so if UUID is also using the long type then #bits required to encode any long will be 64.
| catch(Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } else if(clazz == UUID.class) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we plan to also implement these in the generated HollowAPI? Atm clients would need something like
com.netflix.foo.generated.UUID uuid = someHollowObject.getMyUuidField();
// System.out.println(uuid); // mostSigBits: -8232749396958297039 leastSigBits: -7121165017364359242
java.util.UUID javaUUID = new java.util.UUID(uuid.getMostSigBits(), uuid.getLeastSigBits());
System.out.println(javaUUID); // 8dbf660a-dd7d-4431-9d2c-8a25710f17b6
| int nanoPosInPojoSchema = schema.getPosition(MappedFieldType.INSTANT_SECONDS.getSpecialFieldName()); | ||
| if(nanoPosInPojoSchema != -1) { | ||
| int nanoValue = objectNode.getFieldValueInt(MappedFieldType.INSTANT_SECONDS.getSpecialFieldName()); | ||
| if(nanoValue != Long.MIN_VALUE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah this seems like a bug
| case FLOAT: | ||
| return (int)fixedLengthValue == HollowObjectWriteRecord.NULL_FLOAT_BITS; | ||
| case DOUBLE: | ||
| case UUID_LONG: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this field for avoiding making all LONGs to take 64 bits. But it will be backward incompatible for namespaces using UUID already.
1. Old producer (LONG) → New consumer (UUID_LONG): The consumer will see a field type mismatch and throw IncompatibleSchemaException
2. New producer (UUID_LONG) → Old consumer (LONG): The old consumer will see a field type mismatch and throw IncompatibleSchemaException
No description provided.