Skip to content

Commit 45d9387

Browse files
committed
feat: add support for Expression.EmptyMapLiteral
1 parent ac0b7d1 commit 45d9387

File tree

10 files changed

+71
-3
lines changed

10 files changed

+71
-3
lines changed

core/src/main/java/io/substrait/expression/AbstractExpressionVisitor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ public OUTPUT visit(Expression.MapLiteral expr) throws EXCEPTION {
129129
return visitFallback(expr);
130130
}
131131

132+
@Override
133+
public OUTPUT visit(Expression.EmptyMapLiteral expr) throws EXCEPTION {
134+
return visitFallback(expr);
135+
}
136+
132137
@Override
133138
public OUTPUT visit(Expression.ListLiteral expr) throws EXCEPTION {
134139
return visitFallback(expr);

core/src/main/java/io/substrait/expression/Expression.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,25 @@ public <R, E extends Throwable> R accept(ExpressionVisitor<R, E> visitor) throws
498498
}
499499
}
500500

501+
@Value.Immutable
502+
abstract static class EmptyMapLiteral implements Literal {
503+
public abstract Type keyType();
504+
505+
public abstract Type valueType();
506+
507+
public Type getType() {
508+
return Type.withNullability(nullable()).map(keyType(), valueType());
509+
}
510+
511+
public static ImmutableExpression.EmptyMapLiteral.Builder builder() {
512+
return ImmutableExpression.EmptyMapLiteral.builder();
513+
}
514+
515+
public <R, E extends Throwable> R accept(ExpressionVisitor<R, E> visitor) throws E {
516+
return visitor.visit(this);
517+
}
518+
}
519+
501520
@Value.Immutable
502521
abstract static class ListLiteral implements Literal {
503522
public abstract List<Literal> values();

core/src/main/java/io/substrait/expression/ExpressionCreator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,15 @@ public static Expression.MapLiteral map(
252252
return Expression.MapLiteral.builder().nullable(nullable).putAllValues(values).build();
253253
}
254254

255+
public static Expression.EmptyMapLiteral emptyMap(
256+
boolean nullable, Type keyType, Type valueType) {
257+
return Expression.EmptyMapLiteral.builder()
258+
.keyType(keyType)
259+
.valueType(valueType)
260+
.nullable(nullable)
261+
.build();
262+
}
263+
255264
public static Expression.ListLiteral list(boolean nullable, Expression.Literal... values) {
256265
return Expression.ListLiteral.builder().nullable(nullable).addValues(values).build();
257266
}

core/src/main/java/io/substrait/expression/ExpressionVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public interface ExpressionVisitor<R, E extends Throwable> {
5353

5454
R visit(Expression.MapLiteral expr) throws E;
5555

56+
R visit(Expression.EmptyMapLiteral expr) throws E;
57+
5658
R visit(Expression.ListLiteral expr) throws E;
5759

5860
R visit(Expression.EmptyListLiteral expr) throws E;

core/src/main/java/io/substrait/expression/proto/ExpressionProtoConverter.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,21 @@ public Expression visit(io.substrait.expression.Expression.MapLiteral expr) {
241241
});
242242
}
243243

244+
@Override
245+
public Expression visit(io.substrait.expression.Expression.EmptyMapLiteral expr) {
246+
return lit(
247+
bldr -> {
248+
var protoMapType = expr.getType().accept(typeProtoConverter);
249+
bldr.setEmptyMap(protoMapType.getMap())
250+
// For empty maps, the Literal message's own nullable field should be ignored
251+
// in favor of the nullability of the Type.Map in the literal's
252+
// empty_map field. But for safety we set the literal's nullable field
253+
// to match in case any readers either look in the wrong location
254+
// or want to verify that they are consistent.
255+
.setNullable(expr.nullable());
256+
});
257+
}
258+
244259
@Override
245260
public Expression visit(io.substrait.expression.Expression.ListLiteral expr) {
246261
return lit(

core/src/main/java/io/substrait/expression/proto/ProtoExpressionConverter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ public Expression.Literal from(io.substrait.proto.Expression.Literal literal) {
391391
literal.getNullable(),
392392
literal.getMap().getKeyValuesList().stream()
393393
.collect(Collectors.toMap(kv -> from(kv.getKey()), kv -> from(kv.getValue()))));
394+
case EMPTY_MAP -> {
395+
// literal.getNullable() is intentionally ignored in favor of the nullability
396+
// specified in the literal.getEmptyMap() type.
397+
var mapType = protoTypeConverter.fromMap(literal.getEmptyMap());
398+
yield ExpressionCreator.emptyMap(mapType.nullable(), mapType.key(), mapType.value());
399+
}
394400
case UUID -> ExpressionCreator.uuid(literal.getNullable(), literal.getUuid());
395401
case NULL -> ExpressionCreator.typedNull(protoTypeConverter.from(literal.getNull()));
396402
case LIST -> ExpressionCreator.list(

core/src/main/java/io/substrait/relation/ExpressionCopyOnWriteVisitor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ public Optional<Expression> visit(Expression.MapLiteral expr) throws EXCEPTION {
153153
return visitLiteral(expr);
154154
}
155155

156+
@Override
157+
public Optional<Expression> visit(Expression.EmptyMapLiteral expr) throws EXCEPTION {
158+
return visitLiteral(expr);
159+
}
160+
156161
@Override
157162
public Optional<Expression> visit(Expression.ListLiteral expr) throws EXCEPTION {
158163
return visitLiteral(expr);

core/src/main/java/io/substrait/type/TypeCreator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public Type.ListType list(Type type) {
9595
return Type.ListType.builder().nullable(nullable).elementType(type).build();
9696
}
9797

98-
public Type map(Type key, Type value) {
98+
public Type.Map map(Type key, Type value) {
9999
return Type.Map.builder().nullable(nullable).key(key).value(value).build();
100100
}
101101

core/src/main/java/io/substrait/type/proto/ProtoTypeConverter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ public Type from(io.substrait.proto.Type type) {
5757
.map(this::from)
5858
.collect(java.util.stream.Collectors.toList()));
5959
case LIST -> fromList(type.getList());
60-
case MAP -> n(type.getMap().getNullability())
61-
.map(from(type.getMap().getKey()), from(type.getMap().getValue()));
60+
case MAP -> fromMap(type.getMap());
6261
case USER_DEFINED -> {
6362
var userDefined = type.getUserDefined();
6463
var t = lookup.getType(userDefined.getTypeReference(), extensions);
@@ -74,6 +73,10 @@ public Type.ListType fromList(io.substrait.proto.Type.List list) {
7473
return n(list.getNullability()).list(from(list.getType()));
7574
}
7675

76+
public Type.Map fromMap(io.substrait.proto.Type.Map map) {
77+
return n(map.getNullability()).map(from(map.getKey()), from(map.getValue()));
78+
}
79+
7780
public static boolean isNullable(io.substrait.proto.Type.Nullability nullability) {
7881
return io.substrait.proto.Type.Nullability.NULLABILITY_NULLABLE == nullability;
7982
}

spark/src/main/scala/io/substrait/debug/ExpressionToString.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,8 @@ class ExpressionToString extends DefaultExpressionVisitor[String] {
7272
override def visit(expr: Expression.UserDefinedLiteral): String = {
7373
expr.toString
7474
}
75+
76+
override def visit(expr: Expression.EmptyMapLiteral): String = {
77+
expr.toString
78+
}
7579
}

0 commit comments

Comments
 (0)