Skip to content

Commit 424e8b3

Browse files
authored
feat(isthmus): support exists and unique set predicates (#354)
1 parent 5b12f9b commit 424e8b3

File tree

2 files changed

+105
-10
lines changed

2 files changed

+105
-10
lines changed

isthmus/src/main/java/io/substrait/isthmus/expression/ExpressionRexConverter.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.substrait.expression.Expression;
77
import io.substrait.expression.Expression.FailureBehavior;
88
import io.substrait.expression.Expression.ScalarSubquery;
9+
import io.substrait.expression.Expression.SetPredicate;
910
import io.substrait.expression.Expression.SingleOrList;
1011
import io.substrait.expression.Expression.Switch;
1112
import io.substrait.expression.FieldReference;
@@ -545,4 +546,20 @@ public RexNode visit(ScalarSubquery expr) throws RuntimeException {
545546
RelNode inputRelnode = expr.input().accept(relNodeConverter);
546547
return RexSubQuery.scalar(inputRelnode);
547548
}
549+
550+
@Override
551+
public RexNode visit(SetPredicate expr) throws RuntimeException {
552+
RelNode inputRelnode = expr.tuples().accept(relNodeConverter);
553+
switch (expr.predicateOp()) {
554+
case PREDICATE_OP_EXISTS:
555+
return RexSubQuery.exists(inputRelnode);
556+
case PREDICATE_OP_UNIQUE:
557+
return RexSubQuery.unique(inputRelnode);
558+
case PREDICATE_OP_UNSPECIFIED:
559+
default:
560+
throw new UnsupportedOperationException(
561+
String.format(
562+
"Cannot handle SetPredicate when PredicateOp is %s.", expr.predicateOp().name()));
563+
}
564+
}
548565
}

isthmus/src/test/java/io/substrait/isthmus/SubstraitExpressionConverterTest.java

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
44
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
56

67
import io.substrait.dsl.SubstraitBuilder;
78
import io.substrait.expression.Expression;
@@ -53,16 +54,7 @@ public void switchExpression() {
5354

5455
@Test
5556
public void scalarSubQuery() {
56-
Rel subQueryRel =
57-
b.project(
58-
input -> List.of(b.fieldReference(input, 0)),
59-
Remap.of(List.of(3)),
60-
b.filter(
61-
input ->
62-
b.equal(
63-
b.fieldReference(input, 2),
64-
Expression.StrLiteral.builder().nullable(false).value("EUROPE").build()),
65-
commonTable));
57+
Rel subQueryRel = createSubQueryRel();
6658

6759
Expression.ScalarSubquery expr =
6860
Expression.ScalarSubquery.builder()
@@ -81,6 +73,92 @@ public void scalarSubQuery() {
8173
assertEquals(SqlKind.SCALAR_QUERY, calciteProjectExpr.get(0).getKind());
8274
}
8375

76+
@Test
77+
public void existsSetPredicate() {
78+
Rel subQueryRel = createSubQueryRel();
79+
80+
Expression.SetPredicate expr =
81+
Expression.SetPredicate.builder()
82+
.predicateOp(Expression.PredicateOp.PREDICATE_OP_EXISTS)
83+
.tuples(subQueryRel)
84+
.build();
85+
86+
Project query = b.project(input -> List.of(expr), b.emptyScan());
87+
88+
SubstraitToCalcite substraitToCalcite = new SubstraitToCalcite(extensions, typeFactory);
89+
RelNode calciteRel = substraitToCalcite.convert(query);
90+
91+
assertInstanceOf(LogicalProject.class, calciteRel);
92+
List<RexNode> calciteProjectExpr = ((LogicalProject) calciteRel).getProjects();
93+
assertEquals(1, calciteProjectExpr.size());
94+
assertEquals(SqlKind.EXISTS, calciteProjectExpr.get(0).getKind());
95+
}
96+
97+
@Test
98+
public void uniqueSetPredicate() {
99+
Rel subQueryRel = createSubQueryRel();
100+
101+
Expression.SetPredicate expr =
102+
Expression.SetPredicate.builder()
103+
.predicateOp(Expression.PredicateOp.PREDICATE_OP_UNIQUE)
104+
.tuples(subQueryRel)
105+
.build();
106+
107+
Project query = b.project(input -> List.of(expr), b.emptyScan());
108+
109+
SubstraitToCalcite substraitToCalcite = new SubstraitToCalcite(extensions, typeFactory);
110+
RelNode calciteRel = substraitToCalcite.convert(query);
111+
112+
assertInstanceOf(LogicalProject.class, calciteRel);
113+
List<RexNode> calciteProjectExpr = ((LogicalProject) calciteRel).getProjects();
114+
assertEquals(1, calciteProjectExpr.size());
115+
assertEquals(SqlKind.UNIQUE, calciteProjectExpr.get(0).getKind());
116+
}
117+
118+
@Test
119+
public void unspecifiedSetPredicate() {
120+
Rel subQueryRel = createSubQueryRel();
121+
122+
Expression.SetPredicate expr =
123+
Expression.SetPredicate.builder()
124+
.predicateOp(Expression.PredicateOp.PREDICATE_OP_UNSPECIFIED)
125+
.tuples(subQueryRel)
126+
.build();
127+
128+
Project query = b.project(input -> List.of(expr), b.emptyScan());
129+
130+
SubstraitToCalcite substraitToCalcite = new SubstraitToCalcite(extensions, typeFactory);
131+
Exception exception =
132+
assertThrows(
133+
UnsupportedOperationException.class,
134+
() -> {
135+
substraitToCalcite.convert(query);
136+
});
137+
138+
assertEquals(
139+
"Cannot handle SetPredicate when PredicateOp is PREDICATE_OP_UNSPECIFIED.",
140+
exception.getMessage());
141+
}
142+
143+
/**
144+
* Creates a Substrait {@link Rel} equivalent to the following SQL query:
145+
*
146+
* <p>select a from example where c = 'EUROPE'
147+
*
148+
* @return the Substrait {@link Rel} equivalent of the above SQL query
149+
*/
150+
Rel createSubQueryRel() {
151+
return b.project(
152+
input -> List.of(b.fieldReference(input, 0)),
153+
Remap.of(List.of(3)),
154+
b.filter(
155+
input ->
156+
b.equal(
157+
b.fieldReference(input, 2),
158+
Expression.StrLiteral.builder().nullable(false).value("EUROPE").build()),
159+
commonTable));
160+
}
161+
84162
void assertTypeMatch(RelDataType actual, Type expected) {
85163
Type type = TypeConverter.DEFAULT.toSubstrait(actual);
86164
assertEquals(expected, type);

0 commit comments

Comments
 (0)