Skip to content

Commit b43ed28

Browse files
authored
Unpivoting repeated fields with SQL functions (#3537)
Previously, correlated qualifiers were incorrectly processed, which prevented unpivoting operations on repeated fields when used alongside SQL functions. This fix resolves the qualifier handling logic to enable proper unpivoting according to our SQL syntax. This fixes #3532.
1 parent 3466ef4 commit b43ed28

File tree

5 files changed

+37
-8
lines changed

5 files changed

+37
-8
lines changed

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/LogicalOperator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public static LogicalOperator generateAccess(@Nonnull Identifier identifier,
184184
} else if (semanticAnalyzer.tableExists(identifier)) {
185185
return logicalOperatorCatalog.lookupTableAccess(identifier, alias, requestedIndexes, semanticAnalyzer);
186186
} else if (semanticAnalyzer.functionExists(identifier)) {
187-
return semanticAnalyzer.resolveTableFunction(identifier.getName(), Expressions.empty(), false);
187+
return semanticAnalyzer.resolveTableFunction(identifier, Expressions.empty(), false);
188188
} else {
189189
final var correlatedField = semanticAnalyzer.resolveCorrelatedIdentifier(identifier, currentPlanFragment.getLogicalOperatorsIncludingOuter());
190190
Assert.thatUnchecked(requestedIndexes.isEmpty(), ErrorCode.UNSUPPORTED_QUERY, () -> String.format(Locale.ROOT, "Can not hint indexes with correlated field access %s", identifier));

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -815,11 +815,11 @@ private void processFunctionSideEffects(@Nonnull final CatalogedFunction builtIn
815815
* @return A {@link LogicalOperator} representing the semantics of the requested SQL table function.
816816
*/
817817
@Nonnull
818-
public LogicalOperator resolveTableFunction(@Nonnull final String functionName, @Nonnull final Expressions arguments,
818+
public LogicalOperator resolveTableFunction(@Nonnull final Identifier functionName, @Nonnull final Expressions arguments,
819819
boolean flattenSingleItemRecords) {
820-
Assert.thatUnchecked(functionCatalog.containsFunction(functionName), ErrorCode.UNDEFINED_FUNCTION,
820+
Assert.thatUnchecked(functionCatalog.containsFunction(functionName.getName()), ErrorCode.UNDEFINED_FUNCTION,
821821
() -> String.format(Locale.ROOT, "Unknown function %s", functionName));
822-
final var tableFunction = functionCatalog.lookupFunction(functionName, arguments);
822+
final var tableFunction = functionCatalog.lookupFunction(functionName.getName(), arguments);
823823
if (tableFunction instanceof BuiltInFunction) {
824824
Assert.thatUnchecked(tableFunction instanceof BuiltInTableFunction, functionName + " is not a table-valued function");
825825
}
@@ -838,11 +838,11 @@ public LogicalOperator resolveTableFunction(@Nonnull final String functionName,
838838
final var tableFunctionExpression = new TableFunctionExpression(Assert.castUnchecked(resultingValue, StreamingValue.class));
839839
final var resultingQuantifier = Quantifier.forEach(Reference.initialOf(tableFunctionExpression));
840840
final var output = Expressions.of(LogicalOperator.convertToExpressions(resultingQuantifier));
841-
return LogicalOperator.newUnnamedOperator(output, resultingQuantifier);
841+
return LogicalOperator.newNamedOperator(functionName, output, resultingQuantifier);
842842
}
843843
final var relationalExpression = Assert.castUnchecked(resultingValue, RelationalExpression.class);
844844
final var topQun = Quantifier.forEach(Reference.initialOf(relationalExpression));
845-
return LogicalOperator.newUnnamedOperator(Expressions.fromQuantifier(topQun), topQun);
845+
return LogicalOperator.newNamedOperator(functionName, Expressions.fromQuantifier(topQun), topQun);
846846
}
847847

848848
// TODO: this will be removed once we unify both Java- and SQL-UDFs.

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/BaseVisitor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ public Expression resolveFunction(@Nonnull String functionName, boolean flattenS
223223
}
224224

225225
@Nonnull
226-
public LogicalOperator resolveTableValuedFunction(@Nonnull String functionName, @Nonnull Expressions arguments) {
226+
public LogicalOperator resolveTableValuedFunction(@Nonnull Identifier functionName, @Nonnull Expressions arguments) {
227227
return getSemanticAnalyzer().resolveTableFunction(functionName, arguments, true);
228228
}
229229

fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/visitors/ExpressionVisitor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public static ExpressionVisitor of(@Nonnull BaseVisitor baseVisitor) {
8787

8888
@Override
8989
public LogicalOperator visitTableFunction(@Nonnull RelationalParser.TableFunctionContext ctx) {
90-
final var functionName = visitTableFunctionName(ctx.tableFunctionName()).toString();
90+
final var functionName = visitTableFunctionName(ctx.tableFunctionName());
9191
return ctx.tableFunctionArgs() == null
9292
? getDelegate().resolveTableValuedFunction(functionName, Expressions.empty())
9393
: getDelegate().resolveTableValuedFunction(functionName, visitTableFunctionArgs(ctx.tableFunctionArgs()));

fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,35 @@ void attemptToCreateTemporaryFunctionWithDifferentCaseSensitivityOptionCase2() t
10511051
}
10521052
}
10531053

1054+
@Test
1055+
void unpivotRepeatedFieldInSqlFunctionWorksCorrectly() throws Exception {
1056+
final String schemaTemplate = "create type as struct city(name string, population bigint) " +
1057+
"create table country(id bigint, name string, continent string, cities city array, primary key(id))";
1058+
try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) {
1059+
try (var statement = ddl.setSchemaAndGetConnection().createStatement()) {
1060+
statement.executeUpdate("insert into country values " +
1061+
"(1, 'USA', 'North America', [('New York' ,8419600), ('Los Angeles', 3980400)]), " +
1062+
"(2, 'Canada', 'North America', [('Toronto', 2731571), ('Montreal', 1760400)]), " +
1063+
"(3, 'Brazil', 'South America', [('Rio de Janeiro', 6795900), ('Sao Paulo', 12303800)]), " +
1064+
"(4, 'France', 'Europe', [('Paris', 2148327), ('Lyon', 516855)])");
1065+
}
1066+
final var connection = ddl.getConnection();
1067+
connection.setAutoCommit(false);
1068+
try (var statement = connection.prepareStatement("create temporary function northAmericaCountries() " +
1069+
"on commit drop function as select * from country where continent = 'North America'")) {
1070+
statement.execute();
1071+
}
1072+
try (var statement = connection.prepareStatement("select A.name from northAmericaCountries, (select * from northAmericaCountries.cities) as A where population > 8000000")) {
1073+
try (var resultSet = statement.executeQuery()) {
1074+
Assertions.assertTrue(resultSet.next());
1075+
Assertions.assertEquals("New York", resultSet.getString(1));
1076+
Assertions.assertFalse(resultSet.next());
1077+
}
1078+
}
1079+
connection.rollback();
1080+
}
1081+
}
1082+
10541083
private void invokeAndVerifyTempFunction(final RelationalStatement statement) throws SQLException {
10551084
Assertions.assertTrue(statement.execute("select * from sq1(x => 2)"));
10561085
invokeAndVerify(statement::getResultSet, 1L, 10L, 2L, 20L, 3L, 30L, 4L, 40L);

0 commit comments

Comments
 (0)