fix: table UUID partitions not working - #2916
Conversation
|
A few notes ... Avro has an The other thing I found is with the integration tests. I found when I had multiple tests in the same file it would fail with a Reqwest + Tokio error. I believe in As a workaround I put my second test into a second file. I'd be happy to try to solve this in a followup ticket if there is interest, and any other work to make test simpler. The solution is technically straight forward, but would be a chunk of code. We are talking a custom S3 storage layer, changing OpenDal core, or moving to use |
f92395e to
de83553
Compare
blackmwk
left a comment
There was a problem hiding this comment.
Thanks @JosephLenton for this fix!
5f0221f to
6ca9fa2
Compare
|
Hey @blackmwk those changes are done. Please take another look when you have time! Btw whilst working on this I found a few items which I suspect to be bugs. I can add them as tickets later when I have time:
|
mbutrovich
left a comment
There was a problem hiding this comment.
First pass, thanks for working on this @JosephLenton!
| PrimitiveLiteral::Float(v) => RawLiteralEnum::Float(v.0), | ||
| PrimitiveLiteral::Double(v) => RawLiteralEnum::Double(v.0), | ||
| PrimitiveLiteral::String(v) => RawLiteralEnum::String(v), | ||
| PrimitiveLiteral::UInt128(v) => { |
There was a problem hiding this comment.
The crash in #2913 traces back to apache_avro's Value::validate_internal, which has match arms for (Value::String, Schema::Uuid) and (Value::Uuid, Schema::Uuid) but none for (Value::Bytes, Schema::Uuid) (apache-avro 0.21.0, types.rs around line 444-451). That diagnosis looks right.
But Schema::Uuid in apache_avro always serializes to {"type": "string", "logicalType": "uuid"} (schema.rs:2201-2206), and a Value::Uuid gets encoded as a length-prefixed string for that schema, not raw fixed bytes (encode.rs:135-142). The spec's Avro mapping for uuid is {"type": "fixed", "size": 16, "logicalType": "uuid"} (format/spec.md:936), and iceberg-java's TypeToSchema.UUID_SCHEMA builds exactly that, with UUIDWriter/UUIDReader calling encoder.writeFixed/reading a fixed 16-byte buffer.
avro/schema.rs:237 maps PrimitiveType::Uuid to AvroSchema::Uuid for the manifest schema, so doesn't this mean a manifest with a UUID partition column written after this fix ends up with that column typed as Avro string on disk, not fixed(16)? Avro string and fixed aren't resolution-compatible, so would a Java reader (or anything with a fixed(16) reader schema) fail to read it?
Would keeping this arm as Bytes (unchanged) and instead building a raw Fixed(16) schema with a logicalType: "uuid" attribute in avro/schema.rs (same pattern avro_decimal_schema already uses, wrapping Fixed for decimals) avoid the crash without changing the wire format? Value::Bytes already resolves fine against Schema::Fixed, and apache_avro's own schema parser already folds fixed(16)+logicalType=uuid back into Schema::Uuid on read, so the existing RawLiteralEnum::Bytes deserialize branch for Uuid a few lines down wouldn't need to change either.
There was a problem hiding this comment.
I have made that change (to apply the work avro/schema.rs instead of values/serde.rs), and it makes a lot more sense.
However I am finding if I use {"type": "fixed", "size": 16} works, but {"type": "fixed", "size": 16, "logicalType": "uuid"} causes Avro to panic. The latter I am pretty certain I see Java using here: https://github.com/apache/iceberg/blob/0d60b2bb8780d781f4ceb69032418d222b649ae5/core/src/main/java/org/apache/iceberg/avro/TypeToSchema.java#L54-L55
I am wondering if a change is needed in Avro on this. I am going to have to go away and investigate further and come back.
There was a problem hiding this comment.
Avro 0.21 seems to have a bug in it's Schema reading code handling {"type": "fixed", "size": 16, "logicalType": "uuid"}, specifically the logicalType: uuid part. It reads the type as Schema::UUID and transforms that to Schema::Bytes, which is wrong and mismatches in other parts causing a panic.
I'm pretty certain this is fixed in version 0.22, however that is not yet released. The release process is ongoing now.
I will come back to this PR once that is out.
There was a problem hiding this comment.
Avro 0.21 seems to have a bug in it's Schema reading code handling
{"type": "fixed", "size": 16, "logicalType": "uuid"}, specifically thelogicalType: uuidpart. It reads the type asSchema::UUIDand transforms that toSchema::Bytes, which is wrong and mismatches in other parts causing a panic.I'm pretty certain this is fixed in version 0.22, however that is not yet released. The release process is ongoing now.
I will come back to this PR once that is out.
Thanks for the initial round of revision @JosephLenton! Since we're blocked on a dependency, should we set this to draft? Then we can tag/request reviews from folks again when we're ready?
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_insert_into_partitioned_by_uuid() -> Result<()> { |
There was a problem hiding this comment.
This round-trips entirely through iceberg-rust's own reader, so it wouldn't catch a mismatch between the Avro type written for the UUID column and what the spec (or another implementation) expects, since iceberg-rust just reads back what it wrote. Would it be worth asserting on the raw manifest's Avro schema for the partition field here, given that's the part actually in question?
There was a problem hiding this comment.
This I have added on my local branch and will push up.
6ca9fa2 to
a9fbd60
Compare
| GlobalTestFixture { catalog_config } | ||
| } | ||
|
|
||
| pub async fn rest_catalog(&self) -> RestCatalog { |
| /// | ||
| /// The Iceberg spec maps `uuid` to `{"type": "fixed", "size": 16, "logicalType": "uuid"}`, | ||
| /// i.e. 16 raw bytes, which is what iceberg-java writes. | ||
| fn avro_uuid_schema() -> Result<AvroSchema> { |
There was a problem hiding this comment.
I'm confused, how is this different from AvroSchema::Uuid?
There was a problem hiding this comment.
Also this pr only changed iceberg schema to avro schema, I think we should also do vice versa?
| // SingleValueParser enforces the same (DateTimeUtil.isUTCTimestamptz). A non-UTC offset is not a | ||
| // valid encoding and must be rejected, not silently re-based to UTC. | ||
| let record = serde_json::Value::String("2017-11-16T22:31:08.123456789+05:00".to_string()); | ||
| let record = JsonValue::String("2017-11-16T22:31:08.123456789+05:00".to_string()); |
Which issue does this PR close?
Table Partitions work on writing with UUID values.
What changes are included in this PR?
PrimitiveLiteral::UInt128will now convert to aRawLiteralEnum::String, when the type is UUID.RawLiteralEnumfor UUID deserialisation.Are these changes tested?
I added: