You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Expression.MapLiteral keeps its pairs in a Map<Literal, Literal>. The proto message it round-trips does not, so a class of valid plans cannot be imported at all.
key_values is a repeated field with no uniqueness constraint, so a literal map that repeats a key - {1: 'a', 1: 'b'} - is a well-formed Substrait plan. A Map<Literal, Literal> cannot hold one.
What happens today
ProtoExpressionConverter collects the pairs with Collectors.toMap:
import fails with a bare IllegalStateException: Duplicate key ... from inside the stream collector
import it faithfully, or fail with an error that names the offending key
distinct keys, producer order
pairs come back reordered - toMap collects into a HashMap
key_values order survives the round trip
So this is worse than lossy: a valid plan fails to import rather than round-tripping imperfectly.
Two things are not affected:
EmptyMapLiteral - it carries only the key and value types, no pairs.
Export (ExpressionProtoConverter) - it cannot produce a bad plan, because the POJO it reads from can never hold a repeated key in the first place. That is the same limitation seen from the other side: a producer built on the POJO model has no way to emit a repeated-key map literal.
MapLiteral is the remaining place where the limitation lives.
Two ways out
A. Make the representation match the proto. Replace values() with an ordered list of literal key/value pairs, mirroring NestedMap.
Fixes both symptoms - repeated keys are representable and pair order is preserved.
Breaking change to a public API that predates this repo's current shape, and it touches every consumer of MapLiteral (ExpressionCreator.map, both proto converters, isthmus ExpressionRexConverter, spark ToSparkExpression). This is why it was left out of feat(core)!: support nested map expressions and import nested structs #1062.
B. Keep the Map and make the limitation explicit. Import fails with a message naming the duplicate key instead of a bare IllegalStateException, and the pairs are collected into a LinkedHashMap so distinct-key plans at least keep their order.
Not a breaking change, and it turns a confusing failure into a clear one.
Expression.MapLiteralkeeps its pairs in aMap<Literal, Literal>. The proto message it round-trips does not, so a class of valid plans cannot be imported at all.The proto allows repeated keys
key_valuesis a repeated field with no uniqueness constraint, so a literal map that repeats a key -{1: 'a', 1: 'b'}- is a well-formed Substrait plan. AMap<Literal, Literal>cannot hold one.What happens today
ProtoExpressionConvertercollects the pairs withCollectors.toMap:https://github.com/substrait-io/substrait-java/blob/main/core/src/main/java/io/substrait/expression/proto/ProtoExpressionConverter.java#L580-L584
{1: 'a', 1: 'b'}IllegalStateException: Duplicate key ...from inside the stream collectortoMapcollects into aHashMapkey_valuesorder survives the round tripSo this is worse than lossy: a valid plan fails to import rather than round-tripping imperfectly.
Two things are not affected:
EmptyMapLiteral- it carries only the key and value types, no pairs.ExpressionProtoConverter) - it cannot produce a bad plan, because the POJO it reads from can never hold a repeated key in the first place. That is the same limitation seen from the other side: a producer built on the POJO model has no way to emit a repeated-key map literal.Related
Expression.NestedMapwas given an orderedList<NestedMap.KeyValue>for exactly this reason (feat(core)!: support nested map expressions and import nested structs #1062).SqlMapValueConstructorCallConverterstops collapsing a repeated-keyMAP[...]into aMapLiteraland emits aNestedMapinstead, for the same reason (feat(isthmus): convert nested struct and map expressions to and from Calcite #1063).MapLiteralis the remaining place where the limitation lives.Two ways out
A. Make the representation match the proto. Replace
values()with an ordered list of literal key/value pairs, mirroringNestedMap.MapLiteral(ExpressionCreator.map, both proto converters, isthmusExpressionRexConverter, sparkToSparkExpression). This is why it was left out of feat(core)!: support nested map expressions and import nested structs #1062.B. Keep the
Mapand make the limitation explicit. Import fails with a message naming the duplicate key instead of a bareIllegalStateException, and the pairs are collected into aLinkedHashMapso distinct-key plans at least keep their order.