diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerInvokedRoutine.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerInvokedRoutine.java index 82f789bb7c..337af64e53 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerInvokedRoutine.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/metadata/RecordLayerInvokedRoutine.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.api.metadata.InvokedRoutine; import com.apple.foundationdb.relational.recordlayer.query.functions.CompiledSqlFunction; import com.apple.foundationdb.relational.util.Assert; +import com.google.common.base.Suppliers; import javax.annotation.Nonnull; import java.util.Objects; @@ -55,7 +56,9 @@ public RecordLayerInvokedRoutine(@Nonnull final String description, this.name = name; this.isTemporary = isTemporary; // TODO this used to be memoized - this.compilableSqlFunctionSupplier = compilableSqlFunctionSupplier; + final var memoizedTrue = Suppliers.memoize(() -> compilableSqlFunctionSupplier.apply(true)); + final var memoizedFalse = Suppliers.memoize(() -> compilableSqlFunctionSupplier.apply(false)); + this.compilableSqlFunctionSupplier = param -> param ? memoizedTrue.get() : memoizedFalse.get(); } @Nonnull diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java index e8e4bdca2a..8254997613 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/query/SemanticAnalyzer.java @@ -29,8 +29,11 @@ import com.apple.foundationdb.record.query.plan.cascades.Correlated; import com.apple.foundationdb.record.query.plan.cascades.CorrelationIdentifier; import com.apple.foundationdb.record.query.plan.cascades.IndexAccessHint; +import com.apple.foundationdb.record.query.plan.cascades.Memoizer; +import com.apple.foundationdb.record.query.plan.cascades.PlannerStage; import com.apple.foundationdb.record.query.plan.cascades.Quantifier; import com.apple.foundationdb.record.query.plan.cascades.Reference; +import com.apple.foundationdb.record.query.plan.cascades.References; import com.apple.foundationdb.record.query.plan.cascades.expressions.RelationalExpression; import com.apple.foundationdb.record.query.plan.cascades.expressions.TableFunctionExpression; import com.apple.foundationdb.record.query.plan.cascades.typing.Type; @@ -48,6 +51,7 @@ import com.apple.foundationdb.record.query.plan.cascades.values.StreamableAggregateValue; import com.apple.foundationdb.record.query.plan.cascades.values.StreamingValue; import com.apple.foundationdb.record.query.plan.cascades.values.Value; +import com.apple.foundationdb.record.query.plan.cascades.values.translation.ToUniqueAliasesTranslationMap; import com.apple.foundationdb.record.util.pair.NonnullPair; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; @@ -66,6 +70,7 @@ import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.collect.Streams; import com.google.protobuf.ByteString; @@ -839,12 +844,18 @@ public LogicalOperator resolveTableFunction(@Nonnull final Identifier functionNa : tableFunction.encapsulate(valueArgs); if (resultingValue instanceof StreamingValue) { final var tableFunctionExpression = new TableFunctionExpression(Assert.castUnchecked(resultingValue, StreamingValue.class)); - final var resultingQuantifier = Quantifier.forEach(Reference.initialOf(tableFunctionExpression)); + final var reference = Reference.initialOf(tableFunctionExpression); + final var translatedReference = Iterables.getOnlyElement(References.rebaseGraphs(List.of(reference), + Memoizer.noMemoization(PlannerStage.INITIAL), new ToUniqueAliasesTranslationMap(), false)); + final var resultingQuantifier = Quantifier.forEach(translatedReference); final var output = Expressions.of(LogicalOperator.convertToExpressions(resultingQuantifier)); return LogicalOperator.newNamedOperator(functionName, output, resultingQuantifier); } final var relationalExpression = Assert.castUnchecked(resultingValue, RelationalExpression.class); - final var topQun = Quantifier.forEach(Reference.initialOf(relationalExpression)); + final var reference = Reference.initialOf(relationalExpression); + final var translatedReference = Iterables.getOnlyElement(References.rebaseGraphs(List.of(reference), + Memoizer.noMemoization(PlannerStage.INITIAL), new ToUniqueAliasesTranslationMap(), false)); + final var topQun = Quantifier.forEach(translatedReference); return LogicalOperator.newNamedOperator(functionName, Expressions.fromQuantifier(topQun), topQun); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java index e6397ecbd5..9e17ff50db 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metadata/SchemaTemplateSerDeTests.java @@ -52,7 +52,6 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -61,6 +60,7 @@ import javax.annotation.Nonnull; import java.net.URI; +import java.sql.SQLException; import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; @@ -362,11 +362,10 @@ public void findTableByNameWorksCorrectly() { Assertions.assertFalse(nonExisting.isPresent()); } - @Disabled @Test - public void sqlFunctionsAreLazilyParsed() throws RelationalException { + public void sqlFunctionsAreLazilyParsed() throws Exception { final var peekingDeserializer = recMetadataSampleWithFunctions( - "CREATE FUNCTION SqlFunction1(IN Q BIGINT) AS SELECT * FROM T1 WHERE col1 < Q"); + "CREATE FUNCTION SqlFunction1(IN Q BIGINT) AS SELECT * FROM T1 WHERE COL1 < Q"); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction1")); final var planGenerator = peekingDeserializer.getPlanGenerator(); @@ -378,12 +377,11 @@ public void sqlFunctionsAreLazilyParsed() throws RelationalException { Assertions.assertNotNull(plan); } - @Disabled @Test - public void nestedSqlFunctionsAreLazilyParsed() throws RelationalException { + public void nestedSqlFunctionsAreLazilyParsed() throws Exception { final var peekingDeserializer = recMetadataSampleWithFunctions( - "CREATE FUNCTION SqlFunction1(IN Q BIGINT) AS SELECT * FROM T1 WHERE col1 < Q", - "CREATE FUNCTION SqlFunction2(IN Q BIGINT) AS SELECT * FROM SqlFunction1(100) WHERE col1 < Q"); + "CREATE FUNCTION SqlFunction1(IN Q BIGINT) AS SELECT * FROM T1 WHERE COL1 < Q", + "CREATE FUNCTION SqlFunction2(IN Q BIGINT) AS SELECT * FROM SqlFunction1(100) WHERE COL1 < Q"); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction1")); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction2")); @@ -396,17 +394,16 @@ public void nestedSqlFunctionsAreLazilyParsed() throws RelationalException { Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction1")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction2")); - Assertions.assertDoesNotThrow(() -> planGenerator.getPlan("select * from SqlFunction2(200) where col1 < 300")); + Assertions.assertDoesNotThrow(() -> planGenerator.getPlan("select * from SqlFunction2(200) where COL1 < 300")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction1")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction2")); } - @Disabled @Test - public void onlyQueriedSqlFunctionsAreCompiled() throws RelationalException { + public void onlyQueriedSqlFunctionsAreCompiled() throws Exception { final var peekingDeserializer = recMetadataSampleWithFunctions( - "CREATE FUNCTION SqlFunction1(IN Q BIGINT) AS SELECT * FROM T1 WHERE col1 < Q", - "CREATE FUNCTION SqlFunction2(IN Q BIGINT) AS SELECT * FROM SqlFunction1(100) WHERE col1 < Q", + "CREATE FUNCTION SqlFunction1(IN Q BIGINT) AS SELECT * FROM T1 WHERE COL1 < Q", + "CREATE FUNCTION SqlFunction2(IN Q BIGINT) AS SELECT * FROM SqlFunction1(100) WHERE COL1 < Q", "CREATE FUNCTION SqlFunction3() AS SELECT * FROM T1"); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction1")); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction2")); @@ -418,17 +415,19 @@ public void onlyQueriedSqlFunctionsAreCompiled() throws RelationalException { Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction2")); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction3")); + planGenerator.getPlan("select * from SqlFunction2(200)"); + Assertions.assertDoesNotThrow(() -> planGenerator.getPlan("select * from SqlFunction2(200)")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction1")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction2")); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction3")); - Assertions.assertDoesNotThrow(() -> planGenerator.getPlan("select * from SqlFunction2(200) where col1 < 300")); + Assertions.assertDoesNotThrow(() -> planGenerator.getPlan("select * from SqlFunction2(200) where COL1 < 300")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction1")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction2")); Assertions.assertTrue(peekingDeserializer.hasNoCompilationRequestsFor("SqlFunction4")); - Assertions.assertDoesNotThrow(() -> planGenerator.getPlan("select * from SqlFunction3() where col1 < 300")); + Assertions.assertDoesNotThrow(() -> planGenerator.getPlan("select * from SqlFunction3() where COL1 < 300")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction1")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction2")); Assertions.assertTrue(peekingDeserializer.hasOneCompilationRequestFor("SqlFunction3")); @@ -526,14 +525,16 @@ private static RecordMetadataDeserializerWithPeekingFunctionSupplier recMetadata // Verify that the provided functions match the ones we just deserialized Assertions.assertEquals(expectedFunctionMap, actualFunctionMap); - - Assertions.assertTrue(invokedRoutines.containsKey("SqlFunction1")); - final var function = invokedRoutines.get("SqlFunction1"); - Assertions.assertInstanceOf(RawSqlFunction.class, function); - final var rawSqlFunction = (RawSqlFunction)function; - Assertions.assertEquals("SqlFunction1", rawSqlFunction.getFunctionName()); - Assertions.assertEquals("CREATE FUNCTION SqlFunction1(IN Q BIGINT) AS SELECT * FROM T1 WHERE col1 < Q", - rawSqlFunction.getDefinition()); + for (final var entry : expectedFunctionMap.entrySet()) { + final var functionName = entry.getKey(); + final var functionDescription = entry.getValue(); + Assertions.assertTrue(invokedRoutines.containsKey(functionName)); + final var function = invokedRoutines.get(functionName); + Assertions.assertInstanceOf(RawSqlFunction.class, function); + final var rawSqlFunction = (RawSqlFunction)function; + Assertions.assertEquals(functionName, rawSqlFunction.getFunctionName()); + Assertions.assertEquals(functionDescription, rawSqlFunction.getDefinition()); + } // let's verify now that _no_ compilation is invoked when deserializing the record metadata. // for that, we use a deserializer with peeking supplier to the function compilation logic. @@ -586,8 +587,7 @@ boolean hasOneCompilationRequestFor(@Nonnull final String functionName) { } @Nonnull - public PlanGenerator getPlanGenerator() - throws RelationalException { + public PlanGenerator getPlanGenerator() throws RelationalException, SQLException { final var metricCollector = new MetricCollector() { @Override @@ -610,7 +610,8 @@ public T clock(@Nonnull RelationalMetric.RelationalEvent event, .withPlannerConfiguration(PlannerConfiguration.ofAllAvailableIndexes()) .withUserVersion(0) .build(); - return PlanGenerator.create(Optional.empty(), ctx, ctx.getMetaData(), new RecordStoreState(null, Map.of()), Options.NONE); + return PlanGenerator.create(Optional.empty(), ctx, ctx.getMetaData(), new RecordStoreState(null, Map.of()), + Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()); } } }