-
Notifications
You must be signed in to change notification settings - Fork 1
Add support for new native types #26
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: main
Are you sure you want to change the base?
Changes from all commits
40bee8d
3e2ddcc
a3dbd93
a3100e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,13 @@ | |
| import com.imprint.util.VarInt; | ||
| import lombok.experimental.UtilityClass; | ||
|
|
||
| import java.nio.ByteBuffer; | ||
| import java.math.BigDecimal; | ||
| import java.math.BigInteger; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.time.Instant; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalTime; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
| * Static primitive deserialization methods for all Imprint types. | ||
|
|
@@ -49,27 +55,65 @@ public static Object deserializePrimitive(ImprintBuffer buffer, TypeCode typeCod | |
| } | ||
| return buffer.getDouble(); | ||
| case BYTES: | ||
| VarInt.DecodeResult lengthResult = VarInt.decode(buffer); | ||
| int length = lengthResult.getValue(); | ||
| if (buffer.remaining() < length) { | ||
| int byteLength = VarInt.decode(buffer).getValue(); | ||
| if (buffer.remaining() < byteLength) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, | ||
| "Not enough bytes for bytes value data after VarInt. Needed: " + | ||
| length + ", available: " + buffer.remaining()); | ||
| byteLength + ", available: " + buffer.remaining()); | ||
| } | ||
| byte[] bytes = new byte[length]; | ||
| byte[] bytes = new byte[byteLength]; | ||
| buffer.get(bytes); | ||
| return bytes; | ||
| case STRING: | ||
| VarInt.DecodeResult strLengthResult = VarInt.decode(buffer); | ||
| int strLength = strLengthResult.getValue(); | ||
| int strLength = VarInt.decode(buffer).getValue(); | ||
| if (buffer.remaining() < strLength) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, | ||
| "Not enough bytes for string value data after VarInt. Needed: " + | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, | ||
| "Not enough bytes for string value data after VarInt. Needed: " + | ||
| strLength + ", available: " + buffer.remaining()); | ||
| } | ||
| byte[] stringBytes = new byte[strLength]; | ||
| buffer.get(stringBytes); | ||
| return new String(stringBytes, java.nio.charset.StandardCharsets.UTF_8); | ||
| return new String(stringBytes, StandardCharsets.UTF_8); | ||
| case DATE: | ||
| if (buffer.remaining() < 4) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, "Not enough bytes for date"); | ||
| } | ||
| int daysSinceEpoch = buffer.getInt(); | ||
| return LocalDate.ofEpochDay(daysSinceEpoch); | ||
| case TIME: | ||
| if (buffer.remaining() < 4) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, "Not enough bytes for time"); | ||
| } | ||
| int millisSinceMidnight = buffer.getInt(); | ||
| return LocalTime.ofNanoOfDay(millisSinceMidnight * 1_000_000L); | ||
| case UUID: | ||
| if (buffer.remaining() < 16) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, "Not enough bytes for UUID"); | ||
| } | ||
| long mostSignificantBits = buffer.getLong(); | ||
| long leastSignificantBits = buffer.getLong(); | ||
| return new UUID(mostSignificantBits, leastSignificantBits); | ||
| case DECIMAL: | ||
| if (buffer.remaining() < 1) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, "Not enough bytes for decimal scale"); | ||
| } | ||
| int scale = VarInt.decode(buffer).getValue(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we check buffer.remaining here? |
||
| int unscaledLength = VarInt.decode(buffer).getValue(); | ||
| if (buffer.remaining() < unscaledLength) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, | ||
| "Not enough bytes for decimal unscaled value. Needed: " + | ||
| unscaledLength + ", available: " + buffer.remaining()); | ||
| } | ||
| byte[] unscaledBytes = new byte[unscaledLength]; | ||
| buffer.get(unscaledBytes); | ||
| var unscaledValue = new BigInteger(unscaledBytes); | ||
| return new BigDecimal(unscaledValue, scale); | ||
| case TIMESTAMP: | ||
| if (buffer.remaining() < 8) { | ||
| throw new ImprintException(ErrorType.BUFFER_UNDERFLOW, "Not enough bytes for timestamp"); | ||
| } | ||
| long millisSinceEpoch = buffer.getLong(); | ||
| return Instant.ofEpochMilli(millisSinceEpoch); | ||
| default: | ||
| throw new ImprintException(ErrorType.SERIALIZATION_ERROR, "Cannot deserialize " + typeCode + " as primitive"); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,18 +3,20 @@ | |
| import com.imprint.error.ErrorType; | ||
| import com.imprint.error.ImprintException; | ||
| import com.imprint.util.ImprintBuffer; | ||
| import com.imprint.util.VarInt; | ||
| import lombok.experimental.UtilityClass; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.nio.ByteBuffer; | ||
| import java.time.Instant; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalTime; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.UUID; | ||
| import java.util.function.BiConsumer; | ||
| import java.util.function.Function; | ||
|
|
||
| /** | ||
| * Static serialization methods for all Imprint types. | ||
| * Eliminates virtual dispatch overhead from TypeHandler interface. | ||
| */ | ||
| @UtilityClass | ||
| public final class ImprintSerializers { | ||
|
|
||
|
|
@@ -55,7 +57,6 @@ public static void serializeBytes(byte[] value, ImprintBuffer buffer) { | |
| public static void serializeArray(List<?> list, ImprintBuffer buffer, Function<Object, TypeCode> typeConverter, BiConsumer<Object, ImprintBuffer> elementSerializer) | ||
| throws ImprintException { | ||
| buffer.putVarInt(list.size()); | ||
|
|
||
| if (list.isEmpty()) | ||
| return; // Empty arrays technically don't need type code | ||
|
|
||
|
|
@@ -139,6 +140,49 @@ public static void serializeNull(ImprintBuffer buffer) { | |
| // NULL values have no payload data | ||
| } | ||
|
|
||
| /** | ||
| * Serialize a LocalDate as days since Unix epoch. | ||
| */ | ||
| public static void serializeDate(LocalDate value, ImprintBuffer buffer) { | ||
| int daysSinceEpoch = (int) value.toEpochDay(); | ||
| buffer.putInt(daysSinceEpoch); | ||
| } | ||
|
|
||
| /** | ||
| * Serialize a LocalTime as milliseconds since midnight. | ||
| */ | ||
| public static void serializeTime(LocalTime value, ImprintBuffer buffer) { | ||
| int millisSinceMidnight = (int) (value.toNanoOfDay() / 1_000_000); | ||
| buffer.putInt(millisSinceMidnight); | ||
| } | ||
|
|
||
| /** | ||
| * Serialize a UUID as 16-byte binary representation. | ||
| */ | ||
| public static void serializeUuid(UUID value, ImprintBuffer buffer) { | ||
| buffer.putLong(value.getMostSignificantBits()); | ||
| buffer.putLong(value.getLeastSignificantBits()); | ||
| } | ||
|
|
||
| /** | ||
| * Serialize a BigDecimal as scale + unscaled value. | ||
| */ | ||
| public static void serializeDecimal(BigDecimal value, ImprintBuffer buffer) { | ||
| int scale = value.scale(); | ||
| byte[] unscaledBytes = value.unscaledValue().toByteArray(); | ||
|
|
||
| buffer.putVarInt(scale); | ||
| buffer.putVarInt(unscaledBytes.length); | ||
| buffer.putBytes(unscaledBytes); | ||
| } | ||
|
|
||
| /** | ||
| * Serialize an Instant as milliseconds since Unix epoch (UTC). | ||
| */ | ||
| public static void serializeTimestamp(Instant value, ImprintBuffer buffer) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @agavra I think we had mentioned that timestamp should be milliseconds and that would be milliseconds since midnight, but Avro does milliseconds since Unix epoch and I think that's what we wanted to do here too which means this would need to be 8 bytes after all. If that's the case then I can go back to the main readme and update it again and clarify - just wanted to double check this again to make sure I wasn't getting myself too confused There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we said that timestamp would be 8 bytes millis since midnight but "time" (which is time of day) would be millis since midnight. EDIT: I just looked at the README and didn't notice that we changed both to 4 bytes. Good catch, "timestamp" should definitely be 8 bytes. I'll change that in the core README as well. |
||
| long millisSinceEpoch = value.toEpochMilli(); | ||
| buffer.putLong(millisSinceEpoch); | ||
| } | ||
|
|
||
| public static int estimateSize(TypeCode typeCode, Object value) { | ||
| byte code = typeCode.getCode(); | ||
|
|
@@ -156,6 +200,17 @@ public static int estimateSize(TypeCode typeCode, Object value) { | |
| } | ||
| if (code == TypeCode.ARRAY.getCode() || code == TypeCode.MAP.getCode()) return 512; | ||
| if (code == TypeCode.ROW.getCode()) return 1024; | ||
| if (code == TypeCode.DATE.getCode()) return 4; | ||
| if (code == TypeCode.TIME.getCode()) return 4; | ||
| if (code == TypeCode.UUID.getCode()) return 16; | ||
| if (code == TypeCode.TIMESTAMP.getCode()) return 8; | ||
| if (code == TypeCode.DECIMAL.getCode()) { | ||
| var decimal = (BigDecimal) value; | ||
| byte[] unscaledBytes = decimal.unscaledValue().toByteArray(); | ||
| return VarInt.encodedLength(decimal.scale()) + | ||
| VarInt.encodedLength(unscaledBytes.length) + | ||
| unscaledBytes.length; | ||
| } | ||
| throw new IllegalArgumentException("Unknown TypeCode: " + typeCode); | ||
| } | ||
| } | ||
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.
Need to settle on and add a style file