Skip to content

Commit 03b0f24

Browse files
committed
test: that order added determines precedence in FunctionConverter
1 parent f97fde7 commit 03b0f24

File tree

1 file changed

+59
-4
lines changed

1 file changed

+59
-4
lines changed

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

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
package io.substrait.isthmus;
22

3+
import static org.junit.jupiter.api.Assertions.assertEquals;
34
import static org.junit.jupiter.api.Assertions.assertNotNull;
45

6+
import io.substrait.expression.Expression;
57
import io.substrait.extension.SimpleExtension;
68
import io.substrait.isthmus.expression.AggregateFunctionConverter;
79
import io.substrait.isthmus.expression.ScalarFunctionConverter;
810
import io.substrait.isthmus.expression.WindowFunctionConverter;
911
import java.io.IOException;
1012
import java.io.UncheckedIOException;
1113
import java.util.List;
14+
import java.util.Optional;
15+
import org.apache.calcite.rex.RexBuilder;
16+
import org.apache.calcite.rex.RexCall;
17+
import org.apache.calcite.rex.RexNode;
18+
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
1219
import org.junit.jupiter.api.Test;
1320

1421
/** Tests to reproduce #562 */
1522
public class DuplicateFunctionUrnTest extends PlanTestBase {
1623

24+
static final SimpleExtension.ExtensionCollection collection1;
25+
static final SimpleExtension.ExtensionCollection collection2;
1726
static final SimpleExtension.ExtensionCollection collection;
1827

1928
static {
2029
try {
2130
String extensions1 = asString("extensions/functions_duplicate_urn1.yaml");
2231
String extensions2 = asString("extensions/functions_duplicate_urn2.yaml");
23-
SimpleExtension.ExtensionCollection collection1 =
24-
SimpleExtension.load("urn1://functions", extensions1);
25-
SimpleExtension.ExtensionCollection collection2 =
26-
SimpleExtension.load("urn2://functions", extensions2);
32+
collection1 = SimpleExtension.load("urn1://functions", extensions1);
33+
collection2 = SimpleExtension.load("urn2://functions", extensions2);
2734
collection = collection1.merge(collection2);
2835

2936
// Verify that the merged collection contains duplicate functions with different URNs
@@ -70,4 +77,52 @@ void testDuplicateWindowFunctionWithDifferentUrns() {
7077

7178
assertNotNull(converter);
7279
}
80+
81+
@Test
82+
void testMergeOrderDeterminesFunctionPrecedence() {
83+
// This test verifies that when multiple extension collections contain functions with
84+
// the same name and signature but different URNs, the merge order determines precedence.
85+
// The FunctionConverter uses a "last-wins" strategy: the last function added to the
86+
// extension collection will be matched when converting from Calcite to Substrait.
87+
88+
SimpleExtension.ExtensionCollection reverseCollection = collection2.merge(collection1);
89+
ScalarFunctionConverter converterA =
90+
new ScalarFunctionConverter(collection.scalarFunctions(), typeFactory);
91+
ScalarFunctionConverter converterB =
92+
new ScalarFunctionConverter(reverseCollection.scalarFunctions(), typeFactory);
93+
94+
RexBuilder rexBuilder = new RexBuilder(typeFactory);
95+
RexNode arg1 = rexBuilder.makeLiteral("hello");
96+
RexNode arg2 = rexBuilder.makeLiteral("world");
97+
RexCall concatCall = (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.CONCAT, arg1, arg2);
98+
99+
// Create a simple topLevelConverter that converts literals to Substrait expressions
100+
java.util.function.Function<RexNode, Expression> topLevelConverter =
101+
rexNode -> {
102+
if (rexNode instanceof org.apache.calcite.rex.RexLiteral) {
103+
org.apache.calcite.rex.RexLiteral lit = (org.apache.calcite.rex.RexLiteral) rexNode;
104+
return Expression.StrLiteral.builder()
105+
.value(lit.getValueAs(String.class))
106+
.nullable(false)
107+
.build();
108+
}
109+
throw new UnsupportedOperationException("Only literals supported in test");
110+
};
111+
112+
Optional<Expression> exprA = converterA.convert(concatCall, topLevelConverter);
113+
Optional<Expression> exprB = converterB.convert(concatCall, topLevelConverter);
114+
115+
Expression.ScalarFunctionInvocation funcA = (Expression.ScalarFunctionInvocation) exprA.get();
116+
Expression.ScalarFunctionInvocation funcB = (Expression.ScalarFunctionInvocation) exprB.get();
117+
118+
assertEquals(
119+
"extension:com.domain:string",
120+
funcA.declaration().getAnchor().urn(),
121+
"converterA should use last concat function (from collection2)");
122+
123+
assertEquals(
124+
"extension:io.substrait:functions_string",
125+
funcB.declaration().getAnchor().urn(),
126+
"converterB should use last concat function (from collection1)");
127+
}
73128
}

0 commit comments

Comments
 (0)