From b6eff6f38b56d6fec69fc26e21daa55b966a6e6b Mon Sep 17 00:00:00 2001 From: Rohit Date: Fri, 10 Jan 2025 18:05:47 +0530 Subject: [PATCH] support mapping relations to classes --- .../legend/engine/repl/client/Client.java | 1 + .../legend/engine/repl/client/ModelState.java | 13 +- .../core/commands/PromoteRelationToModel.java | 149 ++++++++++ .../shared/RelationClassMappingGenerator.java | 115 ++++++++ ...t_Relational_UsingPureClientTestSuite.java | 1 + .../ClassMappingFirstPassBuilder.java | 22 ++ .../ClassMappingSecondPassBuilder.java | 24 ++ .../ClassMappingThirdPassBuilder.java | 7 + .../toPureGraph/PropertyMappingBuilder.java | 45 ++- .../validator/MappingValidator.java | 56 +++- .../TestMappingCompilationFromGrammar.java | 217 ++++++++++++++ .../from/antlr4/core/M3ParserGrammar.g4 | 2 +- .../RelationFunctionMappingLexerGrammar.g4 | 6 + .../RelationFunctionMappingParserGrammar.g4 | 19 ++ .../grammar/from/CorePureGrammarParser.java | 38 ++- .../from/PureGrammarParserUtility.java | 11 + ...reInstanceClassMappingParseTreeWalker.java | 10 +- ...elationFunctionMappingParseTreeWalker.java | 82 ++++++ .../DEPRECATED_PureGrammarComposerCore.java | 21 +- .../to/PureGrammarComposerUtility.java | 7 + .../test/parser/TestMappingGrammarParser.java | 187 +++++++++++- .../TestMappingGrammarRoundtrip.java | 37 +++ .../mapping/ClassMapping.java | 3 +- .../mapping/ClassMappingVisitor.java | 3 + .../mapping/PropertyMapping.java | 3 +- .../mapping/PropertyMappingVisitor.java | 4 + .../RelationFunctionClassMapping.java | 34 +++ .../RelationFunctionPropertyMapping.java | 29 ++ .../protocol/v1_33_0/models/metamodel.pure | 29 +- .../v1_33_0/scan/buildBasePureModel.pure | 11 +- .../protocol/v1_33_0/transfers/mapping.pure | 46 ++- .../v1_33_0/transfers/valueSpecification.pure | 107 ++++++- .../protocol/vX_X_X/models/metamodel.pure | 29 +- .../vX_X_X/scan/buildBasePureModel.pure | 10 +- .../protocol/vX_X_X/transfers/mapping.pure | 45 ++- .../vX_X_X/transfers/valueSpecification.pure | 99 ++++++- .../core/pure/router/store/builder.pure | 14 +- .../core/pure/router/store/cluster.pure | 6 + .../core/pure/router/store/routing.pure | 66 ++++- .../relation/tests/composition.pure | 2 +- ...lational_DuckDB_RelationFunctions_PCT.java | 1 - ...t_Relational_H2_RelationFunctions_PCT.java | 3 - ...tional_Postgres_RelationFunctions_PCT.java | 1 - .../from/RelationalParseTreeWalker.java | 9 +- .../TestRelationalCompilationFromGrammar.java | 1 + .../pom.xml | 8 +- .../graphFetch/relationalGraphFetch.pure | 4 +- .../helperFunctions/helperFunctions.pure | 31 ++ .../relational/modelJoins/modelJoins.pure | 271 +++++++++--------- .../defaultPostProcessor/reAliasQuery.pure | 4 + .../relational/pureToSQLQuery/metamodel.pure | 10 + .../pureToSQLQuery/pureToSQLQuery.pure | 154 ++++++---- .../pureToSQLQuery/pureToSQLQuery_union.pure | 10 +- .../relationalMappingExecution.pure | 5 +- .../testDataGeneration.pure | 8 +- .../relation/relationMappingSetup.pure | 179 ++++++++++++ .../tests/mapping/relation/tests.pure | 164 +++++++++++ pom.xml | 2 +- 58 files changed, 2202 insertions(+), 273 deletions(-) create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/PromoteRelationToModel.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/shared/RelationClassMappingGenerator.java create mode 100644 legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingLexerGrammar.g4 create mode 100644 legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingParserGrammar.g4 create mode 100644 legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/RelationFunctionMappingParseTreeWalker.java create mode 100644 legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionClassMapping.java create mode 100644 legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionPropertyMapping.java create mode 100644 legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/relationMappingSetup.pure create mode 100644 legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/tests.pure diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java index da8b610b79f..70fc8b8dd68 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java @@ -119,6 +119,7 @@ public Client(MutableList replExtensions, MutableList replExtensions; - private MutableList state = Lists.mutable.empty(); + private final MutableList state = Lists.mutable.empty(); public ModelState(LegendInterface legendInterface, MutableList replExtensions) { @@ -44,6 +45,16 @@ public PureModelContextData parse() return this.legendInterface.parse(getText()); } + public PureModel compile() + { + return this.legendInterface.compile(parse()); + } + + public PureModel compileWithTransient(String transientCode) + { + return this.legendInterface.compile(parseWithTransient(transientCode)); + } + public PureModelContextData parseWithTransient(String transientCode) { return this.legendInterface.parse(getText() + transientCode); diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/PromoteRelationToModel.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/PromoteRelationToModel.java new file mode 100644 index 00000000000..13e1709dafb --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/PromoteRelationToModel.java @@ -0,0 +1,149 @@ +// Copyright 2025 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.repl.core.commands; + +import org.eclipse.collections.api.list.MutableList; +import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; +import org.finos.legend.engine.language.pure.compiler.toPureGraph.SourceInformationHelper; +import org.finos.legend.engine.protocol.pure.v1.model.context.EngineErrorType; +import org.finos.legend.engine.repl.client.Client; +import org.finos.legend.engine.repl.core.Command; +import org.finos.legend.engine.repl.shared.RelationClassMappingGenerator; +import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; +import org.finos.legend.pure.generated.core_pure_corefunctions_stringExtension; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.ConcreteFunctionDefinition; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.FunctionDefinition; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.generics.GenericType; +import org.finos.legend.pure.m3.navigation.M3Paths; +import org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement; +import org.finos.legend.pure.m3.navigation.function.FunctionDescriptor; +import org.finos.legend.pure.m3.navigation.relation._RelationType; +import org.finos.legend.pure.runtime.java.compiled.execution.CompiledExecutionSupport; +import org.jline.reader.Candidate; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +public class PromoteRelationToModel implements Command +{ + private final Client client; + + public PromoteRelationToModel(Client client) + { + this.client = client; + } + + @Override + public boolean process(String line) throws Exception + { + if (line.startsWith("promoteRelationToModel")) + { + String[] tokens = line.split(" "); + if (tokens.length < 1 || tokens.length > 3) + { + throw new RuntimeException("Command should be used as '" + this.documentation() + "'"); + } + + PureModel currentModel = client.getModelState().compile(); + CompiledExecutionSupport es = currentModel.getExecutionSupport(); + String relationName = core_pure_corefunctions_stringExtension.Root_meta_pure_functions_string_makeCamelCase_String_1__Boolean_1__String_1_(tokens[1], true, es); + String packagePrefix = tokens[2]; + + String className = packagePrefix + "::domain::" + relationName + "Class"; + String functionName = packagePrefix + "::function::" + relationName + "Fn"; + String mappingName = packagePrefix + "::mapping::" + relationName + "Mapping"; + + if (currentModel.getMapping_safe(mappingName) != null) + { + throw new EngineException("Mapping by name " + mappingName + "already exists in current state!"); + } + + String expression = this.client.getLastCommand(1); + if (expression == null) + { + this.client.printError("Failed to retrieve the last command"); + return true; + } + + try + { + // generate initial function with return type Any, then resolve type post compilation + FunctionDefinition relationFunction = getFunction(RelationClassMappingGenerator.generateFunction(functionName, expression), functionName); + RelationType relationType = getRelationType(relationFunction, es); + String returnType = M3Paths.Relation + "<" + _RelationType.print(relationType, es.getProcessorSupport()) + ">"; + String functionBody = RelationClassMappingGenerator.generateFunction(functionName, expression, returnType); + + String classBody = RelationClassMappingGenerator.generateClass(className, relationType, es); + + String functionDescriptor = FunctionDescriptor.writeFunctionDescriptor(new StringBuilder(), getFunction(functionBody, functionName), false, es.getProcessorSupport()).toString(); + String mappingBody = RelationClassMappingGenerator.generateClassMapping(mappingName, className, functionDescriptor, relationType, es); + + client.getModelState().addElement(mappingBody); + client.getModelState().addElement(classBody); + client.getModelState().addElement(functionBody); + // Verify generated code compiles + client.getModelState().compile(); + return true; + } + catch (Exception e) + { + this.client.printError("Last command run may not have been an execution of a Pure expression which returns a Relation (command run: '" + expression + "')"); + if (e instanceof EngineException) + { + this.client.printEngineError((EngineException) e, expression); + } + else + { + throw e; + } + } + } + return false; + } + + private FunctionDefinition getFunction(String functionBody, String functionName) + { + PureModel model = this.client.getModelState().compileWithTransient(functionBody); + return model.getAllPackageableElementsOfType(ConcreteFunctionDefinition.class).detect(f -> PackageableElement.getUserPathForPackageableElement(f).contains(functionName)); + } + + private static RelationType getRelationType(FunctionDefinition relationFunction, CompiledExecutionSupport es) + { + GenericType lastExpressionType = relationFunction._expressionSequence().toList().getLast()._genericType(); + if (!es.getProcessorSupport().type_subTypeOf(lastExpressionType._rawType(), es.getProcessorSupport().package_getByUserPath(M3Paths.Relation))) + { + throw new EngineException("Last expression returned a " + org.finos.legend.pure.m3.navigation.generictype.GenericType.print(lastExpressionType, es.getProcessorSupport()) + ". A Relation is required for promotion to class.", SourceInformationHelper.fromM3SourceInformation(relationFunction.getSourceInformation()), EngineErrorType.COMPILATION); + } + return (RelationType) lastExpressionType._typeArguments().toList().getFirst()._rawType(); + } + + @Override + public String documentation() + { + return "promoteRelationToModel "; + } + + @Override + public String description() + { + return "generate model class and corresponding relation expression mapping for last run relation expression. Example Usage: promoteRelationToModel AggregatedPerson my::package"; + } + + @Override + public MutableList complete(String cmd, LineReader lineReader, ParsedLine parsedLine) + { + return null; + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/shared/RelationClassMappingGenerator.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/shared/RelationClassMappingGenerator.java new file mode 100644 index 00000000000..fe135823b1b --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/shared/RelationClassMappingGenerator.java @@ -0,0 +1,115 @@ +// Copyright 2025 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.repl.shared; + +import org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposer; +import org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerContext; +import org.finos.legend.engine.protocol.pure.v1.model.PackageableElement; +import org.finos.legend.engine.protocol.pure.v1.model.context.PackageableElementPointer; +import org.finos.legend.engine.protocol.pure.v1.model.context.PackageableElementType; +import org.finos.legend.engine.protocol.pure.v1.model.domain.Class; +import org.finos.legend.engine.protocol.pure.v1.model.domain.Property; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.Mapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyPointer; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionPropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.type.PackageableType; +import org.finos.legend.pure.generated.core_pure_corefunctions_stringExtension; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.multiplicity.Multiplicity; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.Column; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType; +import org.finos.legend.pure.m3.execution.ExecutionSupport; +import org.finos.legend.pure.m3.navigation.relation._Column; +import org.finos.legend.pure.runtime.java.compiled.execution.CompiledExecutionSupport; + +import java.util.Collections; + +public class RelationClassMappingGenerator +{ + private static String getColumnPureType(Column c) + { + return _Column.getColumnType(c)._rawType()._name(); + } + + private static String getPropertyName(String columnName, ExecutionSupport es) + { + String sqlSafeColumnName = columnName.replaceAll("[ /@~<>.]", "_").replace("\"", ""); + return core_pure_corefunctions_stringExtension.Root_meta_pure_functions_string_makeCamelCase_String_1__String_1_(sqlSafeColumnName, es); + } + + public static String generateFunction(String functionName, String expression) + { + return generateFunction(functionName, expression, "Any", "1"); + } + + public static String generateFunction(String functionName, String expression, String returnType) + { + return generateFunction(functionName, expression, returnType, "1"); + } + + private static String generateFunction(String functionName, String expression, String returnType, String returnMultiplicity) + { + return "###Pure\n" + + "function " + functionName + "():" + returnType + "[" + returnMultiplicity + "]\n" + + "{\n" + + expression + ";\n" + + "}\n"; + } + + public static String generateClass(String className, RelationType relationType, CompiledExecutionSupport es) + { + Class clazz = new Class(); + clazz.name = className; + clazz.properties = relationType._columns().collect(c -> + { + Property property = new Property(); + property.name = getPropertyName(c._name(), es); + property.genericType = new org.finos.legend.engine.protocol.pure.v1.model.type.GenericType(); + property.genericType.rawType = new PackageableType(getColumnPureType(c)); + Multiplicity m = _Column.getColumnMultiplicity(c); + property.multiplicity = new org.finos.legend.engine.protocol.pure.v1.model.domain.Multiplicity(m._lowerBound()._value().intValue(), m. _upperBound()._value().intValue()); + return property; + }).toList(); + return "###Pure\n" + renderElement(clazz); + } + + public static String generateClassMapping(String mappingName, String className, String functionDescriptor, RelationType relationType, CompiledExecutionSupport es) + { + Mapping mapping = new Mapping(); + mapping.name = mappingName; + RelationFunctionClassMapping classMapping = new RelationFunctionClassMapping(); + classMapping.relationFunction = new PackageableElementPointer(); + classMapping.relationFunction.type = PackageableElementType.FUNCTION; + classMapping.relationFunction.path = functionDescriptor; + classMapping._class = className; + classMapping.propertyMappings = relationType._columns().collect(c -> + { + RelationFunctionPropertyMapping propertyMapping = new RelationFunctionPropertyMapping(); + propertyMapping.property = new PropertyPointer(); + propertyMapping.property._class = className; + propertyMapping.property.property = getPropertyName(c._name(), es); + propertyMapping.column = c._name(); + return (PropertyMapping) propertyMapping; + }).toList(); + mapping.classMappings = Collections.singletonList(classMapping); + return "###Mapping\n" + renderElement(mapping); + } + + private static String renderElement(PackageableElement element) + { + return PureGrammarComposer.newInstance(PureGrammarComposerContext.Builder.newInstance().build()).render(element); + } +} diff --git a/legend-engine-config/legend-engine-server/legend-engine-server-http-server/src/test/java/org/finos/legend/engine/server/test/pureClient/stores/Test_Relational_UsingPureClientTestSuite.java b/legend-engine-config/legend-engine-server/legend-engine-server-http-server/src/test/java/org/finos/legend/engine/server/test/pureClient/stores/Test_Relational_UsingPureClientTestSuite.java index 52fddc10fa0..b0d815b221b 100644 --- a/legend-engine-config/legend-engine-server/legend-engine-server-http-server/src/test/java/org/finos/legend/engine/server/test/pureClient/stores/Test_Relational_UsingPureClientTestSuite.java +++ b/legend-engine-config/legend-engine-server/legend-engine-server-http-server/src/test/java/org/finos/legend/engine/server/test/pureClient/stores/Test_Relational_UsingPureClientTestSuite.java @@ -56,6 +56,7 @@ public static Test suite() throws Exception suite.addTest(buildSuite(TestCollection.collectTests("meta::relational::tests::mapping::merge", executionSupport.getProcessorSupport(), ci -> satisfiesConditions(ci, executionSupport.getProcessorSupport())), executionSupport)); suite.addTest(buildSuite(TestCollection.collectTests("meta::relational::tests::mapping::multigrain", executionSupport.getProcessorSupport(), ci -> satisfiesConditions(ci, executionSupport.getProcessorSupport())), executionSupport)); suite.addTest(buildSuite(TestCollection.collectTests("meta::relational::tests::mapping::propertyfunc", executionSupport.getProcessorSupport(), ci -> satisfiesConditions(ci, executionSupport.getProcessorSupport())), executionSupport)); + suite.addTest(buildSuite(TestCollection.collectTests("meta::relational::tests::mapping::relation", executionSupport.getProcessorSupport(), ci -> satisfiesConditions(ci, executionSupport.getProcessorSupport())), executionSupport)); suite.addTest(buildSuite(TestCollection.collectTests("meta::relational::tests::mapping::selfJoin", executionSupport.getProcessorSupport(), ci -> satisfiesConditions(ci, executionSupport.getProcessorSupport())), executionSupport)); suite.addTest(buildSuite(TestCollection.collectTests("meta::relational::tests::mapping::sqlFunction", executionSupport.getProcessorSupport(), ci -> satisfiesConditions(ci, executionSupport.getProcessorSupport())), executionSupport)); suite.addTest(buildSuite(TestCollection.collectTests("meta::relational::tests::mapping::subType", executionSupport.getProcessorSupport(), ci -> satisfiesConditions(ci, executionSupport.getProcessorSupport())), executionSupport)); diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingFirstPassBuilder.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingFirstPassBuilder.java index 13175c85017..66eed65f7ac 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingFirstPassBuilder.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingFirstPassBuilder.java @@ -27,6 +27,7 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregateSetImplementationContainer; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwareClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PureInstanceClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.type.GenericType; import org.finos.legend.engine.protocol.pure.v1.model.type.PackageableType; @@ -34,6 +35,7 @@ import org.finos.legend.pure.generated.Root_meta_external_store_model_PureInstanceSetImplementation_Impl; import org.finos.legend.pure.generated.Root_meta_pure_mapping_MergeOperationSetImplementation_Impl; import org.finos.legend.pure.generated.Root_meta_pure_mapping_OperationSetImplementation_Impl; +import org.finos.legend.pure.generated.Root_meta_pure_mapping_relation_RelationFunctionInstanceSetImplementation_Impl; import org.finos.legend.pure.generated.Root_meta_pure_mapping_aggregationAware_AggregationAwareSetImplementation_Impl; import org.finos.legend.pure.m3.coreinstance.meta.external.store.model.PureInstanceSetImplementation; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.EmbeddedSetImplementation; @@ -41,6 +43,7 @@ import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.Mapping; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.MergeOperationSetImplementation; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.OperationSetImplementation; +import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.relation.RelationFunctionInstanceSetImplementation; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.SetImplementation; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.aggregationAware.AggregationAwareSetImplementation; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.LambdaFunction; @@ -48,6 +51,8 @@ import java.util.List; import java.util.Objects; +import static org.finos.legend.engine.language.pure.compiler.toPureGraph.HelperModelBuilder.getElementFullPath; + public class ClassMappingFirstPassBuilder implements ClassMappingVisitor>> { private final CompileContext context; @@ -164,4 +169,21 @@ public Pair> visit(Ag } return Tuples.pair(res, Lists.immutable.empty()); } + + @Override + public Pair> visit(RelationFunctionClassMapping classMapping) + { + final org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.Class pureClass = this.context.resolveClass(classMapping._class, classMapping.classSourceInformation); + String id = HelperMappingBuilder.getClassMappingId(classMapping, this.context); + final RelationFunctionInstanceSetImplementation baseSetImpl = new Root_meta_pure_mapping_relation_RelationFunctionInstanceSetImplementation_Impl(id, SourceInformationHelper.toM3SourceInformation(classMapping.sourceInformation), context.pureModel.getClass("meta::pure::mapping::relation::RelationFunctionInstanceSetImplementation")); + final RelationFunctionInstanceSetImplementation setImpl = baseSetImpl + ._class(pureClass) + ._id(id) + ._superSetImplementationId(classMapping.extendsClassMappingId) + ._root(classMapping.root) + ._parent(parentMapping) + ._propertyMappings(ListIterate.collect(classMapping.propertyMappings, p -> p.accept(new PropertyMappingBuilder(this.context, baseSetImpl, Lists.mutable.empty())))); + HelperMappingBuilder.buildMappingClassOutOfLocalProperties(setImpl, setImpl._propertyMappings(), this.context); + return Tuples.pair(setImpl, Lists.immutable.empty()); + } } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingSecondPassBuilder.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingSecondPassBuilder.java index b9c40a5913c..6739d63b7dc 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingSecondPassBuilder.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingSecondPassBuilder.java @@ -21,12 +21,17 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.ClassMappingVisitor; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.OperationClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwareClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PureInstanceClassMapping; import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; import org.finos.legend.pure.generated.Root_meta_pure_mapping_SetImplementationContainer_Impl; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.Mapping; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.OperationSetImplementation; +import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.relation.RelationFunctionInstanceSetImplementation; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.SetImplementation; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.FunctionDefinition; +import org.finos.legend.pure.m3.navigation.function.FunctionDescriptor; +import org.finos.legend.pure.m3.navigation.function.InvalidFunctionDescriptorException; import java.util.stream.Collectors; @@ -90,4 +95,23 @@ public SetImplementation visit(AggregationAwareClassMapping classMapping) this.context.getCompilerExtensions().getExtraAggregationAwareClassMappingSecondPassProcessors().forEach(processor -> processor.value(classMapping, this.parentMapping, this.context)); return null; } + + @Override + public SetImplementation visit(RelationFunctionClassMapping classMapping) + { + RelationFunctionInstanceSetImplementation setImpl = (RelationFunctionInstanceSetImplementation) parentMapping._classMappings().detect(c -> c._id().equals(HelperMappingBuilder.getClassMappingId(classMapping, context))); + String functionPath = classMapping.relationFunction.path; + String functionId; + try + { + functionId = FunctionDescriptor.isValidFunctionDescriptor(functionPath) ? FunctionDescriptor.functionDescriptorToId(functionPath) : functionPath; + } + catch (InvalidFunctionDescriptorException e) + { + throw new EngineException("Invalid function descriptor specified!", classMapping.relationFunction.sourceInformation, EngineErrorType.COMPILATION, e); + } + FunctionDefinition relationFunction = (FunctionDefinition) context.resolvePackageableElement(functionId, classMapping.sourceInformation); + setImpl._relationFunction(relationFunction); + return setImpl; + } } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingThirdPassBuilder.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingThirdPassBuilder.java index 77abf9c7e10..bec1cc44a42 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingThirdPassBuilder.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/ClassMappingThirdPassBuilder.java @@ -23,6 +23,7 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.ClassMappingVisitor; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.OperationClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwareClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PureInstanceClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PurePropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; @@ -164,6 +165,12 @@ public SetImplementation visit(AggregationAwareClassMapping classMapping) return null; } + @Override + public SetImplementation visit(RelationFunctionClassMapping classMapping) + { + return null; + } + private static void checkPureMappingCompatibility(CompileContext context, Type actualReturnType, Type signatureType, String errorStub, org.finos.legend.engine.protocol.pure.v1.model.SourceInformation errorSourceInformation) { diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PropertyMappingBuilder.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PropertyMappingBuilder.java index 5843f045cfa..e11b28526a8 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PropertyMappingBuilder.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/PropertyMappingBuilder.java @@ -22,15 +22,17 @@ import org.finos.legend.engine.protocol.pure.v1.model.context.EngineErrorType; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMappingVisitor; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwarePropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionPropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.xStore.XStorePropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PurePropertyMapping; import org.finos.legend.engine.shared.core.operational.Assert; import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; +import org.finos.legend.pure.generated.Root_meta_pure_mapping_relation_RelationFunctionInstanceSetImplementation_Impl; import org.finos.legend.pure.generated.Root_meta_pure_mapping_aggregationAware_AggregationAwarePropertyMapping_Impl; import org.finos.legend.pure.generated.Root_meta_external_store_model_PurePropertyMapping_Impl; +import org.finos.legend.pure.generated.Root_meta_pure_mapping_relation_RelationFunctionPropertyMapping_Impl; import org.finos.legend.pure.generated.Root_meta_pure_mapping_xStore_XStorePropertyMapping_Impl; import org.finos.legend.pure.generated.Root_meta_pure_metamodel_function_LambdaFunction_Impl; -import org.finos.legend.pure.generated.Root_meta_pure_metamodel_type_generics_GenericType_Impl; import org.finos.legend.pure.generated.Root_meta_pure_metamodel_valuespecification_VariableExpression_Impl; import org.finos.legend.pure.m3.compiler.postprocessing.processor.milestoning.MilestoningFunctions; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.EnumerationMapping; @@ -44,12 +46,18 @@ import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.PackageableElement; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.LambdaFunction; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.property.Property; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.multiplicity.Multiplicity; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.Class; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.Type; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.generics.GenericType; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.valuespecification.VariableExpression; import org.finos.legend.pure.m3.navigation.M3Paths; +import org.finos.legend.pure.m3.navigation.ProcessorSupport; +import org.finos.legend.pure.m3.navigation.relation._Column; +import org.finos.legend.pure.m3.navigation.relation._RelationType; import org.finos.legend.pure.m4.coreinstance.SourceInformation; +import org.finos.legend.pure.m4.exception.PureCompilationException; import java.util.function.Function; @@ -208,4 +216,39 @@ public PropertyMapping visit(AggregationAwarePropertyMapping propertyMapping) ._owner(immediateParent); return apm; } + + @Override + public PropertyMapping visit(RelationFunctionPropertyMapping propertyMapping) + { + SourceInformation sourceInfo = SourceInformationHelper.toM3SourceInformation(propertyMapping.sourceInformation); + ProcessorSupport processorSupport = context.pureModel.getExecutionSupport().getProcessorSupport(); + + Property property = HelperMappingBuilder.getMappedProperty(propertyMapping, this.context); + Multiplicity propertyMultiplicity = property._multiplicity(); + if (!org.finos.legend.pure.m3.navigation.multiplicity.Multiplicity.isToOne(propertyMultiplicity) && !org.finos.legend.pure.m3.navigation.multiplicity.Multiplicity.isZeroToOne(propertyMultiplicity)) + { + throw new EngineException("Properties in relation mappings can only have multiplicity 1 or 0..1, but the property '" + org.finos.legend.pure.m3.navigation.property.Property.getPropertyName(property) + "' has multiplicity " + org.finos.legend.pure.m3.navigation.multiplicity.Multiplicity.print(propertyMultiplicity) + ".", propertyMapping.sourceInformation, EngineErrorType.COMPILATION); + } + + Type propertyType = property._genericType()._rawType(); + if (!processorSupport.type_isPrimitiveType(propertyType)) + { + throw new EngineException("Relation mapping is only supported for primitive properties, but the property '" + org.finos.legend.pure.m3.navigation.property.Property.getPropertyName(property) + "' has type " + propertyType._name() + ".", propertyMapping.sourceInformation, EngineErrorType.COMPILATION); + } + RelationType newRelationType = _RelationType.build(Lists.mutable.with(_Column.getColumnInstance(propertyMapping.column, false, propertyType._name(), property._multiplicity(), sourceInfo, processorSupport)), sourceInfo, processorSupport); + org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.relation.RelationFunctionPropertyMapping relationFunctionPropertyMapping = new Root_meta_pure_mapping_relation_RelationFunctionPropertyMapping_Impl("", sourceInfo, context.pureModel.getClass("meta::pure::mapping::relation::RelationFunctionPropertyMapping")) + ._property(property) + ._sourceSetImplementationId(propertyMapping.source == null || propertyMapping.source.isEmpty() ? immediateParent._id() : propertyMapping.source) + ._targetSetImplementationId(HelperMappingBuilder.getPropertyMappingTargetId(propertyMapping, property, context)) + ._column(newRelationType._columns().toList().get(0)) + ._owner(immediateParent); + + if (propertyMapping.localMappingProperty != null) + { + relationFunctionPropertyMapping._localMappingProperty(true); + relationFunctionPropertyMapping._localMappingPropertyType(this.context.resolveType(propertyMapping.localMappingProperty.type, propertyMapping.localMappingProperty.sourceInformation)); + relationFunctionPropertyMapping._localMappingPropertyMultiplicity(this.context.pureModel.getMultiplicity(propertyMapping.localMappingProperty.multiplicity)); + } + return relationFunctionPropertyMapping; + } } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/validator/MappingValidator.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/validator/MappingValidator.java index c32493fe385..f64fcb694d9 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/validator/MappingValidator.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/validator/MappingValidator.java @@ -23,6 +23,7 @@ import org.eclipse.collections.impl.utility.LazyIterate; import org.finos.legend.engine.language.pure.compiler.toPureGraph.HelperModelBuilder; import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; +import org.finos.legend.engine.language.pure.compiler.toPureGraph.SourceInformationHelper; import org.finos.legend.engine.language.pure.compiler.toPureGraph.Warning; import org.finos.legend.engine.language.pure.compiler.toPureGraph.extension.CompilerExtensions; import org.finos.legend.engine.language.pure.compiler.toPureGraph.handlers.IncludedMappingHandler; @@ -32,6 +33,18 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.Mapping; import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.*; +import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.relation.RelationFunctionInstanceSetImplementation; +import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.relation.RelationFunctionPropertyMapping; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.FunctionDefinition; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.Column; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.FunctionType; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.generics.GenericType; +import org.finos.legend.pure.m3.navigation.M3Paths; +import org.finos.legend.pure.m3.navigation.ProcessorSupport; +import org.finos.legend.pure.m3.navigation.relation._Column; +import org.finos.legend.pure.m3.navigation.relation._RelationType; +import org.finos.legend.pure.m4.exception.PureCompilationException; import java.util.*; import java.util.stream.Collectors; @@ -56,6 +69,7 @@ public void validate(PureModel pureModel, PureModelContextData pureModelContextD this.validateEnumerationMappings(pureModel, mappings); this.validateMappingElementIds(pureModel, mappings, pureMappings, mappingByClassMappingId); this.validateClassMappingRoots(pureModel, mappings, pureMappings); + this.validateRelationFunctionClassMapping(pureModel, pureMappings); MappingValidatorContext mappingValidatorContext = new MappingValidatorContext(mappingByClassMappingId, pureMappings, mappings); extensions.getExtraMappingPostValidators().forEach(v -> v.accept(pureModel, mappingValidatorContext)); @@ -275,7 +289,7 @@ private Iterable checkId(PureModel pureModel, org.finos.legend.pure.m3. { if (!"".equals(id) && !ids.contains(id)) { - return Lists.mutable.with(new Warning(org.finos.legend.engine.language.pure.compiler.toPureGraph.SourceInformationHelper.fromM3SourceInformation(pm.getSourceInformation()), "Error '" + id + "' can't be found in the mapping " + pureModel.buildPackageString(mapping._package()._name(), mapping._name()))); + return Lists.mutable.with(new Warning(SourceInformationHelper.fromM3SourceInformation(pm.getSourceInformation()), "Error '" + id + "' can't be found in the mapping " + pureModel.buildPackageString(mapping._package()._name(), mapping._name()))); } return Lists.mutable.empty(); } @@ -311,4 +325,44 @@ public void validateClassMappingRoots(PureModel pureModel, Map } }); } + + public void validateRelationFunctionClassMapping(PureModel pureModel, Map pureMappings) + { + pureMappings.forEach((mappingPath, mapping) -> + mapping._classMappings().select(classMapping -> (classMapping instanceof RelationFunctionInstanceSetImplementation)).each(classMapping -> + { + RelationFunctionInstanceSetImplementation relationClassMapping = (RelationFunctionInstanceSetImplementation) classMapping; + FunctionDefinition relationFunction = relationClassMapping._relationFunction(); + ProcessorSupport processorSupport = pureModel.getExecutionSupport().getProcessorSupport(); + FunctionType functionType = (FunctionType) processorSupport.function_getFunctionType(relationFunction); + if (functionType._parameters().size() != 0) + { + throw new EngineException("Relation mapping function expecting arguments is not supported!", SourceInformationHelper.fromM3SourceInformation(relationFunction.getSourceInformation()), EngineErrorType.COMPILATION); + } + if (!processorSupport.type_subTypeOf(functionType._returnType()._rawType(), processorSupport.package_getByUserPath(M3Paths.Relation))) + { + throw new EngineException("Relation mapping function should return a Relation! Found a " + org.finos.legend.pure.m3.navigation.generictype.GenericType.print(functionType._returnType(), processorSupport) + " instead.", SourceInformationHelper.fromM3SourceInformation(relationFunction.getSourceInformation()), EngineErrorType.COMPILATION); + } + GenericType lastExpressionType = relationFunction._expressionSequence().toList().getLast()._genericType(); + RelationType relationType = (RelationType) lastExpressionType._typeArguments().toList().getFirst()._rawType(); + relationClassMapping._propertyMappings().each(pm -> + { + RelationFunctionPropertyMapping propertyMapping = (RelationFunctionPropertyMapping) pm; + try + { + Column column = (Column) _RelationType.findColumn(relationType, propertyMapping._column()._name(), propertyMapping.getSourceInformation(), processorSupport); + if (!org.finos.legend.pure.m3.navigation.generictype.GenericType.subTypeOf(_Column.getColumnType(column), _Column.getColumnType(propertyMapping._column()), processorSupport)) + { + throw new EngineException("Mismatching property and relation column types. Property type is " + _Column.getColumnType(propertyMapping._column())._rawType()._name() + ", but relation column it is mapped to has type " + _Column.getColumnType(column)._rawType()._name() + ".", SourceInformationHelper.fromM3SourceInformation(propertyMapping.getSourceInformation()), EngineErrorType.COMPILATION); + } + } + catch (PureCompilationException e) + { + throw new EngineException(e.getInfo(), SourceInformationHelper.fromM3SourceInformation(e.getSourceInformation()), EngineErrorType.COMPILATION); + } + }); + })); + + } + } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestMappingCompilationFromGrammar.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestMappingCompilationFromGrammar.java index 935c040ee0f..e38676d6186 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestMappingCompilationFromGrammar.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-compiler/src/test/java/org/finos/legend/engine/language/pure/compiler/test/fromGrammar/TestMappingCompilationFromGrammar.java @@ -2464,4 +2464,221 @@ public void testMappingSuiteDataReference() " ]\n" + ")\n"); } + + public static final String RELATION_MAPPING_PURE_SOURCE = "###Pure\n" + + "Class my::Person\n" + + "{\n" + + " firstName: String[1];\n" + + " age: Integer[1];\n" + + " firmId: Integer[1];\n" + + " address: my::Address[1];\n" + + "}\n" + + "\n" + + "Class my::Firm\n" + + "{\n" + + " id: Integer[1];\n" + + " legalName: String[1];\n" + + " clientNames: String[*];\n" + + "}\n" + + "\n" + + "Class my::Address\n" + + "{\n" + + " city: String[1];\n" + + "}\n" + + "\n" + + "Association my::Person_Firm\n" + + "{\n" + + " employees: my::Person[*];\n" + + " firm: my::Firm[1];\n" + + "}\n" + + "function my::personFunction():meta::pure::metamodel::relation::Relation[1]\n" + + "{\n" + + " 1->cast(@meta::pure::metamodel::relation::Relation<(FIRSTNAME:String, AGE:Integer, FIRMID:Integer, CITY:String)>);\n" + + "}\n"; + + public void testRelationMapping(String grammar) + { + test(RELATION_MAPPING_PURE_SOURCE + grammar); + } + + public void testRelationMapping(String grammar, String expectedErrorMsg) + { + test(RELATION_MAPPING_PURE_SOURCE + grammar, expectedErrorMsg); + } + + @Test + public void testValidRelationFunctionMapping() + { + testRelationMapping("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::personFunction():Relation[1]\n" + + " firstName: FIRSTNAME,\n" + + " age: AGE\n" + + " }\n" + + ")\n"); + } + + @Test + public void testValidRelationFunctionMappingWithLocalPropertyMapping() + { + testRelationMapping("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::personFunction():Relation[1]\n" + + " firstName: FIRSTNAME,\n" + + " age: AGE,\n" + + " +firmId: Integer[1]: FIRMID\n" + + " }\n" + + ")\n"); + } + + @Test + public void testValidRelationFunctionMappingWithQuotedRelationColumn() + { + testRelationMapping("###Pure\n" + + "import meta::pure::metamodel::relation::*;\n" + + "function my::personFunctionWithQuotedCol():meta::pure::metamodel::relation::Relation[1]\n" + + "{\n" + + " 1->cast(@meta::pure::metamodel::relation::Relation<(FIRSTNAME:String, AGE:Integer, 'FIRM ID':Integer, CITY:String)>);\n" + + "}\n" + + "###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person[person]: Relation\n" + + " {\n" + + " ~func my::personFunctionWithQuotedCol():Relation[1]\n" + + " firstName: FIRSTNAME," + + " firmId: 'FIRM ID'" + + " }\n" + + ")\n"); + } + + @Test + public void testRelationFunctionMappingWithInvalidFunctionPointer() + { + testRelationMapping("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::someRelationFunction():Relation[1]\n" + + " firstName: FIRSTNAME,\n" + + " age: AGE\n" + + " }\n" + + ")\n", "COMPILATION error at [34:3-39:3]: Can't find the packageable element 'my::someRelationFunction__Relation_1_'"); + } + + @Test + public void testRelationFunctionMappingWithNonRelationFunction() + { + testRelationMapping("###Pure\n" + + "function my::integerFunction():Integer[1]\n" + + "{\n" + + " 1;\n" + + "}\n" + + "###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::integerFunction():Integer[1]\n" + + " firstName: FIRSTNAME,\n" + + " age: AGE\n" + + " }\n" + + ")\n", "COMPILATION error at [32:1-35:1]: Relation mapping function should return a Relation! Found a Integer instead."); + + testRelationMapping("###Pure\n" + + "function my::relationFunction(): Any[1]\n" + + "{\n" + + " 1->cast(@meta::pure::metamodel::relation::Relation<(FIRSTNAME:String, AGE:Integer)>);\n" + + "}\n" + + "###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::relationFunction():Any[1]\n" + + " firstName: FIRSTNAME,\n" + + " age: AGE\n" + + " }\n" + + ")\n", "COMPILATION error at [32:1-35:1]: Relation mapping function should return a Relation! Found a Any instead."); + } + + @Test + public void testRelationFunctionMappingWithInvalidRelationColumn() + { + testRelationMapping("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::personFunction():Relation[1]\n" + + " firstName: FOO,\n" + + " age: AGE\n" + + " }\n" + + ")\n", "COMPILATION error at [37:5-18]: The system can't find the column FOO in the Relation (FIRSTNAME:String, AGE:Integer, FIRMID:Integer, CITY:String)"); + } + + @Test + public void testRelationFunctionMappingWithMismatchingTypes() + { + testRelationMapping("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::personFunction():Relation[1]\n" + + " firstName: AGE,\n" + + " age: AGE\n" + + " }\n" + + ")\n", "COMPILATION error at [37:5-18]: Mismatching property and relation column types. Property type is String, but relation column it is mapped to has type Integer."); + } + + @Test + public void testRelationFunctionMappingWithInvalidMultiplicityProperty() + { + testRelationMapping("###Pure\n" + + "Class my::Person1\n" + + "{\n" + + " name: String[*];\n" + + " age: Integer[1];\n" + + "}\n" + + "\n" + + "###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person1: Relation \n" + + " {\n" + + " ~func my::personFunction():Relation[1]\n" + + " name: NAME,\n" + + " age: AGE\n" + + " }\n" + + ")\n", "COMPILATION error at [44:5-14]: Properties in relation mappings can only have multiplicity 1 or 0..1, but the property 'name' has multiplicity [*]."); + } + + @Test + public void testRelationFunctionMappingWithArguments() + { + testRelationMapping("###Pure\n" + + "function my::relationFunction(i:Integer[1]):meta::pure::metamodel::relation::Relation[1]\n" + + "{\n" + + " 1->cast(@meta::pure::metamodel::relation::Relation<(FIRSTNAME:String, AGE:Integer)>);\n" + + "}\n" + + "###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " my::Person: Relation \n" + + " {\n" + + " ~func my::relationFunction(Integer[1]):Relation[1]\n" + + " firstName: FIRSTNAME,\n" + + " age: AGE\n" + + " }\n" + + ")\n", "COMPILATION error at [32:1-35:1]: Relation mapping function expecting arguments is not supported!"); + } + } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 index d2372e68efb..209eddb7097 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 @@ -266,5 +266,5 @@ toMultiplicity: INTEGER | STAR -functionIdentifier: qualifiedName PAREN_OPEN (qualifiedName multiplicity (COMMA qualifiedName multiplicity)*)? PAREN_CLOSE COLON qualifiedName multiplicity +functionIdentifier: qualifiedName PAREN_OPEN (functionTypePureType (COMMA functionTypePureType)*)? PAREN_CLOSE COLON functionTypePureType ; \ No newline at end of file diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingLexerGrammar.g4 b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingLexerGrammar.g4 new file mode 100644 index 00000000000..575c806f035 --- /dev/null +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingLexerGrammar.g4 @@ -0,0 +1,6 @@ +lexer grammar RelationFunctionMappingLexerGrammar; + +import M3LexerGrammar; + +RELATION_FUNC: '~func' ; +STRING: String; diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingParserGrammar.g4 b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingParserGrammar.g4 new file mode 100644 index 00000000000..4d062c6d742 --- /dev/null +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/mapping/relationFunctionMapping/RelationFunctionMappingParserGrammar.g4 @@ -0,0 +1,19 @@ +parser grammar RelationFunctionMappingParserGrammar; + +import M3ParserGrammar; + +options +{ + tokenVocab = RelationFunctionMappingLexerGrammar; +} + +// -------------------------------------- RELATION MAPPING -------------------------------------- + +relationMapping: RELATION_FUNC functionIdentifier + (singlePropertyMapping (COMMA singlePropertyMapping)*)? + EOF +; + +singlePropertyMapping: ((PLUS qualifiedName COLON type multiplicity) | qualifiedName) COLON identifier +; + diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/CorePureGrammarParser.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/CorePureGrammarParser.java index 04e5c3e4bf6..aa3086ace78 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/CorePureGrammarParser.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/CorePureGrammarParser.java @@ -34,6 +34,8 @@ import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.operationClassMapping.OperationClassMappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.pureInstanceClassMapping.PureInstanceClassMappingLexerGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.pureInstanceClassMapping.PureInstanceClassMappingParserGrammar; +import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.relationFunctionMapping.RelationFunctionMappingLexerGrammar; +import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.relationFunctionMapping.RelationFunctionMappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.xStoreAssociationMapping.XStoreAssociationMappingLexerGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.xStoreAssociationMapping.XStoreAssociationMappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.from.connection.ConnectionValueSourceCode; @@ -59,6 +61,7 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregateSpecification; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwareClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.mappingTest.InputData; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.xStore.XStoreAssociationMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.section.DefaultCodeSection; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.section.Section; @@ -87,6 +90,7 @@ public class CorePureGrammarParser implements PureGrammarParserExtension public static final String XSTORE_ASSOCIATION_MAPPING_TYPE = "XStore"; public static final String AGGREGATION_AWARE_MAPPING_TYPE = "AggregationAware"; public static final String AGGREGATE_SPECIFICATION = "AggregateSpecification"; + public static final String RELATION_EXPRESSION = "Relation"; private static RelationStoreAccessorPureParser relationStoreAccessorPureParser = new RelationStoreAccessorPureParser(); @@ -105,7 +109,9 @@ public Iterable getExtraMappingElementParsers() MappingElementParser.newParser(PURE_INSTANCE_CLASS_MAPPING_TYPE, CorePureGrammarParser::parsePureClassMapping), MappingElementParser.newParser(XSTORE_ASSOCIATION_MAPPING_TYPE, CorePureGrammarParser::parseXStoreAssociationMapping), MappingElementParser.newParser(AGGREGATION_AWARE_MAPPING_TYPE, CorePureGrammarParser::parseAggregationAwareMapping), - MappingElementParser.newParser(AGGREGATE_SPECIFICATION, CorePureGrammarParser::parseAggregateSpecification)); + MappingElementParser.newParser(AGGREGATE_SPECIFICATION, CorePureGrammarParser::parseAggregateSpecification), + MappingElementParser.newParser(RELATION_EXPRESSION, CorePureGrammarParser::parseRelationFunctionMapping) + ); } @Override @@ -285,6 +291,22 @@ private static AggregateSpecification parseAggregateSpecification(MappingElement return aggregateSpecification; } + private static ClassMapping parseRelationFunctionMapping(MappingElementSourceCode mappingElementSourceCode, PureGrammarParserContext parserContext) + { + MappingParserGrammar.MappingElementContext ctx = mappingElementSourceCode.mappingElementParserRuleContext; + SourceCodeParserInfo parserInfo = getRelationFunctionMappingParserInfo(mappingElementSourceCode); + RelationFunctionMappingParseTreeWalker walker = new RelationFunctionMappingParseTreeWalker(parserInfo.walkerSourceInformation, parserContext, mappingElementSourceCode); + RelationFunctionClassMapping relationFunctionClassMapping = new RelationFunctionClassMapping(); + String className = PureGrammarParserUtility.fromQualifiedName(ctx.qualifiedName().packagePath() == null ? Collections.emptyList() : ctx.qualifiedName().packagePath().identifier(), ctx.qualifiedName().identifier()); + relationFunctionClassMapping.id = ctx.mappingElementId() != null ? ctx.mappingElementId().getText() : null; + relationFunctionClassMapping._class = className; + relationFunctionClassMapping.root = ctx.STAR() != null; + relationFunctionClassMapping.extendsClassMappingId = ctx.superClassMappingId() != null ? ctx.superClassMappingId().getText() : null; + relationFunctionClassMapping.sourceInformation = parserInfo.sourceInformation; + walker.visitRelationFunctionMapping((RelationFunctionMappingParserGrammar.RelationMappingContext) parserInfo.rootContext, relationFunctionClassMapping); + return relationFunctionClassMapping; + } + private static InputData parseObjectInputData(MappingParserGrammar.TestInputElementContext inputDataContext, ParseTreeWalkerSourceInformation walkerSourceInformation) { SourceInformation testInputDataSourceInformation = walkerSourceInformation.getSourceInformation(inputDataContext); @@ -400,6 +422,20 @@ private static SourceCodeParserInfo getAggregateSpecificationParserInfo(MappingE return new SourceCodeParserInfo(mappingElementSourceCode.code, input, source, mappingElementSourceCode.mappingElementParseTreeWalkerSourceInformation, lexer, parser, parser.aggregateSpecification()); } + private static SourceCodeParserInfo getRelationFunctionMappingParserInfo(MappingElementSourceCode mappingElementSourceCode) + { + CharStream input = CharStreams.fromString(mappingElementSourceCode.code); + ParserErrorListener errorListener = new ParserErrorListener(mappingElementSourceCode.mappingElementParseTreeWalkerSourceInformation); + RelationFunctionMappingLexerGrammar lexer = new RelationFunctionMappingLexerGrammar(CharStreams.fromString(mappingElementSourceCode.code)); + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); + RelationFunctionMappingParserGrammar parser = new RelationFunctionMappingParserGrammar(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + SourceInformation source = mappingElementSourceCode.mappingParseTreeWalkerSourceInformation.getSourceInformation(mappingElementSourceCode.mappingElementParserRuleContext); + return new SourceCodeParserInfo(mappingElementSourceCode.code, input, source, mappingElementSourceCode.mappingElementParseTreeWalkerSourceInformation, lexer, parser, parser.relationMapping()); + } + private static Section parseDataSection(SectionSourceCode sectionSourceCode, Consumer elementConsumer, PureGrammarParserContext pureGrammarParserContext) { SourceCodeParserInfo parserInfo = getDataParserInfo(sectionSourceCode); diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/PureGrammarParserUtility.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/PureGrammarParserUtility.java index 34ad7545d85..b82dce7dd8a 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/PureGrammarParserUtility.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/PureGrammarParserUtility.java @@ -19,6 +19,7 @@ import org.apache.commons.text.StringEscapeUtils; import org.finos.legend.engine.protocol.pure.v1.model.SourceInformation; import org.finos.legend.engine.protocol.pure.v1.model.context.EngineErrorType; +import org.finos.legend.engine.protocol.pure.v1.model.domain.Multiplicity; import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; import java.util.ArrayList; @@ -135,4 +136,14 @@ public static String validatePath(String path, SourceInformation sourceInformati } return String.join(PACKAGE_SEPARATOR, parts); } + + public static Multiplicity buildMultiplicity(ParserRuleContext fromMultiplicity, ParserRuleContext toMultiplicity) + { + String star = "*"; + Multiplicity m = new Multiplicity(); + m.lowerBound = Integer.parseInt(fromMultiplicity != null ? fromMultiplicity.getText() : star.equals(toMultiplicity.getText()) ? "0" : toMultiplicity.getText()); + m.setUpperBound(star.equals(toMultiplicity.getText()) ? null : Integer.parseInt(toMultiplicity.getText())); + return m; + } + } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/PureInstanceClassMappingParseTreeWalker.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/PureInstanceClassMappingParseTreeWalker.java index b1c88712762..6d50304b591 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/PureInstanceClassMappingParseTreeWalker.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/PureInstanceClassMappingParseTreeWalker.java @@ -75,7 +75,7 @@ private PurePropertyMapping visitPurePropertyMapping(PureInstanceClassMappingPar // Local property mapping purePropertyMapping.localMappingProperty = new LocalMappingPropertyInfo(); purePropertyMapping.localMappingProperty.type = ctx.type().getText(); - purePropertyMapping.localMappingProperty.multiplicity = buildMultiplicity(ctx.multiplicity().multiplicityArgument()); + purePropertyMapping.localMappingProperty.multiplicity = PureGrammarParserUtility.buildMultiplicity(ctx.multiplicity().multiplicityArgument().fromMultiplicity(), ctx.multiplicity().multiplicityArgument().toMultiplicity()); purePropertyMapping.localMappingProperty.sourceInformation = this.walkerSourceInformation.getSourceInformation(ctx.qualifiedName()); } @@ -116,12 +116,4 @@ private Lambda visitLambda(PureInstanceClassMappingParserGrammar.CombinedExpress return lambda; } - private static Multiplicity buildMultiplicity(PureInstanceClassMappingParserGrammar.MultiplicityArgumentContext ctx) - { - String star = "*"; - Multiplicity m = new Multiplicity(); - m.lowerBound = Integer.parseInt(ctx.fromMultiplicity() != null ? ctx.fromMultiplicity().getText() : star.equals(ctx.toMultiplicity().getText()) ? "0" : ctx.toMultiplicity().getText()); - m.setUpperBound(star.equals(ctx.toMultiplicity().getText()) ? null : Integer.parseInt(ctx.toMultiplicity().getText())); - return m; - } } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/RelationFunctionMappingParseTreeWalker.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/RelationFunctionMappingParseTreeWalker.java new file mode 100644 index 00000000000..8f31c640084 --- /dev/null +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/mapping/RelationFunctionMappingParseTreeWalker.java @@ -0,0 +1,82 @@ +// Copyright 2025 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.language.pure.grammar.from.mapping; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.finos.legend.engine.language.pure.grammar.from.ParseTreeWalkerSourceInformation; +import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParserContext; +import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParserUtility; +import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.relationFunctionMapping.RelationFunctionMappingParserGrammar; +import org.finos.legend.engine.protocol.pure.v1.model.context.PackageableElementPointer; +import org.finos.legend.engine.protocol.pure.v1.model.context.PackageableElementType; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.LocalMappingPropertyInfo; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyPointer; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionPropertyMapping; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class RelationFunctionMappingParseTreeWalker +{ + private final ParseTreeWalkerSourceInformation walkerSourceInformation; + private final PureGrammarParserContext parserContext; + private final MappingElementSourceCode mappingElementSourceCode; + + public RelationFunctionMappingParseTreeWalker(ParseTreeWalkerSourceInformation walkerSourceInformation, PureGrammarParserContext parserContext, MappingElementSourceCode mappingElementSourceCode) + { + this.walkerSourceInformation = walkerSourceInformation; + this.parserContext = parserContext; + this.mappingElementSourceCode = mappingElementSourceCode; + } + + public void visitRelationFunctionMapping(RelationFunctionMappingParserGrammar.RelationMappingContext ctx, RelationFunctionClassMapping relationFunctionClassMapping) + { + relationFunctionClassMapping.relationFunction = new PackageableElementPointer(PackageableElementType.FUNCTION, ctx.functionIdentifier().getText(), walkerSourceInformation.getSourceInformation(ctx.functionIdentifier())); + relationFunctionClassMapping.propertyMappings = ctx.singlePropertyMapping() + .stream() + .map(c -> this.visitRelationPropertyMapping(c, relationFunctionClassMapping)) + .collect(Collectors.toList()); + } + + private PropertyMapping visitRelationPropertyMapping(RelationFunctionMappingParserGrammar.SinglePropertyMappingContext ctx, RelationFunctionClassMapping relationFunctionClassMapping) + { + RelationFunctionPropertyMapping propertyMapping = new RelationFunctionPropertyMapping(); + if (ctx.PLUS() != null) + { + LocalMappingPropertyInfo localMappingPropertyInfo = new LocalMappingPropertyInfo(); + localMappingPropertyInfo.type = ctx.type().getText(); + RelationFunctionMappingParserGrammar.MultiplicityArgumentContext mulCtx = ctx.multiplicity().multiplicityArgument(); + localMappingPropertyInfo.multiplicity = PureGrammarParserUtility.buildMultiplicity(mulCtx.fromMultiplicity(), mulCtx.toMultiplicity()); + localMappingPropertyInfo.sourceInformation = this.walkerSourceInformation.getSourceInformation(ctx.qualifiedName()); + propertyMapping.localMappingProperty = localMappingPropertyInfo; + } + + PropertyPointer propertyPointer = new PropertyPointer(); + propertyPointer.property = PureGrammarParserUtility.fromQualifiedName(ctx.qualifiedName().packagePath() == null ? Collections.emptyList() : ctx.qualifiedName().packagePath().identifier(), ctx.qualifiedName().identifier()); + propertyPointer._class = relationFunctionClassMapping._class; + propertyMapping.property = propertyPointer; + + propertyMapping.column = PureGrammarParserUtility.fromIdentifier(ctx.identifier()); + propertyMapping.source = relationFunctionClassMapping.id; + + propertyPointer.sourceInformation = this.walkerSourceInformation.getSourceInformation(ctx.qualifiedName()); + propertyMapping.sourceInformation = this.walkerSourceInformation.getSourceInformation(ctx); + return propertyMapping; + } + +} diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/DEPRECATED_PureGrammarComposerCore.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/DEPRECATED_PureGrammarComposerCore.java index 51c714057af..ed6a9254dc3 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/DEPRECATED_PureGrammarComposerCore.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/DEPRECATED_PureGrammarComposerCore.java @@ -48,6 +48,8 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMappingVisitor; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwareClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwarePropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionPropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.xStore.XStorePropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.PackageableRuntime; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.SingleConnectionEngineRuntime; @@ -520,8 +522,7 @@ public String visit(PurePropertyMapping purePropertyMapping) { purePropertyMapping.transform.parameters = Collections.emptyList(); String lambdaString = purePropertyMapping.transform.accept(this).replaceFirst("\\|", ""); - return (purePropertyMapping.localMappingProperty != null ? "+" : "") + PureGrammarComposerUtility.convertIdentifier(purePropertyMapping.property.property) + - (purePropertyMapping.localMappingProperty != null ? ": " + purePropertyMapping.localMappingProperty.type + "[" + HelperDomainGrammarComposer.renderMultiplicity(purePropertyMapping.localMappingProperty.multiplicity) + "]" : "") + + return PureGrammarComposerUtility.renderPossibleLocalMappingProperty(purePropertyMapping) + (purePropertyMapping.explodeProperty != null && purePropertyMapping.explodeProperty ? "*" : "") + (purePropertyMapping.target == null || purePropertyMapping.target.isEmpty() ? "" : "[" + PureGrammarComposerUtility.convertIdentifier(purePropertyMapping.target) + "]") + (purePropertyMapping.enumMappingId == null ? "" : ": EnumerationMapping " + purePropertyMapping.enumMappingId) + @@ -553,12 +554,28 @@ public String visit(AggregationAwareClassMapping classMapping) getTabString() + "}"; } + @Override + public String visit(RelationFunctionClassMapping classMapping) + { + return ": Relation\n" + + getTabString(getBaseTabLevel()) + "{\n" + + getTabString(getBaseTabLevel() + 1) + "~func " + classMapping.relationFunction.path + "\n" + + LazyIterate.collect(classMapping.propertyMappings, pm -> getTabString(getBaseTabLevel() + 1) + pm.accept(this)).makeString(",\n") + (classMapping.propertyMappings.isEmpty() ? "" : "\n") + + getTabString(getBaseTabLevel()) + "}"; + } + @Override public String visit(AggregationAwarePropertyMapping propertyMapping) { return unsupported(AggregationAwarePropertyMapping.class); } + @Override + public String visit(RelationFunctionPropertyMapping propertyMapping) + { + return PureGrammarComposerUtility.renderPossibleLocalMappingProperty(propertyMapping) + + ": " + PureGrammarComposerUtility.convertIdentifier(propertyMapping.column, false); + } // ----------------------------------------------- CONNECTION ----------------------------------------------- diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/PureGrammarComposerUtility.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/PureGrammarComposerUtility.java index 4cdcde9d21f..70c90244df4 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/PureGrammarComposerUtility.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/to/PureGrammarComposerUtility.java @@ -15,6 +15,7 @@ package org.finos.legend.engine.language.pure.grammar.to; import org.apache.commons.text.StringEscapeUtils; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMapping; import java.util.Arrays; import java.util.List; @@ -184,4 +185,10 @@ public static String renderObject(Object value) return String.valueOf(value); } + public static String renderPossibleLocalMappingProperty(PropertyMapping propertyMapping) + { + return (propertyMapping.localMappingProperty != null ? "+" : "") + PureGrammarComposerUtility.convertIdentifier(propertyMapping.property.property) + + (propertyMapping.localMappingProperty != null ? ": " + propertyMapping.localMappingProperty.type + "[" + HelperDomainGrammarComposer.renderMultiplicity(propertyMapping.localMappingProperty.multiplicity) + "]" : ""); + } + } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/parser/TestMappingGrammarParser.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/parser/TestMappingGrammarParser.java index 75fcc06212e..ecc525b9b96 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/parser/TestMappingGrammarParser.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/parser/TestMappingGrammarParser.java @@ -17,11 +17,13 @@ import org.antlr.v4.runtime.Vocabulary; import org.eclipse.collections.impl.list.mutable.FastList; import org.eclipse.collections.impl.list.mutable.ListAdapter; +import org.eclipse.collections.impl.utility.ListIterate; import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParser; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.MappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.enumerationMapping.EnumerationMappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.operationClassMapping.OperationClassMappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.pureInstanceClassMapping.PureInstanceClassMappingParserGrammar; +import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.relationFunctionMapping.RelationFunctionMappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.from.antlr4.mapping.xStoreAssociationMapping.XStoreAssociationMappingParserGrammar; import org.finos.legend.engine.language.pure.grammar.test.TestGrammarParser; import org.finos.legend.engine.protocol.pure.v1.model.context.PackageableElementType; @@ -30,6 +32,8 @@ import org.finos.legend.engine.protocol.pure.v1.model.domain.Class; import org.finos.legend.engine.protocol.pure.v1.model.domain.Multiplicity; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.Mapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionPropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.application.AppliedProperty; import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.datatype.CLatestDate; import org.finos.legend.engine.shared.core.operational.Assert; @@ -55,7 +59,8 @@ public List getDelegatedParserGrammarVocabulary() EnumerationMappingParserGrammar.VOCABULARY, OperationClassMappingParserGrammar.VOCABULARY, PureInstanceClassMappingParserGrammar.VOCABULARY, - XStoreAssociationMappingParserGrammar.VOCABULARY + XStoreAssociationMappingParserGrammar.VOCABULARY, + RelationFunctionMappingParserGrammar.VOCABULARY ); } @@ -82,6 +87,12 @@ public String getParserGrammarIdentifierInclusionTestCode(List keywords) " firstName: $src.fullName->substring(0, $src.fullName->indexOf(' ')),\n" + " incType : EnumerationMapping a : 1" + " }\n" + + // relation expression class mapping + " *anything::goes[anything_goes]: Relation\n" + + " {\n" + + " ~func anything::goes::" + ListAdapter.adapt(keywords).makeString("::") + "():Any[1]\n" + + " firstName: firstName\n" + + " }\n" + "\n" + ")\n"; } @@ -873,4 +884,178 @@ public void testLatestMultiplicity() Multiplicity latest = ((CLatestDate) ((AppliedProperty) model.getElementsOfType(Class.class).get(0).qualifiedProperties.get(0).body.get(0)).parameters.get(1)).multiplicity; Assert.assertTrue(latest.isUpperBoundEqualTo(1), () -> "CLatestDate must have multiplicity set to 1"); } + + @Test + public void testFaultyRelationFunctionMapping() + { + // Missing relation function + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *Person: Relation\n" + + " {\n" + + " firstName : firstName,\n" + + " firm : jsonColumn\n" + + " }\n" + + ")\n", "PARSER error at [6:9-17]: Unexpected token"); + + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " firstName : firstName\n" + + " firm : jsonColumn\n" + + " }\n" + + ")\n", "PARSER error at [8:9-12]: Unexpected token 'firm'. Valid alternatives: [',']"); + + // Column names cannot contain special characters + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Relation[1]\n" + + " firstName : table.firstName,\n" + + " firm : jsonColumn\n" + + " }\n" + + ")\n", "PARSER error at [7:26]: Unexpected token '.'. Valid alternatives: [',']"); + } + + @Test + public void testValidRelationFunctionMapping() + { + // Empty property mappings are allowed + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " }\n" + + ")\n"); + + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " firstName : firstName,\n" + + " firm : jsonColumn\n" + + " }\n" + + ")\n"); + + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " firstName : firstName,\n" + + " firm : jsonColumn\n" + + " }\n" + + ")\n"); + + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Relation[1]\n" + + " firstName : firstName,\n" + + " firm : jsonColumn\n" + + " }\n" + + ")\n"); + + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " firstName : firstName,\n" + + " +firm : String[0..1] : firm,\n" + + " +localProp : String[1] : localProp\n" + + " }\n" + + ")\n"); + + test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " firstName : 'FIRST NAME',\n" + + " firm : 'firm \"name\"'\n" + + " }\n" + + ")\n"); + } + + @Test + public void testRelationFunctionMappingSourceInformation() + { + PureModelContextData pureModelContextData = test("###Mapping\n" + + "Mapping my::testMapping\n" + + "(\n" + + " *my::Person[person]: Relation\n" + + " {\n" + + " ~func my::testFunc():Relation[1]\n" + + " firstName : 'FIRST NAME',\n" + + " age : AGE,\n" + + " +firmId : String[0..1] : FIRMID\n" + + " }\n" + + ")\n"); + + Mapping mapping = (Mapping) ListIterate.select(pureModelContextData.getElements(), e -> e.name.equals("testMapping")).get(0); + + org.junit.Assert.assertEquals(1, mapping.classMappings.size()); + org.junit.Assert.assertTrue(mapping.classMappings.get(0) instanceof RelationFunctionClassMapping); + RelationFunctionClassMapping classMapping = (RelationFunctionClassMapping) mapping.classMappings.get(0); + + org.junit.Assert.assertEquals("person", classMapping.id); + org.junit.Assert.assertEquals("my::Person", classMapping._class); + org.junit.Assert.assertEquals(PackageableElementType.FUNCTION, classMapping.relationFunction.type); + org.junit.Assert.assertEquals("my::testFunc():Relation[1]", classMapping.relationFunction.path); + org.junit.Assert.assertEquals(6, classMapping.relationFunction.sourceInformation.startLine); + org.junit.Assert.assertEquals(15, classMapping.relationFunction.sourceInformation.startColumn); + org.junit.Assert.assertEquals(6, classMapping.relationFunction.sourceInformation.endLine); + org.junit.Assert.assertEquals(45, classMapping.relationFunction.sourceInformation.endColumn); + org.junit.Assert.assertEquals(4, classMapping.sourceInformation.startLine); + org.junit.Assert.assertEquals(5, classMapping.sourceInformation.startColumn); + org.junit.Assert.assertEquals(10, classMapping.sourceInformation.endLine); + org.junit.Assert.assertEquals(5, classMapping.sourceInformation.endColumn); + + org.junit.Assert.assertEquals(3, classMapping.propertyMappings.size()); + + RelationFunctionPropertyMapping propertyMapping1 = (RelationFunctionPropertyMapping) classMapping.propertyMappings.get(0); + org.junit.Assert.assertEquals("my::Person", propertyMapping1.property._class); + org.junit.Assert.assertEquals("firstName", propertyMapping1.property.property); + org.junit.Assert.assertEquals("FIRST NAME", propertyMapping1.column); + org.junit.Assert.assertEquals(7, propertyMapping1.sourceInformation.startLine); + org.junit.Assert.assertEquals(9, propertyMapping1.sourceInformation.startColumn); + org.junit.Assert.assertEquals(7, propertyMapping1.sourceInformation.endLine); + org.junit.Assert.assertEquals(32, propertyMapping1.sourceInformation.endColumn); + + RelationFunctionPropertyMapping propertyMapping2 = (RelationFunctionPropertyMapping) classMapping.propertyMappings.get(1); + org.junit.Assert.assertEquals("my::Person", propertyMapping2.property._class); + org.junit.Assert.assertEquals("age", propertyMapping2.property.property); + org.junit.Assert.assertEquals("AGE", propertyMapping2.column); + org.junit.Assert.assertEquals(8, propertyMapping2.sourceInformation.startLine); + org.junit.Assert.assertEquals(9, propertyMapping2.sourceInformation.startColumn); + org.junit.Assert.assertEquals(8, propertyMapping2.sourceInformation.endLine); + org.junit.Assert.assertEquals(17, propertyMapping2.sourceInformation.endColumn); + + RelationFunctionPropertyMapping propertyMapping3 = (RelationFunctionPropertyMapping) classMapping.propertyMappings.get(2); + org.junit.Assert.assertEquals("my::Person", propertyMapping3.property._class); + org.junit.Assert.assertEquals("firmId", propertyMapping3.property.property); + org.junit.Assert.assertEquals("FIRMID", propertyMapping3.column); + org.junit.Assert.assertEquals("String", propertyMapping3.localMappingProperty.type); + org.junit.Assert.assertEquals(0, propertyMapping3.localMappingProperty.multiplicity.lowerBound); + org.junit.Assert.assertEquals(9, propertyMapping3.sourceInformation.startLine); + org.junit.Assert.assertEquals(9, propertyMapping3.sourceInformation.startColumn); + org.junit.Assert.assertEquals(9, propertyMapping3.sourceInformation.endLine); + org.junit.Assert.assertEquals(39, propertyMapping3.sourceInformation.endColumn); + } } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/roundtrip/TestMappingGrammarRoundtrip.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/roundtrip/TestMappingGrammarRoundtrip.java index bc416e0e9af..8392a77614d 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/roundtrip/TestMappingGrammarRoundtrip.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/roundtrip/TestMappingGrammarRoundtrip.java @@ -931,4 +931,41 @@ public void testComplexPropertyMappedToComplexSourceWithoutClassMapping() " }\n" + ")\n"); } + + @Test + public void testRelationFunctionMapping() + { + // Empty property mappings are allowed + test("###Mapping\n" + + "Mapping mappingPackage::myMapping\n" + + "(\n" + + " *my::Person[my_Person]: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " }\n" + + ")\n"); + + test("###Mapping\n" + + "Mapping mappingPackage::myMapping\n" + + "(\n" + + " *my::Person[person]: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " firstName: firstName,\n" + + " firm: jsonColumn\n" + + " }\n" + + ")\n"); + + test("###Mapping\n" + + "Mapping mappingPackage::myMapping\n" + + "(\n" + + " *my::Person[p]: Relation\n" + + " {\n" + + " ~func my::testFunc():Any[1]\n" + + " firstName: 'first name',\n" + + " firm: 'firm \"name\"',\n" + + " +localProp: String[0..1]: localProp\n" + + " }\n" + + ")\n"); + } } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMapping.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMapping.java index a741e73a724..06a0f221e43 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMapping.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMapping.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.finos.legend.engine.protocol.pure.v1.model.SourceInformation; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwareClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PureInstanceClassMapping; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "_type") @@ -27,7 +28,7 @@ @JsonSubTypes.Type(value = PureInstanceClassMapping.class, name = "pureInstance"), @JsonSubTypes.Type(value = AggregationAwareClassMapping.class, name = "aggregationAware"), @JsonSubTypes.Type(value = MergeOperationClassMapping.class, name = "mergeOperation"), - + @JsonSubTypes.Type(value = RelationFunctionClassMapping.class, name = "relation"), }) public abstract class ClassMapping { diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMappingVisitor.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMappingVisitor.java index 6061d62c371..a59bc3519ce 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMappingVisitor.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/ClassMappingVisitor.java @@ -15,6 +15,7 @@ package org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwareClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PureInstanceClassMapping; public interface ClassMappingVisitor @@ -27,4 +28,6 @@ public interface ClassMappingVisitor T visit(PureInstanceClassMapping classMapping); T visit(AggregationAwareClassMapping classMapping); + + T visit(RelationFunctionClassMapping classMapping); } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMapping.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMapping.java index bf5c3e5fce0..95bd9a7dbfa 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMapping.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMapping.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.finos.legend.engine.protocol.pure.v1.model.SourceInformation; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwarePropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionPropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.xStore.XStorePropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PurePropertyMapping; @@ -26,6 +27,7 @@ @JsonSubTypes.Type(value = PurePropertyMapping.class, name = "purePropertyMapping"), @JsonSubTypes.Type(value = XStorePropertyMapping.class, name = "xStorePropertyMapping"), @JsonSubTypes.Type(value = AggregationAwarePropertyMapping.class, name = "AggregationAwarePropertyMapping"), + @JsonSubTypes.Type(value = RelationFunctionPropertyMapping.class, name = "relationFunctionPropertyMapping"), }) public abstract class PropertyMapping { @@ -40,5 +42,4 @@ public T accept(PropertyMappingVisitor visitor) return visitor.visit(this); } - ; } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMappingVisitor.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMappingVisitor.java index 6dc755c31e9..2d7d7125817 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMappingVisitor.java +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/PropertyMappingVisitor.java @@ -15,6 +15,7 @@ package org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.aggregationAware.AggregationAwarePropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction.RelationFunctionPropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.xStore.XStorePropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.mapping.PurePropertyMapping; @@ -28,4 +29,7 @@ public interface PropertyMappingVisitor T visit(XStorePropertyMapping propertyMapping); T visit(AggregationAwarePropertyMapping propertyMapping); + + T visit(RelationFunctionPropertyMapping propertyMapping); + } diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionClassMapping.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionClassMapping.java new file mode 100644 index 00000000000..650e4541603 --- /dev/null +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionClassMapping.java @@ -0,0 +1,34 @@ +// Copyright 2025 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction; + +import org.finos.legend.engine.protocol.pure.v1.model.context.PackageableElementPointer; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.ClassMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.ClassMappingVisitor; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMapping; + +import java.util.List; + +public class RelationFunctionClassMapping extends ClassMapping +{ + public PackageableElementPointer relationFunction; + public List propertyMappings; + + @Override + public T accept(ClassMappingVisitor visitor) + { + return visitor.visit(this); + } +} diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionPropertyMapping.java b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionPropertyMapping.java new file mode 100644 index 00000000000..8aab73eeeaf --- /dev/null +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-protocol-pure/src/main/java/org/finos/legend/engine/protocol/pure/v1/model/packageableElement/mapping/relationFunction/RelationFunctionPropertyMapping.java @@ -0,0 +1,29 @@ +// Copyright 2025 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.relationFunction; + +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMapping; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMappingVisitor; + +public class RelationFunctionPropertyMapping extends PropertyMapping +{ + public String column; + + @Override + public T accept(PropertyMappingVisitor visitor) + { + return visitor.visit(this); + } +} diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/models/metamodel.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/models/metamodel.pure index 1e4eb7bd7a6..2cc6ad98335 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/models/metamodel.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/models/metamodel.pure @@ -66,7 +66,8 @@ Enum meta::protocols::pure::v1_33_0::metamodel::PackageableElementType DIAGRAM, DATASTORESPEC, UNIT, - MEASURE + MEASURE, + FUNCTION } Class meta::protocols::pure::v1_33_0::metamodel::ExecutionContext @@ -519,6 +520,23 @@ Class meta::protocols::pure::v1_33_0::metamodel::objectReference::AlloyObjectRef operationResolvedSetsId : String[*]; } +Class meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec +{ + name: String[1]; + type: String[0..1]; + function1: meta::protocols::pure::v1_33_0::metamodel::valueSpecification::raw::Lambda[0..1]; + function2: meta::protocols::pure::v1_33_0::metamodel::valueSpecification::raw::Lambda[0..1]; +} + +Class meta::protocols::pure::v1_33_0::metamodel::relation::ColSpecArray +{ + colSpecs: meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec[*]; +} + +Class meta::protocols::pure::v1_33_0::metamodel::store::RelationStoreAccessor +{ + path:String[*]; +} Class meta::protocols::pure::v1_33_0::metamodel::store::Store extends meta::protocols::pure::v1_33_0::metamodel::PackageableElement { @@ -727,7 +745,16 @@ Class meta::protocols::pure::v1_33_0::metamodel::mapping::modelToModel::Aggregat { } +Class meta::protocols::pure::v1_33_0::metamodel::mapping::relation::RelationFunctionClassMapping extends meta::protocols::pure::v1_33_0::metamodel::mapping::ClassMapping +{ + relationFunction: meta::protocols::pure::v1_33_0::metamodel::PackageableElementPointer[1]; + propertyMappings : meta::protocols::pure::v1_33_0::metamodel::mapping::PropertyMapping[*]; +} +Class meta::protocols::pure::v1_33_0::metamodel::mapping::relation::RelationFunctionPropertyMapping extends meta::protocols::pure::v1_33_0::metamodel::mapping::PropertyMapping +{ + column: String[1]; +} Class meta::protocols::pure::v1_33_0::metamodel::runtime::Connection { diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/scan/buildBasePureModel.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/scan/buildBasePureModel.pure index 095d5eda0d0..77a5df41109 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/scan/buildBasePureModel.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/scan/buildBasePureModel.pure @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +import meta::pure::router::store::routing::*; import meta::protocols::*; import meta::json::*; import meta::pure::store::*; import meta::external::store::model::*; import meta::pure::milestoning::*; import meta::pure::mapping::*; +import meta::pure::mapping::relation::*; import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::*; Class <> meta::protocols::pure::v1_33_0::transformation::fromPureGraph::AllTypes @@ -252,8 +254,9 @@ function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::extractS { $si->match($extensions.serializerExtension('v1_33_0')->cast(@meta::protocols::pure::v1_33_0::extension::SerializerExtension_v1_33_0).scan_buildBasePureModel_extractStores->map(e|$e->eval($m, $extensions))->concatenate([ ins: OperationSetImplementation[1] | [], - p: PureInstanceSetImplementation[1] | [] - ])->toOneMany() + p: PureInstanceSetImplementation[1] | [], + r: RelationFunctionInstanceSetImplementation[1] | $si->routeRelationFunctionSetImplementation($m, $extensions)->meta::pure::router::routing::getStoreFromSet($m, $extensions) + ])->toOneMany() ); } @@ -321,6 +324,10 @@ function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::buildBas ->concatenate($allMappings->map(m|$m->meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::transformMapping($extensions))) ->concatenate($stores->map(store|$store->meta::protocols::pure::v1_33_0::transformation::fromPureGraph::store::transformStore($extensions))) ->concatenate($extensions->map(e|$e.serializerExtension('v1_33_0')->cast(@meta::protocols::pure::v1_33_0::extension::SerializerExtension_v1_33_0).scan_buildBasePureModel_getExtraElementsFromStores->map(z|$z->eval($stores,$extensions)))) + ->concatenate($allMappings->map(m|$m.classMappings->map(cm|$cm->match([ + r:RelationFunctionInstanceSetImplementation[1]|$r.relationFunction->cast(@ConcreteFunctionDefinition)->transformFunction($extensions), + a:Any[1]|[] + ])))) ) ); } diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/mapping.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/mapping.pure index 4d27285d4bf..3ee97e520bd 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/mapping.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/mapping.pure @@ -17,10 +17,13 @@ import meta::relational::tests::csv::mapping::*; import meta::flatten::metamodel::*; import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::modelToModel::*; import meta::pure::mapping::xStore::*; +import meta::pure::mapping::relation::*; import meta::external::store::model::*; import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::*; +import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::domain::*; import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::csv::*; import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::*; +import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::relation::*; import meta::pure::mapping::*; function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::transformMapping(mapping:Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::v1_33_0::metamodel::mapping::Mapping[1] @@ -68,7 +71,8 @@ function <> meta::protocols::pure::v1_33_0::transformation::fromP $si->match($extensions.serializerExtension('v1_33_0')->cast(@meta::protocols::pure::v1_33_0::extension::SerializerExtension_v1_33_0).transfers_mapping_transformSetImplementation->map(f|$f->eval($mapping))->concatenate( $extensions.serializerExtension('v1_33_0')->cast(@meta::protocols::pure::v1_33_0::extension::SerializerExtension_v1_33_0).transfers_mapping_transformSetImplementation2->map(f|$f->eval($mapping, $extensions)))->concatenate([ o:OperationSetImplementation[1]| $o->transformOperationSetImplementation($mapping,$extensions), - p:PureInstanceSetImplementation[1]| $p->transformPureInstanceSetImplementation($mapping, $extensions) + p:PureInstanceSetImplementation[1]| $p->transformPureInstanceSetImplementation($mapping, $extensions), + r:RelationFunctionInstanceSetImplementation[1]| $r->meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::relation::transformRelationFunctionInstanceSetImplementation($mapping, $extensions) ])->toOneMany() ); } @@ -126,7 +130,7 @@ function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping: ) ) ); - + if($operation == meta::protocols::pure::v1_33_0::metamodel::mapping::MappingOperation.MERGE, | ^meta::protocols::pure::v1_33_0::metamodel::mapping::MergeOperationClassMapping ( @@ -178,6 +182,21 @@ function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping: ); } +function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::relation::transformRelationFunctionInstanceSetImplementation(r:RelationFunctionInstanceSetImplementation[1], mapping:Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::v1_33_0::metamodel::mapping::relation::RelationFunctionClassMapping[1] +{ + ^meta::protocols::pure::v1_33_0::metamodel::mapping::relation::RelationFunctionClassMapping + ( + id = $r.id, + _type = 'relation', + class = $r.class->elementToPath(), + root = $r.root, + extendsClassMappingId = $r.superSetImplementationId, + mappingClass = $r.mappingClass->map(mc|$mc->transformMappingClass($mapping, $extensions)), + propertyMappings = $r.propertyMappings->map(pm|$pm->transformRelationFunctionPropertyMapping($mapping, $extensions)), + relationFunction = ^meta::protocols::pure::v1_33_0::metamodel::PackageableElementPointer(type=meta::protocols::pure::v1_33_0::metamodel::PackageableElementType.FUNCTION, path=$r.relationFunction->elementToPath()) + ); +} + function <> meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::modelToModel::transformPropertyMapping(pm:PropertyMapping[1], mapping : Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::v1_33_0::metamodel::mapping::PropertyMapping[1] { $pm->match([p:PurePropertyMapping[1]| ^meta::protocols::pure::v1_33_0::metamodel::mapping::modelToModel::PurePropertyMapping @@ -222,3 +241,26 @@ function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping: ) ]); } + +function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::mapping::relation::transformRelationFunctionPropertyMapping(pm:PropertyMapping[1], mapping : Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::v1_33_0::metamodel::mapping::relation::RelationFunctionPropertyMapping[1] +{ + $pm->match([r:meta::pure::mapping::relation::RelationFunctionPropertyMapping[1]| + ^meta::protocols::pure::v1_33_0::metamodel::mapping::relation::RelationFunctionPropertyMapping + ( + _type = 'RelationFunctionPropertyMapping', + property = ^meta::protocols::pure::v1_33_0::metamodel::domain::PropertyPtr(class=$pm.property->genericType().typeArguments->at(0).rawType->toOne()->elementToPath(), property=$pm.property.name->toOne()), + column = $r.column.name->toOne(), + source = $r.sourceSetImplementationId, + target = $r.targetSetImplementationId, + localMappingProperty = if ($r.localMappingProperty->isNotEmpty() && $r.localMappingProperty->toOne(), + | ^meta::protocols::pure::v1_33_0::metamodel::mapping::LocalMappingPropertyInfo + ( + type = $r.localMappingPropertyType->toOne()->elementToPath(), + multiplicity = $r.localMappingPropertyMultiplicity->toOne()->meta::protocols::pure::v1_33_0::transformation::fromPureGraph::domain::transformMultiplicity() + ), + | [] + ) + ) + ]); +} + diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/valueSpecification.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/valueSpecification.pure index 69959487d37..a252492086d 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/valueSpecification.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/v1_33_0/transfers/valueSpecification.pure @@ -13,6 +13,10 @@ // limitations under the License. ###Pure +import meta::pure::extension::*; +import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::*; +import meta::protocols::pure::v1_33_0::transformation::fromPureGraph::relation::*; +import meta::pure::metamodel::relation::*; import meta::pure::milestoning::*; import meta::pure::graphFetch::execution::*; import meta::json::*; @@ -76,7 +80,18 @@ function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::valueSpe agg_FunctionDefinition_1__FunctionDefinition_1__AggregateValue_1_, agg_String_1__FunctionDefinition_1__FunctionDefinition_1__AggregateValue_1_, func_String_1__FunctionDefinition_1__TdsOlapAggregation_1_, - func_FunctionDefinition_1__TdsOlapRank_1_ + func_FunctionDefinition_1__TdsOlapRank_1_, + + meta::pure::functions::relation::colSpecArray_String_MANY__T_1__ColSpecArray_1_, + meta::pure::functions::relation::colSpec_String_1__T_1__ColSpec_1_, + meta::pure::functions::relation::funcColSpecArray_FuncColSpec_MANY__P_1__FuncColSpecArray_1_, + meta::pure::functions::relation::funcColSpecArray2_FuncColSpec_MANY__P_1__FuncColSpecArray_1_, + meta::pure::functions::relation::funcColSpec_Function_1__String_1__T_1__FuncColSpec_1_, + meta::pure::functions::relation::funcColSpec2_Function_1__String_1__T_1__FuncColSpec_1_, + meta::pure::functions::relation::aggColSpecArray_AggColSpec_MANY__P_1__AggColSpecArray_1_, + meta::pure::functions::relation::aggColSpecArray2_AggColSpec_MANY__P_1__AggColSpecArray_1_, + meta::pure::functions::relation::aggColSpec_Function_1__Function_1__String_1__T_1__AggColSpec_1_, + meta::pure::functions::relation::aggColSpec2_Function_1__Function_1__String_1__T_1__AggColSpec_1_ ]; let isExtractEnum = $fe->meta::protocols::pure::v1_33_0::transformation::fromPureGraph::valueSpecification::hasExtractEnumValue(); @@ -310,6 +325,69 @@ function <> meta::protocols::pure::v1_33_0::transformation::from ( _type = 'latestDate' ), + c:meta::pure::metamodel::relation::ColSpec[1]| + ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpec', + value = $c.name->transformColSpec() + );, + c:meta::pure::metamodel::relation::ColSpecArray[1]| + + ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpecArray', + value = ^meta::protocols::pure::v1_33_0::metamodel::relation::ColSpecArray + ( + colSpecs = $c.names->map(n|$n->transformColSpec()) + ) + );, + f:meta::pure::metamodel::relation::FuncColSpec[1]| + ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpec', + value = $f->transformFuncColSpec($extensions) + );, + f:meta::pure::metamodel::relation::FuncColSpecArray[1]| + ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpecArray', + value = ^meta::protocols::pure::v1_33_0::metamodel::relation::ColSpecArray + ( + colSpecs = $f.funcSpecs->map(c|$c->transformFuncColSpec($extensions)) + ) + );, + a:meta::pure::metamodel::relation::AggColSpec[1]| + ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpec', + value = $a->transformAggColSpec($extensions) + );, + a:meta::pure::metamodel::relation::AggColSpecArray[1]| + ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpecArray', + value = ^meta::protocols::pure::v1_33_0::metamodel::relation::ColSpecArray + ( + colSpecs = $a.aggSpecs->map(c|$c->transformAggColSpec($extensions)) + ) + );, + + r:meta::pure::store::RelationStoreAccessor[1]| + ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = '>', + value = ^meta::protocols::pure::v1_33_0::metamodel::store::RelationStoreAccessor + ( + path = $r.path + ) + ), c:Class[1]| ^meta::protocols::pure::v1_33_0::metamodel::valueSpecification::raw::PackageableElementPtr ( @@ -521,6 +599,33 @@ function <> meta::protocols::pure::v1_33_0::transformation::from ); } +function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::relation::transformColSpec(c:String[1]):meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec[1] +{ + ^meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec + ( + name = $c + ) +} + +function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::relation::transformFuncColSpec(f:FuncColSpec[1], extensions:Extension[*]):meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec[1] +{ + ^meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec + ( + name = $f.name, + function1 = $f.function->cast(@FunctionDefinition)->transformLambda($extensions) + ) +} + +function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::relation::transformAggColSpec(a:AggColSpec[1], extensions:Extension[*]):meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec[1] +{ + ^meta::protocols::pure::v1_33_0::metamodel::relation::ColSpec + ( + name = $a.name, + function1 = $a.map->cast(@FunctionDefinition)->transformLambda($extensions), + function2 = $a.reduce->cast(@FunctionDefinition)->transformLambda($extensions) + ) +} + function meta::protocols::pure::v1_33_0::transformation::fromPureGraph::valueSpecification::possiblyTransformNewFunction(any:Any[1], m:Multiplicity[1],fe:FunctionExpression[0..1], inScope:String[*], open:Map>[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::v1_33_0::metamodel::valueSpecification::ValueSpecification[1] { if($fe->isNotEmpty() && $fe.functionName== 'new', diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/models/metamodel.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/models/metamodel.pure index 224cb5cf160..64de44d1cf0 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/models/metamodel.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/models/metamodel.pure @@ -66,7 +66,8 @@ Enum meta::protocols::pure::vX_X_X::metamodel::PackageableElementType DIAGRAM, DATASTORESPEC, UNIT, - MEASURE + MEASURE, + FUNCTION } Class meta::protocols::pure::vX_X_X::metamodel::ExecutionContext @@ -560,6 +561,19 @@ Class meta::protocols::pure::vX_X_X::metamodel::objectReference::AlloyObjectRefe operationResolvedSetsId : String[*]; } +Class meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec +{ + name: String[1]; + type: String[0..1]; + function1: meta::protocols::pure::vX_X_X::metamodel::valueSpecification::raw::Lambda[0..1]; + function2: meta::protocols::pure::vX_X_X::metamodel::valueSpecification::raw::Lambda[0..1]; +} + +Class meta::protocols::pure::vX_X_X::metamodel::relation::ColSpecArray +{ + colSpecs: meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec[*]; +} + Class meta::protocols::pure::vX_X_X::metamodel::store::RelationStoreAccessor { path:String[*]; @@ -751,7 +765,7 @@ Class meta::protocols::pure::vX_X_X::metamodel::mapping::modelToModel::PureInsta filter : meta::protocols::pure::vX_X_X::metamodel::valueSpecification::raw::Lambda[0..1]; } -Class meta::protocols::pure::vX_X_X::metamodel::mapping::modelToModel::PurePropertyMapping extends meta::protocols::pure::vX_X_X::metamodel::mapping::PropertyMapping +Class meta::protocols::pure::vX_X_X::metamodel::mapping::modelToModel::PurePropertyMapping extends meta::protocols::pure::vX_X_X::metamodel::mapping::PropertyMapping { explodeProperty : Boolean[0..1]; enumMappingId : String[0..1]; @@ -768,11 +782,20 @@ Class meta::protocols::pure::vX_X_X::metamodel::mapping::xStore::XStorePropertyM crossExpression : meta::protocols::pure::vX_X_X::metamodel::valueSpecification::raw::Lambda[1]; } -Class meta::protocols::pure::vX_X_X::metamodel::mapping::modelToModel::AggregationAwarePropertyMapping extends meta::protocols::pure::vX_X_X::metamodel::mapping::PropertyMapping +Class meta::protocols::pure::vX_X_X::metamodel::mapping::modelToModel::AggregationAwarePropertyMapping extends meta::protocols::pure::vX_X_X::metamodel::mapping::PropertyMapping { } +Class meta::protocols::pure::vX_X_X::metamodel::mapping::relation::RelationFunctionClassMapping extends meta::protocols::pure::vX_X_X::metamodel::mapping::ClassMapping +{ + relationFunction: meta::protocols::pure::vX_X_X::metamodel::PackageableElementPointer[1]; + propertyMappings : meta::protocols::pure::vX_X_X::metamodel::mapping::PropertyMapping[*]; +} +Class meta::protocols::pure::vX_X_X::metamodel::mapping::relation::RelationFunctionPropertyMapping extends meta::protocols::pure::vX_X_X::metamodel::mapping::PropertyMapping +{ + column: String[1]; +} Class meta::protocols::pure::vX_X_X::metamodel::runtime::Connection { diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/scan/buildBasePureModel.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/scan/buildBasePureModel.pure index f984f460b0f..929acbd368f 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/scan/buildBasePureModel.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/scan/buildBasePureModel.pure @@ -12,12 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +import meta::pure::router::store::routing::*; +import meta::core::runtime::*; import meta::protocols::*; import meta::json::*; import meta::pure::store::*; import meta::external::store::model::*; import meta::pure::milestoning::*; import meta::pure::mapping::*; +import meta::pure::mapping::relation::*; import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::*; Class <> meta::protocols::pure::vX_X_X::transformation::fromPureGraph::AllTypes @@ -252,7 +255,8 @@ function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::extractSt { $si->match($extensions.serializerExtension('vX_X_X')->cast(@meta::protocols::pure::vX_X_X::extension::SerializerExtension_vX_X_X).scan_buildBasePureModel_extractStores->map(e|$e->eval($m, $extensions))->concatenate([ ins: OperationSetImplementation[1] | [], - p: PureInstanceSetImplementation[1] | [] + p: PureInstanceSetImplementation[1] | [], + r: RelationFunctionInstanceSetImplementation[1] | $si->routeRelationFunctionSetImplementation($m, $extensions)->meta::pure::router::routing::getStoreFromSet($m, $extensions) ])->toOneMany() ); } @@ -321,6 +325,10 @@ function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::buildBase ->concatenate($allMappings->map(m|$m->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::transformMapping($extensions))) ->concatenate($stores->map(store|$store->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::store::transformStore($extensions))) ->concatenate($extensions->map(e|$e.serializerExtension('vX_X_X')->cast(@meta::protocols::pure::vX_X_X::extension::SerializerExtension_vX_X_X).scan_buildBasePureModel_getExtraElementsFromStores->map(z|$z->eval($stores,$extensions)))) + ->concatenate($allMappings->map(m|$m.classMappings->map(cm|$cm->match([ + r:RelationFunctionInstanceSetImplementation[1]|$r.relationFunction->cast(@ConcreteFunctionDefinition)->transformFunction($extensions), + a:Any[1]|[] + ])))) ) ); } diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/mapping.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/mapping.pure index 404d99b3902..b8468f5f35c 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/mapping.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/mapping.pure @@ -17,10 +17,13 @@ import meta::relational::tests::csv::mapping::*; import meta::flatten::metamodel::*; import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::modelToModel::*; import meta::pure::mapping::xStore::*; +import meta::pure::mapping::relation::*; import meta::external::store::model::*; import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::*; +import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::domain::*; import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::csv::*; import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::*; +import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::relation::*; import meta::pure::mapping::*; function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::transformMapping(mapping:Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::vX_X_X::metamodel::mapping::Mapping[1] @@ -68,7 +71,8 @@ function <> meta::protocols::pure::vX_X_X::transformation::fromPu $si->match($extensions.serializerExtension('vX_X_X')->cast(@meta::protocols::pure::vX_X_X::extension::SerializerExtension_vX_X_X).transfers_mapping_transformSetImplementation->map(f|$f->eval($mapping))->concatenate( $extensions.serializerExtension('vX_X_X')->cast(@meta::protocols::pure::vX_X_X::extension::SerializerExtension_vX_X_X).transfers_mapping_transformSetImplementation2->map(f|$f->eval($mapping, $extensions)))->concatenate([ o:OperationSetImplementation[1]| $o->transformOperationSetImplementation($mapping,$extensions), - p:PureInstanceSetImplementation[1]| $p->transformPureInstanceSetImplementation($mapping, $extensions) + p:PureInstanceSetImplementation[1]| $p->transformPureInstanceSetImplementation($mapping, $extensions), + r:RelationFunctionInstanceSetImplementation[1]| $r->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::store::relational::transformRelationFunctionInstanceSetImplementation($mapping, $extensions) ])->toOneMany() ); } @@ -178,6 +182,21 @@ function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping:: ); } +function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::store::relational::transformRelationFunctionInstanceSetImplementation(r:RelationFunctionInstanceSetImplementation[1], mapping:Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::vX_X_X::metamodel::mapping::relation::RelationFunctionClassMapping[1] +{ + ^meta::protocols::pure::vX_X_X::metamodel::mapping::relation::RelationFunctionClassMapping + ( + id = $r.id, + _type = 'relation', + class = $r.class->elementToPath(), + root = $r.root, + extendsClassMappingId = $r.superSetImplementationId, + mappingClass = $r.mappingClass->map(mc|$mc->transformMappingClass($mapping, $extensions)), + propertyMappings = $r.propertyMappings->map(pm|$pm->transformRelationFunctionPropertyMapping($mapping, $extensions)), + relationFunction = ^meta::protocols::pure::vX_X_X::metamodel::PackageableElementPointer(type=meta::protocols::pure::vX_X_X::metamodel::PackageableElementType.FUNCTION, path=$r.relationFunction->elementToPath()) + ); +} + function <> meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::modelToModel::transformPropertyMapping(pm:PropertyMapping[1], mapping : Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::vX_X_X::metamodel::mapping::PropertyMapping[1] { $pm->match([p:PurePropertyMapping[1]| ^meta::protocols::pure::vX_X_X::metamodel::mapping::modelToModel::PurePropertyMapping @@ -235,3 +254,27 @@ function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping:: ) ]); } + +function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::mapping::relation::transformRelationFunctionPropertyMapping(pm:PropertyMapping[1], mapping : Mapping[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::vX_X_X::metamodel::mapping::relation::RelationFunctionPropertyMapping[1] +{ + $pm->match([r:meta::pure::mapping::relation::RelationFunctionPropertyMapping[1]| + ^meta::protocols::pure::vX_X_X::metamodel::mapping::relation::RelationFunctionPropertyMapping + ( + _type = 'relationFunctionPropertyMapping', + property = ^meta::protocols::pure::vX_X_X::metamodel::domain::PropertyPtr(class=$pm.property->genericType().typeArguments->at(0).rawType->toOne()->elementToPath(), property=$pm.property.name->toOne()), + column = $r.column.name->toOne(), + source = $r.sourceSetImplementationId, + target = $r.targetSetImplementationId, + localMappingProperty = if ($r.localMappingProperty->isNotEmpty() && $r.localMappingProperty->toOne(), + | ^meta::protocols::pure::vX_X_X::metamodel::mapping::LocalMappingPropertyInfo + ( + type = $r.localMappingPropertyType->toOne()->elementToPath(), + multiplicity = $r.localMappingPropertyMultiplicity->toOne()->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::domain::transformMultiplicity() + ), + | [] + ) + ) + ]); +} + + diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/valueSpecification.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/valueSpecification.pure index d62b5a166fd..c9ca5faa129 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/valueSpecification.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/protocol/vX_X_X/transfers/valueSpecification.pure @@ -13,6 +13,10 @@ // limitations under the License. ###Pure +import meta::pure::extension::*; +import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::*; +import meta::protocols::pure::vX_X_X::transformation::fromPureGraph::relation::*; +import meta::pure::metamodel::relation::*; import meta::pure::milestoning::*; import meta::pure::graphFetch::execution::*; import meta::json::*; @@ -83,7 +87,18 @@ function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpec agg_FunctionDefinition_1__FunctionDefinition_1__AggregateValue_1_, agg_String_1__FunctionDefinition_1__FunctionDefinition_1__AggregateValue_1_, func_String_1__FunctionDefinition_1__TdsOlapAggregation_1_, - func_FunctionDefinition_1__TdsOlapRank_1_ + func_FunctionDefinition_1__TdsOlapRank_1_, + + meta::pure::functions::relation::colSpecArray_String_MANY__T_1__ColSpecArray_1_, + meta::pure::functions::relation::colSpec_String_1__T_1__ColSpec_1_, + meta::pure::functions::relation::funcColSpecArray_FuncColSpec_MANY__P_1__FuncColSpecArray_1_, + meta::pure::functions::relation::funcColSpecArray2_FuncColSpec_MANY__P_1__FuncColSpecArray_1_, + meta::pure::functions::relation::funcColSpec_Function_1__String_1__T_1__FuncColSpec_1_, + meta::pure::functions::relation::funcColSpec2_Function_1__String_1__T_1__FuncColSpec_1_, + meta::pure::functions::relation::aggColSpecArray_AggColSpec_MANY__P_1__AggColSpecArray_1_, + meta::pure::functions::relation::aggColSpecArray2_AggColSpec_MANY__P_1__AggColSpecArray_1_, + meta::pure::functions::relation::aggColSpec_Function_1__Function_1__String_1__T_1__AggColSpec_1_, + meta::pure::functions::relation::aggColSpec2_Function_1__Function_1__String_1__T_1__AggColSpec_1_ ]; let isExtractEnum = $fe->meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::hasExtractEnumValue(); @@ -314,11 +329,64 @@ function <> meta::protocols::pure::vX_X_X::transformation::fromP ( _type = 'latestDate' ), + c:meta::pure::metamodel::relation::ColSpec[1]| + ^meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpec', + value = $c.name->transformColSpec() + );, + c:meta::pure::metamodel::relation::ColSpecArray[1]| + + ^meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpecArray', + value = ^meta::protocols::pure::vX_X_X::metamodel::relation::ColSpecArray + ( + colSpecs = $c.names->map(n|$n->transformColSpec()) + ) + );, + f:meta::pure::metamodel::relation::FuncColSpec[1]| + ^meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpec', + value = $f->transformFuncColSpec($extensions) + );, + f:meta::pure::metamodel::relation::FuncColSpecArray[1]| + ^meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpecArray', + value = ^meta::protocols::pure::vX_X_X::metamodel::relation::ColSpecArray + ( + colSpecs = $f.funcSpecs->map(c|$c->transformFuncColSpec($extensions)) + ) + );, + a:meta::pure::metamodel::relation::AggColSpec[1]| + ^meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpec', + value = $a->transformAggColSpec($extensions) + );, + a:meta::pure::metamodel::relation::AggColSpecArray[1]| + ^meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ClassInstance + ( + _type = 'classInstance', + type = 'colSpecArray', + value = ^meta::protocols::pure::vX_X_X::metamodel::relation::ColSpecArray + ( + colSpecs = $a.aggSpecs->map(c|$c->transformAggColSpec($extensions)) + ) + );, + r:meta::pure::store::RelationStoreAccessor[1]| ^meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ClassInstance ( _type = 'classInstance', - type = 'relationStoreAccessor', + type = '>', value = ^meta::protocols::pure::vX_X_X::metamodel::store::RelationStoreAccessor ( path = $r.path @@ -535,6 +603,33 @@ function <> meta::protocols::pure::vX_X_X::transformation::fromP ); } +function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::relation::transformColSpec(c:String[1]):meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec[1] +{ + ^meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec + ( + name = $c + ) +} + +function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::relation::transformFuncColSpec(f:FuncColSpec[1], extensions:Extension[*]):meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec[1] +{ + ^meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec + ( + name = $f.name, + function1 = $f.function->cast(@FunctionDefinition)->transformLambda($extensions) + ) +} + +function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::relation::transformAggColSpec(a:AggColSpec[1], extensions:Extension[*]):meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec[1] +{ + ^meta::protocols::pure::vX_X_X::metamodel::relation::ColSpec + ( + name = $a.name, + function1 = $a.map->cast(@FunctionDefinition)->transformLambda($extensions), + function2 = $a.reduce->cast(@FunctionDefinition)->transformLambda($extensions) + ) +} + function meta::protocols::pure::vX_X_X::transformation::fromPureGraph::valueSpecification::possiblyTransformNewFunction(any:Any[1], m:Multiplicity[1],fe:FunctionExpression[0..1], inScope:String[*], open:Map>[1], extensions:meta::pure::extension::Extension[*]):meta::protocols::pure::vX_X_X::metamodel::valueSpecification::ValueSpecification[1] { if($fe->isNotEmpty() && $fe.functionName== 'new', diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/builder.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/builder.pure index db05edb3d48..7c9be2d7e89 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/builder.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/builder.pure @@ -36,9 +36,11 @@ Class meta::pure::router::store::routing::Void extends ValueSpecification function meta::pure::router::store::routing::enrichStoreMappingRoutedValueSpecification(routedVs: StoreMappingRoutedValueSpecification[1], exeCtx: meta::pure::runtime::ExecutionContext[1], extensions:meta::pure::extension::Extension[*], debug:DebugContext[1]):StoreMappingRoutedValueSpecification[1] { - let setsInScope = $routedVs.routingStrategy->cast(@StoreMappingRoutingStrategy).sets; - let mapping = $routedVs.mapping; - let runtime = $routedVs.runtime; + let routingStrategy = $routedVs.routingStrategy->cast(@StoreMappingRoutingStrategy); + let setsInScope = $routingStrategy.sets->map(s|^$s(sets = $s.sets->routeRelationFunctionSetImplementations($routedVs.mapping, $routedVs.runtime, $extensions))); + let newRoutedVs = ^$routedVs(routingStrategy = ^$routingStrategy(sets = $setsInScope)); + let mapping = $newRoutedVs.mapping; + let runtime = $newRoutedVs.runtime; if(!$setsInScope->isEmpty(), | // Sets @@ -53,7 +55,7 @@ function meta::pure::router::store::routing::enrichStoreMappingRoutedValueSpecif // Build print(if($debug.debug,|'\n'+$debug.space+'Building new queries\n', |'')); let built = $permutations->map(p|print(if($debug.debug,|' Query '+$permutations->indexOf($p)->toString()+'\n', |'')); - $routedVs->evaluateAndDeactivate()->build($p, [], $mapping, $idToClassMapping, $exeCtx, $extensions, $debug);); + $newRoutedVs->evaluateAndDeactivate()->build($p, [], $mapping, $idToClassMapping, $exeCtx, $extensions, $debug);); print(if($debug.debug,|'\n'+$debug.space+'Built queries:'+$built->size()->toString()+'\n', |'')); @@ -70,11 +72,11 @@ function meta::pure::router::store::routing::enrichStoreMappingRoutedValueSpecif print(if($debug.debug,|$debug.space+'Result: '+$concat->asString()+'\n',|'')); $concat;, | if($filteredBuilt->size()!=1, - |$routedVs, + |$newRoutedVs, |$filteredBuilt->toOne()); ); $nbuilt->cast(@StoreMappingRoutedValueSpecification);, - |$routedVs; // This is needed for expressions (like let x = 1->from($mapping, $runtime)) where there are no sets + |$newRoutedVs; // This is needed for expressions (like let x = 1->from($mapping, $runtime)) where there are no sets ); } diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/cluster.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/cluster.pure index c5fa3ba8d19..4e6174c4110 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/cluster.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/cluster.pure @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +import meta::pure::router::routing::*; import meta::pure::mutation::*; import meta::pure::router::metamodel::*; import meta::pure::mapping::*; import meta::pure::mapping::xStore::*; +import meta::pure::mapping::relation::*; import meta::pure::metamodel::path::*; import meta::pure::metamodel::serialization::grammar::*; import meta::pure::router::analytics::*; @@ -501,6 +503,10 @@ function meta::pure::router::clustering::getClusterVSFromSets(r:StoreMappingRout function meta::pure::router::clustering::storeContractForSetImplementation(setImpl:SetImplementation[1], mapping:Mapping[1], extensions:meta::pure::extension::Extension[*]):Pair[1] { $setImpl->match([ + t:RelationFunctionInstanceSetImplementation[1]| let vs = $t.relationFunction.expressionSequence->evaluateAndDeactivate()->at(0); + assert($vs->instanceOf(StoreClusteredValueSpecification), 'Relation mapping does not support cross store queries yet.'); + let store = $vs->cast(@StoreClusteredValueSpecification).store; + pair(meta::pure::extension::storeContractFromStore($extensions, $store), $store);, i: InstanceSetImplementation[1]| let storeContract = $extensions->meta::pure::extension::storeContractForSetImplementation($i->toOne()); let store = $storeContract.resolveStoreFromSetImplementation->toOne()->eval($i); diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/routing.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/routing.pure index f8c0c44aafc..fe558cea125 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/routing.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-compiled-core/src/main/resources/core/pure/router/store/routing.pure @@ -12,12 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +import meta::pure::router::metamodel::clustering::*; +import meta::pure::runtime::*; import meta::pure::store::*; import meta::pure::metamodel::relation::*; import meta::relational::metamodel::*; import meta::pure::extension::*; import meta::pure::graphFetch::*; import meta::pure::mapping::*; +import meta::pure::mapping::relation::*; import meta::pure::router::builder::*; import meta::pure::router::metamodel::*; import meta::pure::router::store::metamodel::*; @@ -48,18 +51,9 @@ function meta::pure::router::store::routing::getRoutingStrategyFromMappingAndRun let modifiedMapping = modifyMappingBasedOnRuntimeLocality($mapping, $modifiedRuntime, $extensions); let currentStoreMappingRoutingStrategy = $routingStrategy->filter(x | $x->instanceOf(StoreMappingRoutingStrategy))->cast(@StoreMappingRoutingStrategy); if($currentStoreMappingRoutingStrategy->isEmpty(), - | ^StoreMappingRoutingStrategy(mapping = $modifiedMapping, - runtime = $modifiedRuntime, - setsByDepth = ^Map(), - classMappingsByClass = $modifiedMapping->buildClassMappingsByClassMap(), - processClass = processClass_Class_1__InstanceValue_1__RoutingState_1__ExecutionContext_1__DebugContext_1__RoutingState_1_, - processRelationStoreAccessor = processRelationStoreAccessor_RelationStoreAccessor_1__InstanceValue_1__RoutingState_1__ExecutionContext_1__DebugContext_1__RoutingState_1_, - processProperty = routeFunctionExpressionProperty_Property_1__FunctionExpression_1__RoutingState_1__ExecutionContext_1__Map_1__Map_1__Extension_MANY__DebugContext_1__RoutingState_1_, - wrapValueSpec = wrapValueSpecificationStoreMapping_ValueSpecification_1__RoutingStrategy_1__String_1__ExecutionContext_1__Extension_MANY__DebugContext_1__ExtendedRoutedValueSpecification_1_, - toString = {strategy:RoutingStrategy[1] | $strategy->cast(@StoreMappingRoutingStrategy).sets->size()->toString()} - );, - | let newStoreMappingRoutingStrategy = $currentStoreMappingRoutingStrategy->toOne(); - ^$newStoreMappingRoutingStrategy(mapping = $modifiedMapping, runtime = $modifiedRuntime, classMappingsByClass = $modifiedMapping->buildClassMappingsByClassMap(), setsByDepth = ^Map()); + | getRoutingStrategy($modifiedMapping, $modifiedRuntime), + | let newStoreMappingRoutingStrategy = $currentStoreMappingRoutingStrategy->toOne(); + ^$newStoreMappingRoutingStrategy(mapping = $modifiedMapping, runtime = $modifiedRuntime, classMappingsByClass = $modifiedMapping->buildClassMappingsByClassMap(), setsByDepth = ^Map()); ); } @@ -72,7 +66,10 @@ function <> meta::pure::router::store::routing::modifyMappingBas x:meta::pure::mapping::xStore::XStoreAssociationImplementation[1]| let mappingIds = $x.propertyMappings.sourceSetImplementationId->concatenate($x.propertyMappings.targetSetImplementationId); let inScopeClassMappings = $classMappings->filter(x|$x.id->in($mappingIds))->distinct(); - let inScopeStores = $inScopeClassMappings->map(setImpl | meta::pure::router::clustering::storeContractForSetImplementation($setImpl, $mapping, $extensions).second); + let inScopeStores = $inScopeClassMappings->map(setImpl | $setImpl->match([ + r:RelationFunctionInstanceSetImplementation[1]|$runtime.connectionStores.element->cast(@meta::pure::store::Store)->toOne('Relation mapping only supported for queries having a single store definitions!'), + a:SetImplementation[1]|meta::pure::router::clustering::storeContractForSetImplementation($setImpl, $mapping, $extensions).second; + ])); if( $runtime.connectionStores->size() >= 1, | let connection = $inScopeStores->map(s|$runtime->connectionByElementFailSafe($s)); @@ -105,6 +102,48 @@ function <> meta::pure::router::store::routing::getIncludedMappi $m->concatenate(if($m.includes->isEmpty(), |[], |$m.includes.included->map(i|$i->getIncludedMappingsRecursively()))); } +function meta::pure::router::store::routing::routeRelationFunctionSetImplementations(sets:SetImplementation[*], mapping:Mapping[1], runtime:Runtime[1], extensions: Extension[*]):SetImplementation[*] +{ + $sets->map(s|$s->routeRelationFunctionSetImplementation($mapping, $runtime, $extensions)); +} + +function meta::pure::router::store::routing::routeRelationFunctionSetImplementation(s:SetImplementation[1], mapping:Mapping[1], extensions: Extension[*]):SetImplementation[1] +{ + routeRelationFunctionSetImplementation($s, $mapping, ^Runtime(connectionStores=^ConnectionStore(connection=^Connection(),element='Mock')), $extensions); +} + +function meta::pure::router::store::routing::routeRelationFunctionSetImplementation(s:SetImplementation[1], mapping:Mapping[1], runtime:Runtime[1], extensions: Extension[*]):SetImplementation[1] +{ + $s->match([ + t:RelationFunctionInstanceSetImplementation[1]|^$t(relationFunction=$t->routeRelationFunction($mapping, $runtime, $extensions)), + s:InstanceSetImplementation[1]|$s, + o:OperationSetImplementation[1]|^$o(parameters = $o.parameters->map(i| ^$i(setImplementation=$mapping->classMappingById($i.id)->toOne()->routeRelationFunctionSetImplementation($mapping, $runtime, $extensions)))) + ]); +} + +function meta::pure::router::store::routing::routeRelationFunction(r:RelationFunctionInstanceSetImplementation[1], mapping:Mapping[1], runtime:Runtime[1], extensions: Extension[*]):FunctionDefinition<{->Relation[1]}>[1] +{ + if($r.relationFunction->evaluateAndDeactivate().expressionSequence->at(0)->instanceOf(ClusteredValueSpecification), + | $r.relationFunction, + | $r.relationFunction->routeFunction(getRoutingStrategy($mapping, $runtime), ^ExecutionContext(), [], $extensions, noDebug())->cast(@FunctionDefinition<{->Relation[1]}>) + ); +} + +function <> meta::pure::router::store::routing::getRoutingStrategy(mapping:Mapping[1], runtime:Runtime[1]):StoreMappingRoutingStrategy[1] +{ + ^StoreMappingRoutingStrategy( + mapping = $mapping, + runtime = $runtime, + setsByDepth = ^Map(), + classMappingsByClass = $mapping->buildClassMappingsByClassMap(), + processClass = processClass_Class_1__InstanceValue_1__RoutingState_1__ExecutionContext_1__DebugContext_1__RoutingState_1_, + processRelationStoreAccessor = processRelationStoreAccessor_RelationStoreAccessor_1__InstanceValue_1__RoutingState_1__ExecutionContext_1__DebugContext_1__RoutingState_1_, + processProperty = routeFunctionExpressionProperty_Property_1__FunctionExpression_1__RoutingState_1__ExecutionContext_1__Map_1__Map_1__Extension_MANY__DebugContext_1__RoutingState_1_, + wrapValueSpec = wrapValueSpecificationStoreMapping_ValueSpecification_1__RoutingStrategy_1__String_1__ExecutionContext_1__Extension_MANY__DebugContext_1__ExtendedRoutedValueSpecification_1_, + toString = {strategy:RoutingStrategy[1] | $strategy->cast(@StoreMappingRoutingStrategy).sets->size()->toString()} + ) +} + function meta::pure::router::store::routing::getRoutingStrategyFromRuntime(runtime:Runtime[1]):StoreRoutingStrategy[1] { ^StoreRoutingStrategy(runtime = $runtime, @@ -504,6 +543,7 @@ function meta::pure::router::store::routing::processProperty(p:AbstractProperty< function meta::pure::router::store::routing::isPropertyAutoMapped(srcSets:SetImplementation[*], p:AbstractProperty[1], extensions:meta::pure::extension::Extension[*]): Boolean[1] { $srcSets->size() == 1 && $srcSets->toOne()->instanceOf(InstanceSetImplementation) // We only support pass through mapping when source set is InstanceSetImpls and not OperationSetImpls + && !$srcSets->toOne()->instanceOf(RelationFunctionInstanceSetImplementation) && $extensions->meta::pure::extension::_storeContractForSetImplementation($srcSets->toOne()).isPropertyAutoMapped->isNotEmpty() && $extensions->meta::pure::extension::_storeContractForSetImplementation($srcSets->toOne()).isPropertyAutoMapped->toOne()->eval($p, $srcSets->toOne()->cast(@InstanceSetImplementation)); } diff --git a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-functions-relation/legend-engine-pure-functions-relation-pure/src/main/resources/core_functions_relation/relation/tests/composition.pure b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-functions-relation/legend-engine-pure-functions-relation-pure/src/main/resources/core_functions_relation/relation/tests/composition.pure index 5a480a4554e..ecc1a9c6fdc 100644 --- a/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-functions-relation/legend-engine-pure-functions-relation-pure/src/main/resources/core_functions_relation/relation/tests/composition.pure +++ b/legend-engine-core/legend-engine-core-pure/legend-engine-pure-code-functions-relation/legend-engine-pure-functions-relation-pure/src/main/resources/core_functions_relation/relation/tests/composition.pure @@ -353,7 +353,7 @@ function <> meta::pure::functions::relation::tests::composition::test_ ' a,4\n'+ ' qw,4\n'+ ' qw,5\n'+ - '#', $res->sort(~str->ascending())->toString()); + '#', $res->sort([~str->ascending(), ~newCol->ascending()])->toString()); } function <> meta::pure::functions::relation::tests::composition::test_Distinct_GroupBy_Filter(f:Function<{Function<{->T[m]}>[1]->T[m]}>[1]):Boolean[1] diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java index f69194f95d7..93cc602cc53 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java @@ -38,7 +38,6 @@ public class Test_Relational_DuckDB_RelationFunctions_PCT extends PCTReportConfi private static final MutableList expectedFailures = Lists.mutable.with( // BUG: unsupported compositions one("meta::pure::functions::relation::tests::composition::test_Distinct_GroupBy_Filter_Function_1__Boolean_1_", "java.sql.SQLException: java.sql.SQLException: Binder Error: column newCol must appear in the GROUP BY clause or be used in an aggregate function"), - one("meta::pure::functions::relation::tests::composition::test_GroupBy_Distinct_Filter_Function_1__Boolean_1_", "java.sql.SQLException: java.sql.SQLException: Binder Error: Referenced table \"restrict__d#2\" not found!\nCandidate tables: \"tb"), one("meta::pure::functions::relation::tests::composition::test_GroupBy_Filter_Function_1__Boolean_1_", "java.sql.SQLException: java.sql.SQLException: Binder Error: column \"newCol\" must appear in the GROUP BY clause or must be part of an aggregate function.\nEither add it to the GROUP BY list, or use \"ANY_VALUE(newCol)\" if the exact value of \"newCol\" is not important.\nLINE 2: select \"str\" as \"str\", \"newCol\" as \"newCol\" from (select \"tb") ); diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-h2/legend-engine-xt-relationalStore-h2-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/h2/pct/Test_Relational_H2_RelationFunctions_PCT.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-h2/legend-engine-xt-relationalStore-h2-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/h2/pct/Test_Relational_H2_RelationFunctions_PCT.java index 3ae29a21a34..e6bb78f9604 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-h2/legend-engine-xt-relationalStore-h2-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/h2/pct/Test_Relational_H2_RelationFunctions_PCT.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-h2/legend-engine-xt-relationalStore-h2-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/h2/pct/Test_Relational_H2_RelationFunctions_PCT.java @@ -43,9 +43,6 @@ public class Test_Relational_H2_RelationFunctions_PCT extends PCTReportConfigura one("meta::pure::functions::relation::tests::composition::test_Extend_Filter_Select_Pivot_GroupBy_Extend_Sort_Function_1__Boolean_1_", "\"pivot is not supported\""), one("meta::pure::functions::relation::tests::composition::test_Extend_Filter_Select_GroupBy_Pivot_Extend_Sort_Limit_Function_1__Boolean_1_", "\"pivot is not supported\""), - // BUG: unsupported compositions - one("meta::pure::functions::relation::tests::composition::test_GroupBy_Distinct_Filter_Function_1__Boolean_1_", "org.h2.jdbc.JdbcSQLSyntaxErrorException: Column \"restrict__d#2.newCol\" not found; SQL statement"), - // BUG: Column name with special characters is not properly escaped one("meta::pure::functions::relation::tests::select::testSingleSelectWithQuotedColumn_Function_1__Boolean_1_", "Error while executing: Create Table leSchema.tb"), diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-postgres/legend-engine-xt-relationalStore-postgres-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/postgres/pct/Test_Relational_Postgres_RelationFunctions_PCT.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-postgres/legend-engine-xt-relationalStore-postgres-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/postgres/pct/Test_Relational_Postgres_RelationFunctions_PCT.java index 5f2e8211a22..f51f6769962 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-postgres/legend-engine-xt-relationalStore-postgres-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/postgres/pct/Test_Relational_Postgres_RelationFunctions_PCT.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-postgres/legend-engine-xt-relationalStore-postgres-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/postgres/pct/Test_Relational_Postgres_RelationFunctions_PCT.java @@ -46,7 +46,6 @@ public class Test_Relational_Postgres_RelationFunctions_PCT extends PCTReportCon // BUG: unsupported compositions one("meta::pure::functions::relation::tests::composition::test_Distinct_GroupBy_Filter_Function_1__Boolean_1_", "org.postgresql.util.PSQLException: ERROR: column \"subselect.newCol\" must appear in the GROUP BY clause or be used in an aggregate function"), - one("meta::pure::functions::relation::tests::composition::test_GroupBy_Distinct_Filter_Function_1__Boolean_1_", "org.postgresql.util.PSQLException: ERROR: missing FROM-clause entry for table \"restrict__d#2\""), one("meta::pure::functions::relation::tests::composition::test_GroupBy_Filter_Function_1__Boolean_1_", "org.postgresql.util.PSQLException: ERROR: column \"subselect.newCol\" must appear in the GROUP BY clause or be used in an aggregate function"), // BUG: Column name with special characters is not properly escaped diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/RelationalParseTreeWalker.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/RelationalParseTreeWalker.java index b1c442e3103..6b3c9d78553 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/RelationalParseTreeWalker.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/grammar/from/RelationalParseTreeWalker.java @@ -1163,14 +1163,7 @@ private PropertyMapping visitPropertyMappingWithLocalMappingProperty(RelationalP LocalMappingPropertyInfo localMappingPropertyInfo = new LocalMappingPropertyInfo(); localMappingPropertyInfo.sourceInformation = this.walkerSourceInformation.getSourceInformation(localMappingPropertyContext); localMappingPropertyInfo.type = PureGrammarParserUtility.fromQualifiedName(localMappingPropertyContext.qualifiedName().packagePath() == null ? Collections.emptyList() : localMappingPropertyContext.qualifiedName().packagePath().identifier(), localMappingPropertyContext.qualifiedName().identifier()); - Multiplicity multiplicity = new Multiplicity(); - localMappingPropertyInfo.multiplicity = multiplicity; - RelationalParserGrammar.LocalMappingPropertyFromMultiplicityContext fromMultiplicityContext = localMappingPropertyContext.localMappingPropertyFromMultiplicity(); - RelationalParserGrammar.LocalMappingPropertyToMultiplicityContext toMultiplicityContext = localMappingPropertyContext.localMappingPropertyToMultiplicity(); - multiplicity.lowerBound = fromMultiplicityContext == null - ? Integer.parseInt("*".equals(toMultiplicityContext.getText()) ? "0" : toMultiplicityContext.getText()) - : Integer.parseInt(fromMultiplicityContext.getText()); - multiplicity.setUpperBound("*".equals(toMultiplicityContext.getText()) ? null : Integer.parseInt(toMultiplicityContext.getText())); + localMappingPropertyInfo.multiplicity = PureGrammarParserUtility.buildMultiplicity(localMappingPropertyContext.localMappingPropertyFromMultiplicity(), localMappingPropertyContext.localMappingPropertyToMultiplicity()); PropertyPointer propertyPointer = new PropertyPointer(); propertyPointer.property = PureGrammarParserUtility.fromIdentifier(ctx.identifier()); propertyPointer._class = null; diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestRelationalCompilationFromGrammar.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestRelationalCompilationFromGrammar.java index 288d5abf457..43e00fc1434 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestRelationalCompilationFromGrammar.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/compiler/test/TestRelationalCompilationFromGrammar.java @@ -2943,4 +2943,5 @@ public void testDuplicateNameForFilters() " Filter FirmFilter(firmTable.ADDRESSID = 1)\n" + ")", null, Arrays.asList("COMPILATION error at [21:3-52]: Found filters with duplicate names: FirmFilter", "COMPILATION error at [22:3-44]: Found filters with duplicate names: FirmFilter")); } + } diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/pom.xml b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/pom.xml index 1fbd9fdaedc..a55e249927a 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/pom.xml +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/pom.xml @@ -339,10 +339,10 @@ org.finos.legend.engine legend-engine-pure-runtime-java-extension-shared-functions-json - - - - + + org.finos.legend.engine + legend-engine-pure-runtime-java-extension-shared-functions-relation + org.finos.legend.engine legend-engine-pure-platform-dsl-store-java diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/graphFetch/relationalGraphFetch.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/graphFetch/relationalGraphFetch.pure index 77d7ef37427..371a9fd6e5a 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/graphFetch/relationalGraphFetch.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/graphFetch/relationalGraphFetch.pure @@ -744,7 +744,7 @@ function <> meta::relational::graphFetch::executionPlan::buildBa s:EmbeddedRelationalInstanceSetImplementation[1] | $s.setMappingOwner; ])->cast(@RootRelationalInstanceSetImplementation); - let mainTable = $srcSetImpl->mainRelation()->processRelation([], '', false, 0, false, [], $pureToSqlState, $debug, $extensions); + let mainTable = $srcSetImpl->mainRelation()->processRelation([], JoinType.LEFT_OUTER, '', false, 0, false, [], $pureToSqlState, $debug, $extensions); let innerFilterExists = $srcSetImpl->getFilter().joinTreeNode.joinType == JoinType.INNER; let rootRelElement = if ($innerFilterExists, | // Create subquery with inner join filter @@ -852,7 +852,7 @@ function <> meta::relational::graphFetch::executionPlan::generat { let setImpl = $propTree.sets->toOne()->cast(@RootRelationalInstanceSetImplementation); let pureToSqlState = ^State(inScopeVars = $inScopeVars, mapping=$mapping, supportedFunctions=getSupportedFunctions(), inProject=false, inFilter=false, filterChainDepth=0, inProjectFunctions=false, processingProjectionThread=false, shouldIsolate=false, contextBasedSupportedFunctions=getContextBasedSupportedFunctions(), idToClassMapping=buildClassMappingsById($mapping)); - let currentNode = ^RootJoinTreeNode(alias=^TableAlias(name = 'root', relationalElement = $setImpl->mainRelation()->processRelation([], '', false, 0, false, [], $pureToSqlState, $debug, $extensions))); + let currentNode = ^RootJoinTreeNode(alias=^TableAlias(name = 'root', relationalElement = $setImpl->mainRelation()->processRelation([], JoinType.LEFT_OUTER, '', false, 0, false, [], $pureToSqlState, $debug, $extensions))); let basePreFilter = ^SelectWithCursor(select = ^SelectSQLQuery(data = $currentNode), currentTreeNode = $currentNode)->applyTypeFilter($setImpl, '_gftcm', $pureToSqlState, $debug, $extensions); let base = ^$basePreFilter(select = $basePreFilter.select->pushFilters($extensions)); diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/helperFunctions/helperFunctions.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/helperFunctions/helperFunctions.pure index 2e0e4fed023..5f0afd4175d 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/helperFunctions/helperFunctions.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/helperFunctions/helperFunctions.pure @@ -12,13 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +import meta::pure::router::metamodel::clustering::*; +import meta::pure::runtime::*; +import meta::pure::router::store::routing::*; import meta::pure::mapping::*; +import meta::pure::mapping::relation::*; import meta::relational::tests::csv::*; import meta::relational::functions::database::*; import meta::external::store::relational::runtime::*; import meta::relational::runtime::*; import meta::core::runtime::*; import meta::relational::functions::pureToSqlQuery::*; +import meta::relational::functions::pureToSqlQuery::metamodel::*; import meta::pure::router::routing::*; import meta::pure::extension::*; import meta::relational::extension::*; @@ -412,3 +417,29 @@ function meta::relational::mapping::findMainClassInGetAllExpression(vs:ValueSpec let getAllFe = $vs->findExpressionsForFunctionInValueSpecification([getAll_Class_1__T_MANY_, getAll_Class_1__Date_1__T_MANY_, getAll_Class_1__Date_1__Date_1__T_MANY_]); if($getAllFe->isEmpty(), | Any, | $getAllFe.parametersValues->at(0)->cast(@ExtendedRoutedValueSpecification)->byPassRouterInfo()->cast(@InstanceValue).values->toOne()->cast(@Class)); } + +function meta::relational::mapping::transformRelationFunctionClassMapping(classMapping:RelationFunctionInstanceSetImplementation[1]):RelationFunctionInstanceSetImplementation[1] +{ + ^$classMapping(propertyMappings=$classMapping.propertyMappings->cast(@RelationFunctionPropertyMapping)->transformRelationPropertyMappingsToRelational($classMapping)); +} + +function meta::relational::mapping::transformRelationPropertyMappingsToRelational(propertyMappings:RelationFunctionPropertyMapping[*], classMapping:RelationFunctionInstanceSetImplementation[1]):RelationalPropertyMapping[*] +{ + $propertyMappings->map(r| let relationColumnType = $r.column.classifierGenericType.typeArguments->at(1).rawType->toOne(); + let relOpElement = ^TableAliasColumn(alias=^TableAlias(name=$classMapping.id, relationalElement=^RelationFunction(owner=$classMapping)), + column=^RelationFunctionColumn(column=$r.column, name=$r.column.name->toOne(), type=pureTypeToRelationalTypeMap()->get($relationColumnType)->toOne()) + ); + ^RelationalPropertyMapping( + owner = $r.owner, + targetSetImplementationId = $r.targetSetImplementationId, + sourceSetImplementationId = $r.sourceSetImplementationId, + property = $r.property, + localMappingProperty = $r.localMappingProperty, + localMappingPropertyType = $r.localMappingPropertyType, + localMappingPropertyMultiplicity = $r.localMappingPropertyMultiplicity, + store = $r.store, + relationalOperationElement = $relOpElement + ); + ); +} + diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/modelJoins/modelJoins.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/modelJoins/modelJoins.pure index 863c12b2f7a..ee0a1d86337 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/modelJoins/modelJoins.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/modelJoins/modelJoins.pure @@ -1,128 +1,143 @@ -import meta::pure::store::*; -import meta::pure::executionPlan::*; -import meta::pure::extension::*; -import meta::relational::extension::*; -import meta::alloy::objectReference::*; -import meta::external::store::relational::runtime::*; -import meta::relational::runtime::*; -import meta::relational::functions::pureToSqlQuery::union::*; -import meta::relational::functions::pureToSqlQuery::metamodel::*; -import meta::relational::functions::pureToSqlQuery::*; -import meta::pure::metamodel::valuespecification::*; -import meta::relational::mapping::*; -import meta::relational::metamodel::*; -import meta::relational::metamodel::operation::*; -import meta::relational::metamodel::relation::*; -import meta::relational::metamodel::join::*; -import meta::relational::functions::pureToSqlQuery::*; -import meta::relational::functions::pureToSqlQuery::relationalmappingspecification::*; -import meta::pure::mapping::*; -import meta::pure::metamodel::path::*; -import meta::pure::metamodel::serialization::grammar::*; -import meta::pure::milestoning::*; -import meta::pure::tds::*; -import meta::relational::functions::*; -import meta::relational::milestoning::*; -import meta::pure::router::clustering::*; -import meta::pure::router::printer::*; -import meta::pure::router::routing::*; -import meta::pure::router::store::embedded::*; -import meta::pure::router::store::metamodel::*; -import meta::pure::router::store::metamodel::clustering::*; -import meta::pure::router::utils::*; -import meta::relational::extension::*; -import meta::pure::mapping::xStore::*; -import meta::external::store::relational::modelJoins::*; - -function meta::external::store::relational::modelJoins::localizeXStoreAssociation(xstoreAssociation: XStoreAssociationImplementation[1], setImpl:SetImplementation[*], stores:Store[*]):AssociationImplementation[1] -{ - let database = ^Database(includes=$stores->cast(@Database)); - let relationalPropertyMappings = $xstoreAssociation.propertyMappings->cast(@XStorePropertyMapping)->map(xstorePropertyMapping | transformXStorePropertyIntoRelationalProperty($xstorePropertyMapping, $setImpl, $database)); - ^RelationalAssociationImplementation(association=$xstoreAssociation.association,parent=$xstoreAssociation.parent,id=$xstoreAssociation.id, stores=$database, propertyMappings=$relationalPropertyMappings); -} - -function <> meta::external::store::relational::modelJoins::transformXStorePropertyIntoRelationalProperty(xstoreProperty: meta::pure::mapping::xStore::XStorePropertyMapping[1], setImpl:SetImplementation[*], database:Database[1]): RelationalPropertyMapping[1] -{ - let expressionSequence = $xstoreProperty.crossExpression.expressionSequence->cast(@SimpleFunctionExpression)->toOne()->evaluateAndDeactivate(); - let sourceId = $xstoreProperty.sourceSetImplementationId->toOne(); - let targetId = $xstoreProperty.targetSetImplementationId->toOne(); - let classMappings = $setImpl->cast(@RootRelationalInstanceSetImplementation); - let sourceMainTableAlias = $classMappings->filter(c| $c.id == $sourceId)->toOne().mainTableAlias; - - let join = transformExpressionSequenceIntoJoin($expressionSequence, $sourceId, $targetId, $classMappings); - - let joinTreeNode = ^JoinTreeNode( - joinName=$join.name, - database=$join.database->toOne(), - alias=$join.target->toOne(), - join=$join - ); - - let ro = ^RelationalOperationElementWithJoin(joinTreeNode=$joinTreeNode); - let r = ^RelationalPropertyMapping(sourceSetImplementationId=$sourceId,property=$xstoreProperty.property->toOne(),relationalOperationElement=$ro,targetSetImplementationId=$targetId); -} - -function <> meta::external::store::relational::modelJoins::getClassMappingAtId(classMappings:RootRelationalInstanceSetImplementation[*], id:String[1]):RootRelationalInstanceSetImplementation[1] -{ - $classMappings->filter(c| $c.id == $id)->toOne('More than one class mapping at the given id: ' + $id); -} - -function <> meta::external::store::relational::modelJoins::convertToRelationalElement(vs:ValueSpecification[1], sourceSet:RootRelationalInstanceSetImplementation[1], targetSet: RootRelationalInstanceSetImplementation[1]):RelationalOperationElement[1] -{ - $vs->match([ - p : SimpleFunctionExpression[1] | - assert($p.func->instanceOf(Property),|'Expected only property function calls in model joins.'); - let var = $p.parametersValues->evaluateAndDeactivate()->at(0); - assert($var->instanceOf(VariableExpression) && $var->cast(@VariableExpression).name->in(['this','that']),|'Properties of $this or $that can be accessed in model joins.'); - let classMapping = if($var->cast(@VariableExpression).name == 'this', | $sourceSet, | $targetSet); - let relationalPropertyMapping = $classMapping.propertyMappings->filter(x|$x.property==$p.func)->cast(@RelationalPropertyMapping)->toOne(); - $relationalPropertyMapping.relationalOperationElement->toOne('Localized relational model joins cannot support more than one relational operation element per property mapping. Found on class mapping id: ' + $classMapping.id);, - - v : InstanceValue[1] | - assert($v.values->size() == 1 && ($v.values->toOne()->instanceOf(String) || $v.values->toOne()->instanceOf(Number) || $v.values->toOne()->instanceOf(Date)),|'The join key should only have one mapping.'); - ^Literal(value=$v.values->toOne()); - ]); - -} - -function <> meta::external::store::relational::modelJoins::transformExpressionSequenceIntoJoin(expressionSequence: SimpleFunctionExpression[1], sourceId:String[1], targetId:String[1], classMappings:RootRelationalInstanceSetImplementation[*]): Join[1] -{ - let functionOperator = $expressionSequence.functionName->toOne(); - let joinName = $sourceId + '_' + $targetId + '_GeneratedRelationalJoin'; - let sourceMainTableAlias = $classMappings->getClassMappingAtId($sourceId).mainTableAlias->map(alias|^$alias(name=$alias.relation->cast(@NamedRelation).name)); - let targetMainTableAlias = $classMappings->getClassMappingAtId($targetId).mainTableAlias->map(alias|^$alias(name=$alias.relation->cast(@NamedRelation).name)); - let sourceDatabase = $sourceMainTableAlias.database->toOne(); - let targetDatabase = $targetMainTableAlias.database->toOne(); - let aggregatedDatabase = ^Database(includes=[$sourceDatabase, $targetDatabase]); - - assertContains(['equal', 'not', 'and', 'or', 'greaterThanEqual', 'greaterThan', 'lessThanEqual', 'lessThan' ], $functionOperator, 'Failed to translate XStore Property into Relational Property because function operator is not in standard list'); - - let join = if( - $functionOperator->in(['equal', 'greaterThanEqual', 'greaterThan', 'lessThanEqual', 'lessThan' ]), - | let expressionParameters = $expressionSequence.parametersValues->evaluateAndDeactivate(); - let sourceSet = getClassMappingAtId($classMappings, $sourceId); - let targetSet = getClassMappingAtId($classMappings, $targetId); - - let parameters = $expressionParameters->map(e | $e->convertToRelationalElement($sourceSet, $targetSet)); - let operation = ^DynaFunction(name=$functionOperator, parameters=$parameters); - ^Join( - name=$joinName, - operation=$operation, - target=$targetMainTableAlias, - database=$aggregatedDatabase, - aliases = [^Pair(first=$targetMainTableAlias,second=$sourceMainTableAlias), ^Pair(first=$sourceMainTableAlias,second=$targetMainTableAlias)] - );, - | let childJoins = $expressionSequence.parametersValues->map(p | - transformExpressionSequenceIntoJoin($p->cast(@SimpleFunctionExpression)->evaluateAndDeactivate(), $sourceId, $targetId, $classMappings) - ); - let operation = ^DynaFunction(name=$functionOperator, parameters=$childJoins.operation->evaluateAndDeactivate()); - ^Join( - name=$joinName, - operation=$operation, - target=$targetMainTableAlias, - database=$aggregatedDatabase, - aliases = [^Pair(first=$targetMainTableAlias,second=$sourceMainTableAlias), ^Pair(first=$sourceMainTableAlias,second=$targetMainTableAlias)]->concatenate($childJoins.aliases) - ); - ); - -} +import meta::relational::contract::*; +import meta::pure::store::*; +import meta::pure::executionPlan::*; +import meta::pure::extension::*; +import meta::relational::extension::*; +import meta::alloy::objectReference::*; +import meta::external::store::relational::runtime::*; +import meta::relational::runtime::*; +import meta::relational::functions::pureToSqlQuery::union::*; +import meta::relational::functions::pureToSqlQuery::metamodel::*; +import meta::relational::functions::pureToSqlQuery::*; +import meta::pure::metamodel::valuespecification::*; +import meta::relational::mapping::*; +import meta::relational::metamodel::*; +import meta::relational::metamodel::operation::*; +import meta::relational::metamodel::relation::*; +import meta::relational::metamodel::join::*; +import meta::relational::functions::pureToSqlQuery::*; +import meta::relational::functions::pureToSqlQuery::relationalmappingspecification::*; +import meta::pure::mapping::*; +import meta::pure::mapping::relation::*; +import meta::pure::metamodel::path::*; +import meta::pure::metamodel::serialization::grammar::*; +import meta::pure::milestoning::*; +import meta::pure::tds::*; +import meta::relational::functions::*; +import meta::relational::milestoning::*; +import meta::pure::router::clustering::*; +import meta::pure::router::printer::*; +import meta::pure::router::routing::*; +import meta::pure::router::store::embedded::*; +import meta::pure::router::store::metamodel::*; +import meta::pure::router::store::metamodel::clustering::*; +import meta::pure::router::utils::*; +import meta::relational::extension::*; +import meta::pure::mapping::xStore::*; +import meta::external::store::relational::modelJoins::*; + +function meta::external::store::relational::modelJoins::localizeXStoreAssociation(xstoreAssociation: XStoreAssociationImplementation[1], setImpl:SetImplementation[*], stores:Store[*]):AssociationImplementation[1] +{ + let database = ^Database(includes=$stores->cast(@Database)); + let newAssociation = ^RelationalAssociationImplementation(association=$xstoreAssociation.association,parent=$xstoreAssociation.parent,id=$xstoreAssociation.id, stores=$database); + let relationalPropertyMappings = $xstoreAssociation.propertyMappings->cast(@XStorePropertyMapping)->map(xstorePropertyMapping | transformXStorePropertyIntoRelationalProperty($xstorePropertyMapping, $setImpl, $database, $newAssociation)); + ^$newAssociation(propertyMappings = $relationalPropertyMappings); +} + +function <> meta::external::store::relational::modelJoins::transformXStorePropertyIntoRelationalProperty(xstoreProperty: meta::pure::mapping::xStore::XStorePropertyMapping[1], setImpl:SetImplementation[*], database:Database[1], propertyMappingOwner:AssociationImplementation[1]): RelationalPropertyMapping[1] +{ + let expressionSequence = $xstoreProperty.crossExpression.expressionSequence->cast(@SimpleFunctionExpression)->toOne()->evaluateAndDeactivate(); + let sourceId = $xstoreProperty.sourceSetImplementationId->toOne(); + let targetId = $xstoreProperty.targetSetImplementationId->toOne(); + let classMappings = $setImpl->map(s|$s->match([ + r:RelationalInstanceSetImplementation[1]|$r, + r:RelationFunctionInstanceSetImplementation[1]|$r->transformRelationFunctionClassMapping() + ] + ))->cast(@InstanceSetImplementation); + + let join = transformExpressionSequenceIntoJoin($expressionSequence, $sourceId, $targetId, $classMappings, $database); + + let joinTreeNode = ^JoinTreeNode( + joinName=$join.name, + database=$join.database->toOne(), + alias=$join.target->toOne(), + join=$join + ); + + let ro = ^RelationalOperationElementWithJoin(joinTreeNode=$joinTreeNode); + let r = ^RelationalPropertyMapping(owner=$propertyMappingOwner, sourceSetImplementationId=$sourceId,property=$xstoreProperty.property->toOne(),relationalOperationElement=$ro,targetSetImplementationId=$targetId); +} + +function <> meta::external::store::relational::modelJoins::getClassMappingAtId(classMappings:InstanceSetImplementation[*], id:String[1]):InstanceSetImplementation[1] +{ + $classMappings->filter(c| $c.id == $id)->toOne('More than one class mapping at the given id: ' + $id); +} + +function <> meta::external::store::relational::modelJoins::getMainTableAliasForSetId(classMapping:InstanceSetImplementation[1]):TableAlias[1] +{ + $classMapping->match( + [ + r:RootRelationalInstanceSetImplementation[1]| $r.mainTableAlias->map(alias|^$alias(name=$alias.relation->cast(@NamedRelation).name)), + t:RelationFunctionInstanceSetImplementation[1]| ^TableAlias(name=$t.id, relationalElement=^RelationFunction(owner=$t)) + ] + ); +} + +function <> meta::external::store::relational::modelJoins::convertToRelationalElement(vs:ValueSpecification[1], sourceSet:InstanceSetImplementation[1], targetSet: InstanceSetImplementation[1]):RelationalOperationElement[1] +{ + $vs->match([ + p : SimpleFunctionExpression[1] | + assert($p.func->instanceOf(Property),|'Expected only property function calls in model joins.'); + let var = $p.parametersValues->evaluateAndDeactivate()->at(0); + assert($var->instanceOf(VariableExpression) && $var->cast(@VariableExpression).name->in(['this','that']),|'Properties of $this or $that can be accessed in model joins.'); + let classMapping = if($var->cast(@VariableExpression).name == 'this', | $sourceSet, | $targetSet); + let relationalPropertyMapping = $classMapping.propertyMappings->filter(x|$x.property==$p.func)->cast(@RelationalPropertyMapping)->toOne(); + $relationalPropertyMapping.relationalOperationElement->toOne('Localized relational model joins cannot support more than one relational operation element per property mapping. Found on class mapping id: ' + $classMapping.id);, + + v : InstanceValue[1] | + assert($v.values->size() == 1 && ($v.values->toOne()->instanceOf(String) || $v.values->toOne()->instanceOf(Number) || $v.values->toOne()->instanceOf(Date)),|'The join key should only have one mapping.'); + ^Literal(value=$v.values->toOne()); + ]); + +} + +function <> meta::external::store::relational::modelJoins::transformExpressionSequenceIntoJoin(expressionSequence: SimpleFunctionExpression[1], sourceId:String[1], targetId:String[1], classMappings:InstanceSetImplementation[*], database:Database[1]): Join[1] +{ + let functionOperator = $expressionSequence.functionName->toOne(); + let joinName = $sourceId + '_' + $targetId + '_GeneratedRelationalJoin'; + let sourceClassMapping = $classMappings->getClassMappingAtId($sourceId); + let targetClassMapping = $classMappings->getClassMappingAtId($targetId); + let sourceMainTableAlias = $sourceClassMapping->getMainTableAliasForSetId(); + let targetMainTableAlias = $targetClassMapping->getMainTableAliasForSetId(); + + assertContains(['equal', 'not', 'and', 'or', 'greaterThanEqual', 'greaterThan', 'lessThanEqual', 'lessThan' ], $functionOperator, 'Failed to translate XStore Property into Relational Property because function operator is not in standard list'); + + let join = if( + $functionOperator->in(['equal', 'greaterThanEqual', 'greaterThan', 'lessThanEqual', 'lessThan' ]), + | let expressionParameters = $expressionSequence.parametersValues->evaluateAndDeactivate(); + let sourceSet = getClassMappingAtId($classMappings, $sourceId); + let targetSet = getClassMappingAtId($classMappings, $targetId); + + let parameters = $expressionParameters->map(e | $e->convertToRelationalElement($sourceSet, $targetSet)); + let operation = ^DynaFunction(name=$functionOperator, parameters=$parameters); + ^Join( + name=$joinName, + operation=$operation, + target=$targetMainTableAlias, + database=$database, + aliases = [^Pair(first=$targetMainTableAlias,second=$sourceMainTableAlias), ^Pair(first=$sourceMainTableAlias,second=$targetMainTableAlias)] + );, + | let childJoins = $expressionSequence.parametersValues->map(p | + transformExpressionSequenceIntoJoin($p->cast(@SimpleFunctionExpression)->evaluateAndDeactivate(), $sourceId, $targetId, $classMappings, $database) + ); + let operation = ^DynaFunction(name=$functionOperator, parameters=$childJoins.operation->evaluateAndDeactivate()); + ^Join( + name=$joinName, + operation=$operation, + target=$targetMainTableAlias, + database=$database, + aliases = [^Pair(first=$targetMainTableAlias,second=$sourceMainTableAlias), ^Pair(first=$sourceMainTableAlias,second=$targetMainTableAlias)]->concatenate($childJoins.aliases) + ); + ); + +} diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/postprocessor/defaultPostProcessor/reAliasQuery.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/postprocessor/defaultPostProcessor/reAliasQuery.pure index a9767154f8f..c0a63e95fd7 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/postprocessor/defaultPostProcessor/reAliasQuery.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/postprocessor/defaultPostProcessor/reAliasQuery.pure @@ -185,6 +185,10 @@ function <> meta::relational::postProcessor::reAlias::transformA assertNotEmpty($new); let alias = $tac.alias; ^$tac(alias = ^$alias(name=$new->toOne()));, + tacn: TableAliasColumnName[1]| let new = $m->get($tacn.alias.name); + assertNotEmpty($new); + let alias = $tacn.alias; + ^$tacn(alias = ^$alias(name=$new->toOne()));, a:Alias[1] | let transformedRelElement = $a.relationalElement->transformAliasName($m); ^$a(relationalElement=$transformedRelElement);, diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/metamodel.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/metamodel.pure index ecdf8fe752c..769f9e96886 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/metamodel.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/metamodel.pure @@ -35,4 +35,14 @@ Class meta::relational::functions::pureToSqlQuery::metamodel::FreeMarkerOperatio Class meta::relational::functions::pureToSqlQuery::metamodel::TableFunctionParamPlaceHolder extends RelationalOperationElement { var: VarPlaceHolder[1]; +} + +Class meta::relational::functions::pureToSqlQuery::metamodel::RelationFunctionColumn extends Column +{ + column: meta::pure::metamodel::relation::Column[1]; +} + +Class meta::relational::functions::pureToSqlQuery::metamodel::RelationFunction extends Relation +{ + owner: meta::pure::mapping::relation::RelationFunctionInstanceSetImplementation[1]; } \ No newline at end of file diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure index ce62e41a6c3..5e8b016239f 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery.pure @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +import meta::pure::router::metamodel::clustering::*; +import meta::pure::router::store::routing::*; +import meta::core::runtime::*; import meta::pure::executionPlan::*; import meta::pure::extension::*; import meta::relational::extension::*; @@ -30,6 +33,7 @@ import meta::relational::metamodel::join::*; import meta::relational::functions::pureToSqlQuery::*; import meta::relational::functions::pureToSqlQuery::relationalmappingspecification::*; import meta::pure::mapping::*; +import meta::pure::mapping::relation::*; import meta::pure::metamodel::path::*; import meta::pure::metamodel::serialization::grammar::*; import meta::pure::milestoning::*; @@ -228,7 +232,7 @@ function <> meta::relational::functions::pureToSqlQuery::manageD | let setImplementation = $functionExpression.parametersValues->at(if($isMap,|1,|0))->cast(@StoreMappingRoutedValueSpecification).sets->toOne()->match([r:RootRelationalInstanceSetImplementation[1]|$r, c:CrossSetImplementation[1]|$c->convertCrossSetImplToRelationalSetImpl()]); - let getAllSelectWithCursor = processGetAll($setImplementation, $setImplementation.class, '', true, -1, true, [], $state, $context, $extensions)->toOne(); + let getAllSelectWithCursor = processGetAll($setImplementation, $setImplementation.class, JoinType.LEFT_OUTER, '', true, -1, true, [], $state, $context, $extensions)->toOne(); let columns = $getAllSelectWithCursor.select.columns->cast(@Alias); assertEquals(1, $columns.relationalElement->cast(@TableAliasColumn).alias->removeDuplicates()->size(), 'Types mapped with joins are not supported yet!'); let topNode =$ sql.currentTreeNode->toOne(); @@ -1288,9 +1292,9 @@ function meta::relational::functions::pureToSqlQuery::processColumnsInRelational // Make sure the column is available a potentially SQL let newSrcOperation = $relationalElement->match([ s:SelectSQLQuery[1] | if ($s.columns->map(c|$c->match([t:TableAliasColumn[1]|$t.column.name, - a:Alias[1]|$a.relationalElement->match([t:TableAliasColumn[1]|$t.column.name, d:DynaFunction[1]|$d.name, l:Literal[1]|$a.name]) - ]) - )->contains($t.column.name), + a:Alias[1]|$a.relationalElement->match([t:TableAliasColumn[1]|$t.column.name, d:DynaFunction[1]|$d.name, l:Literal[1]|$a.name, c:ColumnName[1]|$c.name, wc:WindowColumn[1]|$wc.columnName, x:Any[1]|$a.name]) + ])->stripMatchingQuotes() + )->contains($t.column.name->stripMatchingQuotes()), | $srcOperation;, |// Add the missing columns in the nested select (coming most likely from isolation) // 1. find the right alias within the nested SQL ... @@ -1393,7 +1397,7 @@ function meta::relational::functions::pureToSqlQuery::extractColumnAliasesInRela let aliasesToUse = $nodes->filter(n| let relElements = $n.alias.relationalElement->match([ v:ViewSelectSQLQuery[1]| $v.view, s:SelectSQLQuery[1]| - let relationsSupportingProjectedColumn = $s.columns->filter(c|$c->extractColumnName() == $column.name)->extractTableAliasColumns().alias.relation; + let relationsSupportingProjectedColumn = $s.columns->filter(c|$c->extractColumnName()->toOne()->stripMatchingQuotes() == $column.name)->extractTableAliasColumns().alias.relation; print(if(!$context.debug, |'', | $context.space+'Found depth-2 nested select while processing column - ' + $column.name + '. Adding inner relations from depth-2 subselect for handling class mapping filter with inner join : ' + $relationsSupportingProjectedColumn->map(rel | $rel->buildUniqueName(false, $extensions))->joinStrings('[', ' , ', ']'))); $relationsSupportingProjectedColumn->concatenate($s);, a:Any[1]| $a]); @@ -1423,10 +1427,12 @@ function meta::relational::functions::pureToSqlQuery::processPropertyMapping(pro { print(if(!$context.debug, |'', | $context.space+'*>Process Property: \''+$propertyMapping.property->at(0).name->toOne()+'\' ('+$propertyMapping->map(p|$p.owner->toOne().id+'->'+$p.targetSetImplementationId)->joinStrings(',')+') '+$nodeId+ ',inFilter:' + $state.inFilter->toString() + '\n'+ + $context.space+' (Q)Source Operation> '+$srcOperation->cast(@SelectWithCursor).select->printDebugQuery($context.space, $extensions))); let res = $propertyMapping->match([ s:SemiStructuredRelationalPropertyMapping[*] | processSemiStructuredRelationalPropertyMapping($s, $propertyMapping.property->at(0), $propertyOwnerClass, $srcOperation, $state, $joinType, $nodeId, $aggFromMap, $context->shift(), $extensions), r:RelationalPropertyMapping[*] | processRelationalPropertyMapping($r, $propertyMapping.property->at(0), $propertyOwnerClass, $srcOperation, $state, $joinType, $nodeId, $aggFromMap, $context->shift(), $extensions), + r:RelationFunctionPropertyMapping[*] | processRelationFunctionPropertyMapping($r, $propertyMapping.property->at(0), $propertyOwnerClass, $srcOperation, $state, $joinType, $nodeId, $aggFromMap, $context->shift(), $extensions), r:CrossSetImplementationPropertyMapping[*] | processCrossPropertyMapping($r, $propertyMapping.property->at(0), $propertyOwnerClass, $srcOperation, $state, $joinType, $nodeId, $aggFromMap, $context->shift(), $extensions), e:EmbeddedRelationalInstanceSetImplementation[*] | if(($state.inGetterFlow == true || $state.milestoningUseOtherwise == true) && $e->size() == 1 && $e->toOne()->instanceOf(OtherwiseEmbeddedRelationalInstanceSetImplementation), | processRelationalPropertyMapping($e->toOne()->cast(@OtherwiseEmbeddedRelationalInstanceSetImplementation).otherwisePropertyMapping->cast(@RelationalPropertyMapping), $propertyMapping.property->at(0), $propertyOwnerClass, $srcOperation, $state, $joinType, $nodeId, $aggFromMap, $context->shift(), $extensions);, @@ -1448,7 +1454,7 @@ function meta::relational::functions::pureToSqlQuery::doJoinToClass(relationalPr let className = $c.name->toOne(); let classMappings = $state->getClassMappingById($relationalPropertyMapping.targetSetImplementationId); assertSize($classMappings, 1, 'Expected 1 class mapping for \'%s\' in \'%s\', found %d', [$className, $state.mapping.name->toOne(), $classMappings->size()]); - let setImplementation = $classMappings->toOne()->match([a : meta::pure::mapping::aggregationAware::AggregationAwareSetImplementation[1] | $a.mainSetImplementation, s: SetImplementation[1] | $s])->cast(@RootRelationalInstanceSetImplementation); + let setImplementation = $classMappings->toOne()->match([a : meta::pure::mapping::aggregationAware::AggregationAwareSetImplementation[1] | $a.mainSetImplementation, s: SetImplementation[1] | $s])->cast(@InstanceSetImplementation); doJoinToClass($joinTree, $c, $setImplementation, $base, $joinType, $nodeId, $state, $context, $extensions); } @@ -1512,42 +1518,46 @@ function meta::relational::functions::pureToSqlQuery::processClassMappingInnerJo | $updatedQuery); } -function meta::relational::functions::pureToSqlQuery::doJoinToClass(joinTree: JoinTreeNode[1], c:Class[1], setImplementation: RootRelationalInstanceSetImplementation[1], base:SelectWithCursor[1], joinType:JoinType[1], nodeId:String[1], state:State[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] +function meta::relational::functions::pureToSqlQuery::doJoinToClass(joinTree: JoinTreeNode[1], c:Class[1], setImpl: InstanceSetImplementation[1], base:SelectWithCursor[1], joinType:JoinType[1], nodeId:String[1], state:State[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] { - let queryWithJoin = applyJoinInTree($base.select.data->toOne(), $base.currentTreeNode->toOne(), $joinTree, $base, $nodeId, $joinType, true, true, [], $state, $context, $extensions); + let queryWithJoin = applyJoinInTree($base.select.data->toOne(), $base.currentTreeNode->toOne(), $joinTree, $base, $nodeId, $joinType, true, true, [], $state, $context, $extensions); - // Potentially add filter - let queryWithFilterNoSaved = if ($setImplementation->resolveFilter()->isEmpty() || ($setImplementation->resolveDistinct() == true) || !$setImplementation->resolveGroupBy()->isEmpty(), - | $queryWithJoin, - | if ($setImplementation->resolveFilter()->isEmpty(), - | $queryWithJoin, - | //Do not apply the filter if this is a multigrain table and we are joining on the primary key - if ($setImplementation->resolveFilter()->toOne().filter->instanceOf(MultiGrainFilter) - && isSimpleJoinToPk($joinTree.join, $base.currentTreeNode->toOne(), $extensions), | $queryWithJoin, - | if ($setImplementation->resolveFilter().joinTreeNode.joinType == JoinType.INNER, - | $queryWithJoin->processClassMappingInnerJoinFilter($setImplementation, $c, $base, $nodeId, $state, $context, $extensions), - | $queryWithJoin->applyTypeFilter($setImplementation, $nodeId, $state, $context, $extensions) - )); - ); - ); + if($setImpl->instanceOf(RelationFunctionInstanceSetImplementation), + | $queryWithJoin, + | // Potentially add filter + let setImplementation = $setImpl->cast(@RootRelationalInstanceSetImplementation); + let queryWithFilterNoSaved = if ($setImplementation->resolveFilter()->isEmpty() || ($setImplementation->resolveDistinct() == true) || !$setImplementation->resolveGroupBy()->isEmpty(), + | $queryWithJoin, + | if ($setImplementation->resolveFilter()->isEmpty(), + | $queryWithJoin, + | //Do not apply the filter if this is a multigrain table and we are joining on the primary key + if ($setImplementation->resolveFilter()->toOne().filter->instanceOf(MultiGrainFilter) + && isSimpleJoinToPk($joinTree.join, $base.currentTreeNode->toOne(), $extensions), | $queryWithJoin, + | if ($setImplementation->resolveFilter().joinTreeNode.joinType == JoinType.INNER, + | $queryWithJoin->processClassMappingInnerJoinFilter($setImplementation, $c, $base, $nodeId, $state, $context, $extensions), + | $queryWithJoin->applyTypeFilter($setImplementation, $nodeId, $state, $context, $extensions) + )); + ); + ); - let queryWithFilter = ^$queryWithFilterNoSaved(savedRoot=if(!$queryWithFilterNoSaved.select.data->isEmpty() && !$queryWithFilterNoSaved.currentTreeNode->isEmpty(),|pair($queryWithFilterNoSaved.select.data->toOne(),$queryWithFilterNoSaved.currentTreeNode->toOne()),|[])); + let queryWithFilter = ^$queryWithFilterNoSaved(savedRoot=if(!$queryWithFilterNoSaved.select.data->isEmpty() && !$queryWithFilterNoSaved.currentTreeNode->isEmpty(),|pair($queryWithFilterNoSaved.select.data->toOne(),$queryWithFilterNoSaved.currentTreeNode->toOne()),|[])); - // Add existing filter - let select = $queryWithFilter.select; - let queryAllFilters = ^$queryWithFilter( - select=^$select( - filteringOperation= $select.filteringOperation - ->concatenate($base.select.filteringOperation)->andFilters($extensions) - ) - ); + // Add existing filter + let select = $queryWithFilter.select; + let queryAllFilters = ^$queryWithFilter( + select=^$select( + filteringOperation = $select.filteringOperation + ->concatenate($base.select.filteringOperation)->andFilters($extensions) + ) + ); - if (!$setImplementation->resolveDistinct() == true && $setImplementation->resolveGroupBy()->isEmpty(), - | $queryAllFilters, - | let getAllSelectWithCursor = processGetAll($setImplementation, $setImplementation.class, $nodeId, false, -1, true, [], $state, $context, $extensions); - $queryAllFilters->replaceJoinTableWithSelectQuery($getAllSelectWithCursor.select, $queryAllFilters.currentTreeNode->toOne(), '', $nodeId, $context, $extensions); - ); + if (!$setImplementation->resolveDistinct() == true && $setImplementation->resolveGroupBy()->isEmpty(), + | $queryAllFilters, + | let getAllSelectWithCursor = processGetAll($setImplementation, $setImplementation.class, $joinType, $nodeId, false, -1, true, [], $state, $context, $extensions); + $queryAllFilters->replaceJoinTableWithSelectQuery($getAllSelectWithCursor.select, $queryAllFilters.currentTreeNode->toOne(), '', $nodeId, $context, $extensions); + ); + ); } function meta::relational::functions::pureToSqlQuery::isSimpleJoinToPk(join:Join[1], currentTreeNode:RelationalTreeNode[1], extensions:Extension[*]):Boolean[1] @@ -1639,9 +1649,16 @@ Class meta::relational::functions::pureToSqlQuery::MissingColProcessigRes selectWC:SelectWithCursor[1]; } +function meta::relational::functions::pureToSqlQuery::processRelationFunctionPropertyMapping(propertyMappings:RelationFunctionPropertyMapping[*], property:AbstractProperty[1], propertyOwnerClass:Class[1], oldSrcOperation:SelectWithCursor[1], state:State[1], joinType:JoinType[1], nodeId:String[1], aggFromMap:List[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] +{ + let propertyMapping = $propertyMappings->toOne('Unions not supported with Relation Expression mappings yet!'); + let transformedRelationalPropertyMapping = $propertyMapping->transformRelationPropertyMappingsToRelational($state->getClassMappingById($propertyMapping.sourceSetImplementationId)->toOne()->cast(@RelationFunctionInstanceSetImplementation)); + processRelationalPropertyMapping($transformedRelationalPropertyMapping, $property, $propertyOwnerClass, $oldSrcOperation, $state, $joinType, $nodeId, $aggFromMap, $context, $extensions); +} + function meta::relational::functions::pureToSqlQuery::processRelationalPropertyMapping(OrigrelationalPropertyMappings:RelationalPropertyMapping[*], property:AbstractProperty[1], propertyOwnerClass:Class[1], oldSrcOperation:SelectWithCursor[1], state:State[1], joinType:JoinType[1], nodeId:String[1], aggFromMap:List[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] { - let propertyReturnType = $property->cast(@Property).genericType.rawType->toOne(); + let propertyReturnType = $property->cast(@Property).genericType.rawType->toOne(); let relationalPropertyMappings = $OrigrelationalPropertyMappings->map(p|$p->buildPossibleEnumMappingPushDown($state)); let firstStep = $propertyReturnType->match( [ @@ -1783,6 +1800,7 @@ function meta::relational::functions::pureToSqlQuery::processRelationalPropertyM $updatedRelationalPropertyMappings, $propertyOwnerClass, $c, + $joinType, $nodeId, $state, $context, @@ -1896,7 +1914,7 @@ function meta::relational::functions::pureToSqlQuery::processCrossPropertyMappin | $state->getClassMappingById($crossPropertyMappings->at(0).targetSetImplementationId)->cast(@RootRelationalInstanceSetImplementation)->toOne()); let propertypairs = $crossPropertyMappings->at(0)->cast(@CrossSetImplementationComplexPropertyMapping).crossExperssionPropertyPairs; let currentAlias = $oldSrcOperation.currentTreeNode.alias; - let targetAlias = $targetSet.mainTableAlias->createJoinTableAlias($nodeId, $state, [], $context, $extensions); + let targetAlias = $targetSet.mainTableAlias->createJoinTableAlias($joinType, $nodeId, $state, [], $context, $extensions); let joinTree = ^JoinTreeNode ( alias = $targetAlias->toOne(), @@ -3640,7 +3658,7 @@ function meta::relational::functions::pureToSqlQuery::processGetAllForEachDate(e { let setImplementation = $expression.parametersValues->at(0)->cast(@StoreMappingRoutedValueSpecification).sets->toOne(); let mappingOwner = $expression.parametersValues->at(1)->cast(@StoreMappingRoutedValueSpecification).sets->toOne(); - let swc1 = processGetAll($expression, $setImplementation, [], $nodeId, $state, $vars, $context, $extensions)->cast(@SelectWithCursor); + let swc1 = processGetAll($expression, $setImplementation, [], $vars, $state, $joinType, $nodeId, $context, $extensions)->cast(@SelectWithCursor); let swc2 = processValueSpecification($expression.parametersValues->at(1), $currentPropertyMapping, $operation, $vars, $state, $joinType, $nodeId, $aggFromMap, $context, $extensions)->toOne()->cast(@SelectWithCursor); let query1 = $swc1.select->pushExtraFilteringOperation($extensions)->pushSavedFilteringOperation($extensions); let query2 = $swc2.select->pushExtraFilteringOperation($extensions)->pushSavedFilteringOperation($extensions); @@ -3687,22 +3705,23 @@ function meta::relational::functions::pureToSqlQuery::processGetAllForEachDate(e function meta::relational::functions::pureToSqlQuery::processGetAll(expression:FunctionExpression[1], currentPropertyMapping:PropertyMapping[*], operation:SelectWithCursor[1], vars:Map[1], state:State[1], joinType:JoinType[1], nodeId:String[1], aggFromMap:List[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] { let setImplementation = $expression.parametersValues->at(0)->cast(@StoreMappingRoutedValueSpecification).sets->toOne(); - processGetAll($expression, $setImplementation, $expression.parametersValues, $nodeId, $state, $vars, $context, $extensions); + processGetAll($expression, $setImplementation, $expression.parametersValues, $vars, $state, $joinType, $nodeId, $context, $extensions); } -function meta::relational::functions::pureToSqlQuery::processGetAll(expression: FunctionExpression[1], setImplementation:SetImplementation[1], parameters:ValueSpecification[*], nodeId:String[1], state:State[1], vars:Map[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] +function meta::relational::functions::pureToSqlQuery::processGetAll(expression: FunctionExpression[1], setImplementation:SetImplementation[1], parameters:ValueSpecification[*], vars:Map[1], state:State[1], joinType:JoinType[1], nodeId:String[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] { let processRootSetImpl = {r:RootRelationalInstanceSetImplementation[1] |let milestoningContext = getMilestoningContextForAll($expression,$r, $parameters, $state, $vars, $context, $extensions); - processGetAll($r, $r.class, $nodeId, true, -1, !$state.inProject, $milestoningContext, $state, $context, $extensions);}; + processGetAll($r, $r.class, $joinType, $nodeId, true, -1, !$state.inProject, $milestoningContext, $state, $context, $extensions);}; $setImplementation->match([r:RootRelationalInstanceSetImplementation[1]| $processRootSetImpl->eval($r), + r:RelationFunctionInstanceSetImplementation[1]| processRelationFunctionClassMapping($r, $vars, $state, $joinType, $nodeId, $context, $extensions);, r:meta::pure::router::clustering::CrossSetImplementation[1]| let newTable = ^VarCrossSetPlaceHolder(varName=$r.varName, name=$r.varName, schema=^Schema(name='default', database=^Database()), crossSetImplementation = $r); let treeNode = ^RootJoinTreeNode(alias = ^TableAlias(name = 'root', relationalElement = $newTable)); ^SelectWithCursor(select = ^SelectSQLQuery(data = $treeNode), currentTreeNode = $treeNode);, o:OperationSetImplementation[1]|let setImpls = $o->resolveOperation($state.mapping->toOne())->cast(@RootRelationalInstanceSetImplementation); if($setImpls->size()==1,| $processRootSetImpl->eval($setImpls->at(0)) ,| let milestoningContext = getMilestoningContextForAll($expression,$o, $parameters, $state, $vars, $context, $extensions); - let union = buildUnion($setImpls, [], false, $state.inProject, $milestoningContext, $nodeId, $state, $context, $extensions); + let union = buildUnion($setImpls, [], $joinType, false, $state.inProject, $milestoningContext, $nodeId, $state, $context, $extensions); let newRoot = ^RootJoinTreeNode(alias = ^TableAlias(name='unionBase', relationalElement=$union)); let fullCols = $union.queries->at(0).columns->cast(@Alias).name->filter(n | $n != 'u_type'); ^SelectWithCursor( select = ^SelectSQLQuery( columns = ^Alias(name= 'u_type', relationalElement = ^TableAliasColumn(alias = $newRoot.alias, column = ^Column(name='u_type', type=^meta::relational::metamodel::datatype::Integer()))) @@ -3716,6 +3735,15 @@ function meta::relational::functions::pureToSqlQuery::processGetAll(expression: ]); } +function meta::relational::functions::pureToSqlQuery::processRelationFunctionClassMapping(r:RelationFunctionInstanceSetImplementation[1], vars:Map[1], state:State[1], joinType:JoinType[1], nodeId:String[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] +{ + let routedRelationFunction = $r->routeRelationFunctionSetImplementation($state.mapping->toOne(), $extensions)->cast(@RelationFunctionInstanceSetImplementation).relationFunction; + let relationExpression = $routedRelationFunction.expressionSequence->evaluateAndDeactivate()->at(0)->cast(@ClusteredValueSpecification).val; + let newCursor = ^SelectWithCursor(select=^SelectSQLQuery()); + let newState = defaultState($state.mapping, $state.inScopeVars); + let cursor = $relationExpression->processValueSpecification([], $newCursor, $vars, $newState, $joinType, $nodeId, ^List(), $context, $extensions)->toOne()->cast(@SelectWithCursor); + ^$cursor(select=$cursor.select->moveSelectQueryToSubSelect($cursor.currentTreeNode, [], $nodeId, $context, $extensions)); +} function meta::relational::functions::pureToSqlQuery::getAllExpressionSetImplementations(expression:FunctionExpression[1]):RootRelationalInstanceSetImplementation[*] { @@ -3775,15 +3803,15 @@ function meta::relational::functions::pureToSqlQuery::getRelationalElementWithIn ); } -function meta::relational::functions::pureToSqlQuery::processGetAll(viewSpecification:RelationalMappingSpecification[1], c:Class[0..1], nodeId:String[1], addPk:Boolean[1], pkOffset:Integer[1], addAllColumns:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], state:State[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] +function meta::relational::functions::pureToSqlQuery::processGetAll(viewSpecification:RelationalMappingSpecification[1], c:Class[0..1], joinType:JoinType[1], nodeId:String[1], addPk:Boolean[1], pkOffset:Integer[1], addAllColumns:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], state:State[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] { let newState = ^$state(inProject=false, inProjectFunctions=false, processingProjectionThread=false, shouldIsolate=false, inFilter=false); - processRelationalMappingSpecification($viewSpecification, $c, $nodeId, $addPk, $pkOffset, $addAllColumns, $milestoningContext, $newState, $context, $extensions); + processRelationalMappingSpecification($viewSpecification, $c, $joinType, $nodeId, $addPk, $pkOffset, $addAllColumns, $milestoningContext, $newState, $context, $extensions); } -function meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification(viewSpecification:RelationalMappingSpecification[1], c:Class[0..1], nodeId:String[1], addPk:Boolean[1], pkOffset:Integer[1], addAllColumns:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], state:State[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] +function meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification(viewSpecification:RelationalMappingSpecification[1], c:Class[0..1], joinType:JoinType[1], nodeId:String[1], addPk:Boolean[1], pkOffset:Integer[1], addAllColumns:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], state:State[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] { - let mainTable = $viewSpecification->mainRelation()->processRelation($c, $nodeId, $addPk, $pkOffset, $addAllColumns, $milestoningContext, $state, $context, $extensions); + let mainTable = $viewSpecification->mainRelation()->processRelation($c, $joinType, $nodeId, $addPk, $pkOffset, $addAllColumns, $milestoningContext, $state, $context, $extensions); let innerJoinFilterExists = $viewSpecification->getFilter().joinTreeNode.joinType == JoinType.INNER; let currentRelationalElement = if ($innerJoinFilterExists, | getRelationalElementWithInnerJoin($viewSpecification, $mainTable, $nodeId, $state, $context, $extensions), @@ -3860,18 +3888,19 @@ function meta::relational::functions::pureToSqlQuery::processRelationalMappingSp } -function meta::relational::functions::pureToSqlQuery::processRelation(r: RelationalOperationElement[1], c:Class[0..1], nodeId:String[1], addPk:Boolean[1], pkOffset:Integer[1], addAllColumns:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], state:State[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] +function meta::relational::functions::pureToSqlQuery::processRelation(r: RelationalOperationElement[1], c:Class[0..1], joinType:JoinType[1], nodeId:String[1], addPk:Boolean[1], pkOffset:Integer[1], addAllColumns:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], state:State[1], context:DebugContext[1], extensions:Extension[*]):RelationalOperationElement[1] { $r->match([ s:SelectSQLQuery[1] | $s, t:Table[1] | $t, f:TabularFunction[1] | $f, u:Union[1] | $u, - v:View[1] | let selectWithCursor = processRelationalMappingSpecification($v, $c, $nodeId, $addPk, $pkOffset, true, $milestoningContext, ^$state(inFilter=false), $context, $extensions); + v:View[1] | let selectWithCursor = processRelationalMappingSpecification($v, $c, $joinType, $nodeId, $addPk, $pkOffset, true, $milestoningContext, ^$state(inFilter=false), $context, $extensions); let select = $selectWithCursor.select; let selectWithFiltersProcessed = $select->pushExtraFilteringOperation($extensions)->pushSavedFilteringOperation($extensions); ^ViewSelectSQLQuery(view=$v, name=$v.name, columns = $selectWithFiltersProcessed.columns, selectSQLQuery = $selectWithFiltersProcessed, schema=$v->mainTable().schema);, - s:SemiStructuredArrayFlatten[1] | $s + s:SemiStructuredArrayFlatten[1] | $s, + t:RelationFunction[1] | $t.owner->processRelationFunctionClassMapping(newMap([]->cast(@Pair)), $state, $joinType, $nodeId, $context, $extensions).select ]); } @@ -4544,7 +4573,7 @@ function meta::relational::functions::pureToSqlQuery::processIn(valueArg:ValueSp let valueFilter = if ($isJoinToFilterTable, | $reprocessedCollectionArg.select ,| $mergedSQL); - let value = if($state.inFilter,|$valueFilter.filteringOperation->filter(p|$p->instanceOf(TableAliasColumn) || $p->instanceOf(ColumnName) || $p->instanceOf(DynaFunction) || $p->instanceOf(JoinStrings) || $p->instanceOf(SemiStructuredArrayFlattenOutput) || $p->instanceOf(SemiStructuredObjectNavigation))->at(0),|$mergedSQL.columns->at(0)); + let value = if($state.inFilter,|$valueFilter.filteringOperation->filter(p|$p->instanceOf(TableAliasColumn) || $p->instanceOf(TableAliasColumnName) || $p->instanceOf(ColumnName) || $p->instanceOf(DynaFunction) || $p->instanceOf(JoinStrings) || $p->instanceOf(SemiStructuredArrayFlattenOutput) || $p->instanceOf(SemiStructuredObjectNavigation))->at(0),|$mergedSQL.columns->at(0)); let collection = if($state.inFilter, |$mergedSQL.filteringOperation->filter(p|$p->instanceOf(Literal) || $p->instanceOf(LiteralList) || $p->instanceOf(FreeMarkerOperationHolder))->at(0),|$mergedSQL.columns->at(1)); let selectWithCursor = $value->extractSelectWithCursor($operation); @@ -5041,7 +5070,7 @@ function <> meta::relational::functions::pureToSqlQuery::process { let mainTable = $f.parametersValues->at(0)->cast(@FunctionExpression)->getTableFromParameter(); - let mainTableAliasTDS = ^TableAlias(name = 'root', relationalElement = $mainTable->processRelation([], $nodeId, false, 0, false, [], $state, $context, $extensions)); + let mainTableAliasTDS = ^TableAlias(name = 'root', relationalElement = $mainTable->processRelation([], $joinType, $nodeId, false, 0, false, [], $state, $context, $extensions)); let joinTreeNode = ^RootJoinTreeNode(alias=$mainTableAliasTDS); ^SelectWithCursor( select = ^TdsSelectSqlQuery( @@ -5081,7 +5110,7 @@ function meta::relational::functions::pureToSqlQuery::processTdsRenameColumns(ex ), p:Pair[*] | $p, vss:ValueSpecification[*] | $vss->map(vs|$vs->reprocessVS()->reactivate($state.inScopeVars)->evaluateAndDeactivate()->cast(@Pair)); - ]); + ]); let projectColNamesMap = $projectColPairNames->map(p|^$p(first = $p.first->addQuotesIfNecessary(), second = $p.second->addQuotesIfNecessary()))->newMap(); let noWrappedProjectColNamesMap = $projectColPairNames->map(p|^$p(first = $p.first->stripMatchingQuotes(), second = $p.second))->newMap(); @@ -5987,10 +6016,11 @@ function meta::relational::functions::pureToSqlQuery::removeSelectColumnsAndJoin function meta::relational::functions::pureToSqlQuery::removeColumnsAndJoins(query:SelectWithCursor[1], nodeId:String[1], context:DebugContext[1], extensions:Extension[*]):SelectWithCursor[1] { let isDistinct = $query.select.distinct == true; + let ctn = if($query.currentTreeNode->isEmpty(), |$query.select.data, |$query.currentTreeNode); let selectAndCurrentTreeNode = if($isDistinct, - | let selectMoved = moveSelectQueryToSubSelect($query.select, $query.currentTreeNode, [], $nodeId, $context, $extensions); + | let selectMoved = moveSelectQueryToSubSelect($query.select, $ctn, [], $nodeId, $context, $extensions); pair($selectMoved, $selectMoved.data->toOne());, - | removeSelectColumnsAndJoins($query.select, $query.currentTreeNode)); + | removeSelectColumnsAndJoins($query.select, $ctn)); ^SelectWithCursor( select = $selectAndCurrentTreeNode.first, @@ -7622,7 +7652,7 @@ function meta::relational::functions::pureToSqlQuery::addExtraJoinColumns(tableA let joinTableAliasColumns = $j.operation->extractTableAliasColumns()->filter(c|$c.alias==$tableAlias)->removeDuplicates(); let updatedRelationalElement = $tableAlias.relationalElement->match([s:SelectSQLQuery[1] | let existingCols = $s.columns->map(col| $col->match([tac:TableAliasColumn[1] | $tac.column.name, a:Alias[1] | $a.name - ])); + ])->stripMatchingQuotes()); //join alias is vs select, not safe to use the same alias within the select's join tree nodes, since it will likely match with the root (CorrelatedSubQuery), but the subselect could have many nodes let allNodes = $s.data->toOne()->getAllNodes()->cast(@RelationalTreeNode); let nodeAliasNameToRelation = $allNodes->map(n|pair($n.alias.name, $n.alias.relation))->newMap(); @@ -7769,7 +7799,7 @@ function meta::relational::functions::pureToSqlQuery::applyJoinWithExplodeInCond assert($srcPrimaryKeys->size() >= 1, 'atleast one primary key should be defined on the relation: ' + $sourceAliasInJoin.name); // 1. create a subquery(exploded) for the flattened relation by doing a lateral join between tableToFlatten and the flattened array. - let toExplodeRootTreeNode = ^RootJoinTreeNode(alias=^TableAlias(name='root',relationalElement=$tableToExplodeAlias.relationalElement->processRelation([], $nodeId, true, -1, true, $milestoningContext, $state, $context, $extensions))); + let toExplodeRootTreeNode = ^RootJoinTreeNode(alias=^TableAlias(name='root',relationalElement=$tableToExplodeAlias.relationalElement->processRelation([], $joinType, $nodeId, true, -1, true, $milestoningContext, $state, $context, $extensions))); let pathNavigation = meta::relational::functions::sqlQueryToString::parseSemiStructuredPathNavigation($pathToExplode); let initialNavigation = if($pathNavigation->at(0)->isDigit(), | ^SemiStructuredArrayElementAccess(operand=^$columnToExplode(alias = $toExplodeRootTreeNode.alias),index=^Literal(value = $pathNavigation->at(0))), | ^SemiStructuredPropertyAccess(property=^Literal(value = $pathNavigation->at(0)->substring(1, $pathNavigation->at(0)->length()-1)), operand=^$columnToExplode(alias = $toExplodeRootTreeNode.alias))); let semiStructuredNavigation = $pathNavigation->slice(1, $pathNavigation->size())->fold({property, navigation | if($property->isDigit(), | ^SemiStructuredArrayElementAccess(operand=$navigation,index=^Literal(value = $property)), | ^SemiStructuredPropertyAccess(property=^Literal(value = $property->substring(1, $property->length()-1)), operand=$navigation)) }, $initialNavigation ); @@ -7813,7 +7843,7 @@ function meta::relational::functions::pureToSqlQuery::applyJoinWithExplodeInCond let replaceWith = ^Alias(name = 'dummy', relationalElement = ^TableAliasColumn(column = ^Column(name = $flattenOutputAlias.name, type = $flattendOutputDataType), alias = $explodedSubQuery)); let updatedOperation = replaceDynaFunctionWithTableAliasColumn($join.operation, ^Pair(first = $toReplace, second = $replaceWith))->cast(@Operation); let otherTableInJoin = if($tableToExplodeAlias == $sourceAliasInJoin, | $targetAliasInJoin, | $sourceAliasInJoin); - let otherTableInJoinUpdated = ^TableAlias(name=$otherTableInJoin.name, relationalElement=$otherTableInJoin.relationalElement->processRelation([], $nodeId, true, -1, true, $milestoningContext, $state, $context, $extensions)); + let otherTableInJoinUpdated = ^TableAlias(name=$otherTableInJoin.name, relationalElement=$otherTableInJoin.relationalElement->processRelation([], $joinType, $nodeId, true, -1, true, $milestoningContext, $state, $context, $extensions)); let reprocessedJoin = reprocessJoin(^$join(operation=$updatedOperation), [^OldAliasToNewAlias(first = $tableToExplodeAlias.name->toOne(), second = $explodedSubQuery), ^OldAliasToNewAlias(first = $otherTableInJoin.name, second = $otherTableInJoinUpdated)], []); let innerJoinTreeNode = ^JoinTreeNode( database = $joinTree.database, @@ -7916,7 +7946,7 @@ function meta::relational::functions::pureToSqlQuery::applyOneJoin(currentNode:R let targetAliasInJoin = findTarget($join, $currentNode, $extensions); let sourceAliasInJoin = $join->otherTableFromAlias($targetAliasInJoin)->toOne(); - let newTargetAlias = createJoinTableAlias($targetAliasInJoin, $nodeId, $state, $milestoningContext, $context, $extensions); + let newTargetAlias = createJoinTableAlias($targetAliasInJoin, $joinType, $nodeId, $state, $milestoningContext, $context, $extensions); let res = ^$joinTree( alias=$newTargetAlias, @@ -7935,12 +7965,12 @@ function meta::relational::functions::pureToSqlQuery::applyOneJoin(currentNode:R ); } -function meta::relational::functions::pureToSqlQuery::createJoinTableAlias(targetAliasInJoin:TableAlias[1], nodeId:String[1], state:State[1], milestoningContext: TemporalMilestoningContext[0..1], context:DebugContext[1], extensions:Extension[*]):TableAlias[1] +function meta::relational::functions::pureToSqlQuery::createJoinTableAlias(targetAliasInJoin:TableAlias[1], joinType:JoinType[1], nodeId:String[1], state:State[1], milestoningContext: TemporalMilestoningContext[0..1], context:DebugContext[1], extensions:Extension[*]):TableAlias[1] { let aliasName = if( $targetAliasInJoin.relation->instanceOf(Table), | $targetAliasInJoin.relation->cast(@Table).name->replace(' ', '_')->replace('"', '')+$nodeId,|$targetAliasInJoin.name+$nodeId); - let relationalElement = $targetAliasInJoin.relationalElement->processRelation([], $nodeId, false, 0, false, $milestoningContext, $state, $context, $extensions); + let relationalElement = $targetAliasInJoin.relationalElement->processRelation([], $joinType, $nodeId, false, 0, false, $milestoningContext, $state, $context, $extensions); ^TableAlias(relationalElement = $relationalElement, name=$aliasName); } @@ -8015,7 +8045,7 @@ function meta::relational::functions::pureToSqlQuery::reprocessAliases(element:R op:BinaryOperation[1]|^$op(left=$op.left->reprocessAliases($map, $oldColumnToNewColumn), right=$op.right->reprocessAliases($map, $oldColumnToNewColumn)), op:VariableArityOperation[1]|^$op(args=$op.args->map(arg | $arg->reprocessAliases($map, $oldColumnToNewColumn))), cn:ColumnName[1] | $cn, - wc: WindowColumn[1]|let window=$wc.window; + wc:WindowColumn[1]|let window=$wc.window; let wf = $wc.func ; ^$wc(window=^$window(partition=$window.partition->map(p|$p->reprocessAliases($map, $oldColumnToNewColumn))->cast(@RelationalOperationElement), sortBy=$window.sortBy->map(p|$p->reprocessAliases($map, $oldColumnToNewColumn))->cast(@RelationalOperationElement),sortDirection=$window.sortDirection), func=$wf->reprocessAliases($map, $oldColumnToNewColumn)->cast(@DynaFunction));, s:SemiStructuredPropertyAccess[1] | ^$s(operand = $s.operand->reprocessAliases($map, $oldColumnToNewColumn), property = $s.property->reprocessAliases($map, $oldColumnToNewColumn), index = $s.index->map(i | $i->reprocessAliases($map, $oldColumnToNewColumn))), diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery_union.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery_union.pure index f74d94b001b..46608fbda95 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery_union.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/pureToSQLQuery/pureToSQLQuery_union.pure @@ -169,10 +169,10 @@ function <> meta::relational::functions::pureToSqlQuery::union:: ^Literal(value=$value); } -function meta::relational::functions::pureToSqlQuery::union::buildUnion(setImpls:RootRelationalInstanceSetImplementation[*], relationalPropertyMappings:RelationalPropertyMapping[*], inAsso:Boolean[1], inProject:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], nodeId:String[1], state:State[1], context:DebugContext[1], extensions:Extension[*]):Union[1] +function meta::relational::functions::pureToSqlQuery::union::buildUnion(setImpls:RootRelationalInstanceSetImplementation[*], relationalPropertyMappings:RelationalPropertyMapping[*], joinType:JoinType[1], inAsso:Boolean[1], inProject:Boolean[1], milestoningContext: TemporalMilestoningContext[0..1], nodeId:String[1], state:State[1], context:DebugContext[1], extensions:Extension[*]):Union[1] { let fullGet = !($inAsso || $inProject) || ($state.insertDriverTablePkInTempTable->isNotEmpty()); - let simpleAllQueries = $setImpls->map(r| processGetAll($r, $r.class, $nodeId, $fullGet || $state.importDataFlow == true, $setImpls->indexOf($r), $fullGet, $milestoningContext, ^$state(importDataFlowAddFks=false), $context, $extensions)); + let simpleAllQueries = $setImpls->map(r| processGetAll($r, $r.class, $joinType, $nodeId, $fullGet || $state.importDataFlow == true, $setImpls->indexOf($r), $fullGet, $milestoningContext, ^$state(importDataFlowAddFks=false), $context, $extensions)); // Get FKs for importDataFlow OR from relationship let propertyMappingsInScope = if ($state.importDataFlow == true, @@ -360,7 +360,7 @@ function meta::relational::functions::pureToSqlQuery::union::buildColumnToNameMa } //XY -function meta::relational::functions::pureToSqlQuery::union::buildSQLQueryOutManySetImplementations(srcOperation:SelectWithCursor[1], relationalPropertyMappings:RelationalPropertyMapping[*], sourceClass:Class[1], targetClass:Class[1], nodeId:String[1], state:State[1], context:DebugContext[1], extensions:Extension[*]):Pair>[1] +function meta::relational::functions::pureToSqlQuery::union::buildSQLQueryOutManySetImplementations(srcOperation:SelectWithCursor[1], relationalPropertyMappings:RelationalPropertyMapping[*], sourceClass:Class[1], targetClass:Class[1], joinType:JoinType[1], nodeId:String[1], state:State[1], context:DebugContext[1], extensions:Extension[*]):Pair>[1] { print(if(!$context.debug, |'', | $context.space+'*>Build SQL query out many set implementations (and join) (propertyMappings:'+$relationalPropertyMappings->size()->toString()+'):\n')); @@ -433,7 +433,7 @@ function meta::relational::functions::pureToSqlQuery::union::buildSQLQueryOutMan print(if(!$context.debug, |'', | $context.space+' - build new Union (using processGetAll)\n')); let newTargetOperation = if ($targetSetImplementations->size() == 1, | $targetSetImplementations->at(0)->mainRelation(), - | buildUnion($targetSetImplementations, $relationalPropertyMappings, true, false, $srcOperation.milestoningContext, $nodeId, $state, $context, $extensions) + | buildUnion($targetSetImplementations, $relationalPropertyMappings, $joinType, true, false, $srcOperation.milestoningContext, $nodeId, $state, $context, $extensions) ); print(if(!$context.debug, |'', | $context.space+' - build new JoinTreeNode\n')); @@ -665,7 +665,7 @@ function <> meta::relational::functions::pureToSqlQuery::union:: let joinTreeNode = $rpm->getJoinTreeNode()->toOne(); let sourceRelation = $rpm.sourceSetImplementationId->getRelation($mapping, $state)->cast(@NamedRelation); let joinedRelation = $joinTreeNode.join->otherTable($sourceRelation).relationalElement->cast(@NamedRelation)->toOne(); - let processedRelation = $joinedRelation->processRelation([], $nodeId, false, 0, false, $parentSourceOperation.milestoningContext, $state, $debugContext, $extensions); + let processedRelation = $joinedRelation->processRelation([], JoinType.LEFT_OUTER, $nodeId, false, 0, false, $parentSourceOperation.milestoningContext, $state, $debugContext, $extensions); let newAlias = ^TableAlias(name='root', relationalElement=$processedRelation); let rootNode = ^RootJoinTreeNode(alias=$newAlias); let swc = ^SelectWithCursor(select=^SelectSQLQuery(data=$rootNode), currentTreeNode=$rootNode); diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure index ee00ff40703..4b1e0f8fd69 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/relationalMappingExecution.pure @@ -26,6 +26,7 @@ import meta::relational::functions::*; import meta::pure::store::*; import meta::pure::router::execution::*; import meta::pure::mapping::*; +import meta::pure::mapping::relation::*; import meta::relational::functions::pureToSqlQuery::*; import meta::relational::functions::sqlQueryToString::*; import meta::relational::mapping::*; @@ -422,7 +423,7 @@ function <> meta::relational::mapping::buildExecutionResultInTDS | {a:Any[1]|$a}, | if ($matchedPath->toOne().second.type == Boolean, | {a:Any[1]|$a == 'true' || $a == true}, | let pm = $matchedPath->toOne().second; - if ($pm.propertyMapping == [] && $pm.type != Decimal, |{a:Any[1]|$a}, + if (($pm.propertyMapping == [] || $pm.propertyMapping->toOne()->instanceOf(RelationFunctionPropertyMapping)) && $pm.type != Decimal, |{a:Any[1]|$a}, | if ($pm.propertyMapping == [] && $pm.type == Decimal, |{a:Any[1]|$a->cast(@Number)->toDecimal()}, | if ($pm.type == Decimal, |{a:Any[1]|$pm.propertyMapping->cast(@RelationalPropertyMapping)->toOne()->transform($a)->cast(@Number)->toOne()->toDecimal()}, | {a:Any[1]|$pm.propertyMapping->cast(@RelationalPropertyMapping)->toOne()->transform($a)});););); @@ -665,7 +666,7 @@ function meta::relational::mapping::generatePropertySql(setImplementation: Relat let context = ^DebugContext(debug=false, space=''); - let mainTable = $srcSetImplementation->mainRelation()->processRelation([], '', false, 0, false, [], $state, $context, $extensions); + let mainTable = $srcSetImplementation->mainRelation()->processRelation([], $joinType, '', false, 0, false, [], $state, $context, $extensions); let innerJoinFilterExists = $srcSetImplementation->getFilter().joinTreeNode.joinType == JoinType.INNER; let updatedMainTable = if ($innerJoinFilterExists, diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/testDataGeneration/testDataGeneration.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/testDataGeneration/testDataGeneration.pure index 43514261bfc..8d111156eeb 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/testDataGeneration/testDataGeneration.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/testDataGeneration/testDataGeneration.pure @@ -403,7 +403,7 @@ function meta::relational::testDataGeneration::generateTestDataForNestedViewTree }); let pure2SqlState = meta::relational::functions::pureToSqlQuery::defaultState(^Mapping(), ^Map>()); - let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($relationTree.view, [], '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions); + let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($relationTree.view, [], JoinType.LEFT_OUTER, '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions); let viewQueryReprocessed = $viewQuery.select->meta::relational::postProcessor::fixTables($oldToNew)->cast(@SelectSQLQuery); let viewSql = postProcessQuery($exeCtx, $viewQueryReprocessed, $runtime, ^Database(name='default'), $extensions).query->sqlQueryToStringPretty($dbConnection.type, $dbConnection.timeZone, $dbConnection.quoteIdentifiers, $extensions); let viewResult = executeInDb($viewSql, $dbConnection); @@ -529,7 +529,7 @@ function <> meta::relational::testDataGeneration::getRowIdenti ); let mainAlias = ^TableAlias(name = 'main', relationalElement=^Table(name=$tempName, columns=$root.columns, temporaryTable=true, schema=^Schema(name='default', database=^Database(name='default')))); let pure2SqlState = meta::relational::functions::pureToSqlQuery::defaultState(^Mapping(), ^Map>()); - let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($view, [], '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions).select; + let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($view, [], JoinType.LEFT_OUTER, '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions).select; let viewQueryWithPKs = $viewQuery->recursiveAddPksWithMilestoning($viewMainTable, $config, $extensions); let relatedAlias = ^TableAlias(name = $view.name, relationalElement=$viewQueryWithPKs); @@ -1056,7 +1056,7 @@ function meta::relational::testDataGeneration::executionPlan::planTestDataGenera );, | let mainAlias = ^TableAlias(name = 'main', relationalElement=^SelectSQLQuery(data=^RootJoinTreeNode(alias=^TableAlias(name='root', relationalElement=^meta::relational::functions::pureToSqlQuery::metamodel::VarSetPlaceHolder(varName=$parentResPlaceHolder->toOne()))))); let pure2SqlState = meta::relational::functions::pureToSqlQuery::defaultState(^Mapping(), ^Map>()); - let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($view, [], '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions).select; + let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($view, [], JoinType.LEFT_OUTER, '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions).select; let viewQueryWithPKs = $viewQuery->recursiveAddPksWithMilestoning($table, $config, $extensions); let relatedAlias = ^TableAlias(name = $view.name, relationalElement=$viewQueryWithPKs); @@ -1148,7 +1148,7 @@ function meta::relational::testDataGeneration::executionPlan::planTestDataGenera ->map(x | pair(^RelationPointer(name=$x.resultType->cast(@RelationResultType).relationName, schemaName=$x.resultType->cast(@RelationResultType).schemaName, database=$x.resultType->cast(@RelationResultType).database), ^SelectSQLQuery(data=^RootJoinTreeNode(alias=^TableAlias(name='root', relationalElement=^meta::relational::functions::pureToSqlQuery::metamodel::VarSetPlaceHolder(varName=$x.varName)))))); let pure2SqlState = meta::relational::functions::pureToSqlQuery::defaultState(^Mapping(), ^Map>()); - let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($relationTree.view, [], '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions); + let viewQuery = meta::relational::functions::pureToSqlQuery::processRelationalMappingSpecification($relationTree.view, [], JoinType.LEFT_OUTER, '', true, -1, true, [], $pure2SqlState, noDebug(), $extensions); let viewQueryReprocessed = $viewQuery.select->fixRelations($tableOldToNew)->quoteVarPlaceHolderTableAliasColumnsIfNotQuouted()->cast(@SelectSQLQuery); let viewRelationDataQuery = ^RelationDataSelectSqlQuery( relation = $relationTree.view, diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/relationMappingSetup.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/relationMappingSetup.pure new file mode 100644 index 00000000000..4b1269337ba --- /dev/null +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/relationMappingSetup.pure @@ -0,0 +1,179 @@ +// Copyright 2025 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +###Relational +Database meta::relational::tests::mapping::relation::testDB +( + Table personTable + ( + ID INTEGER PRIMARY KEY, + FIRSTNAME VARCHAR(100), + AGE INTEGER, + FIRMID INTEGER, + SALARY FLOAT + ) + + Table firmTable + ( + ID INTEGER PRIMARY KEY, + LEGALNAME VARCHAR(100) + ) + + Table groupMembershipTable + ( + GROUPID INTEGER PRIMARY KEY, + PERSONID INTEGER, + GROUPNAME VARCHAR(30) + ) +) + +###Pure +Class meta::relational::tests::mapping::relation::Person +{ + firstName: String[1]; + age: Integer[1]; +} + +Class meta::relational::tests::mapping::relation::PersonWithFirmId extends meta::relational::tests::mapping::relation::Person +{ + firmId: Integer[1]; +} + +Class meta::relational::tests::mapping::relation::ExtendedPerson extends meta::relational::tests::mapping::relation::Person +{ + rank: Integer[1]; + groupName: String[1]; +} + +Class meta::relational::tests::mapping::relation::Firm +{ + legalName: String[1]; +} + +Association meta::relational::tests::mapping::relation::Person_Firm +{ + employees: meta::relational::tests::mapping::relation::Person[*]; + firm: meta::relational::tests::mapping::relation::Firm[1]; +} + +###Mapping +import meta::relational::tests::mapping::relation::*; +Mapping meta::relational::tests::mapping::relation::simpleMapping +( + *Person[person]: Relation + { + ~func meta::relational::tests::mapping::relation::personFunction__Relation_1_ + firstName: FIRSTNAME, + age: AGE, + +firmId: Integer[1]: FIRMID + } + + *Firm[firm]: Relation + { + ~func meta::relational::tests::mapping::relation::firmFunction__Relation_1_ + legalName: LEGALNAME, + +id: Integer[1]: ID + } + + Person_Firm: XStore + { + employees[firm, person]: $this.id == $that.firmId, + firm[person, firm]: $this.firmId == $that.id + } +) + +Mapping meta::relational::tests::mapping::relation::mixedMapping +( + *Person[person]: Relation + { + ~func meta::relational::tests::mapping::relation::personFunctionWithProject__Relation_1_ + firstName: FIRSTNAME, + age: AGE, + +firmId: Integer[1]: FIRMID + } + + *PersonWithFirmId: Relational + { + firstName: [meta::relational::tests::mapping::relation::testDB]personTable.FIRSTNAME, + age: [meta::relational::tests::mapping::relation::testDB]personTable.AGE, + firmId: [meta::relational::tests::mapping::relation::testDB]personTable.FIRMID + } + + *Firm[firm]: Relational + { + +id: Integer[1]: [meta::relational::tests::mapping::relation::testDB]firmTable.ID, + legalName: [meta::relational::tests::mapping::relation::testDB]firmTable.LEGALNAME + } + + Person_Firm: XStore + { + employees[firm, person]: $this.id == $that.firmId, + firm[person, firm]: $this.firmId == $that.id + } +) + +Mapping meta::relational::tests::mapping::relation::complexMapping +( + *ExtendedPerson[person]: Relation + { + ~func meta::relational::tests::mapping::relation::complexPersonFunction__Relation_1_ + firstName: FIRSTNAME, + groupName: GROUPNAME, + age: AGE, + rank: RANK + } +) + +###Pure +import meta::relational::metamodel::execute::*; +import meta::relational::metamodel::*; +import meta::pure::mapping::*; +import meta::legend::*; +import meta::pure::metamodel::relation::*; +import meta::relational::tests::mapping::relation::*; + +function meta::relational::tests::mapping::relation::personFunction(): Relation[1] +{ + #>{meta::relational::tests::mapping::relation::testDB.personTable}# + ->filter(x | $x.AGE > 25) + ->limit(5) +} + +function meta::relational::tests::mapping::relation::personFunctionWithProject(): Relation[1] +{ + PersonWithFirmId.all() + ->filter(x|$x.age > 25) + ->project( + ~[ + FIRSTNAME:x|$x.firstName, + AGE:x|$x.age, + FIRMID:x|$x.firmId + ] + ) + ->select(~[FIRSTNAME, AGE, FIRMID]) +} + +function meta::relational::tests::mapping::relation::firmFunction(): Relation[1] +{ + #>{meta::relational::tests::mapping::relation::testDB.firmTable}# + ->limit(10) + ->select(~[ID, LEGALNAME]) +} + +function meta::relational::tests::mapping::relation::complexPersonFunction(): Relation[1] +{ + #>{meta::relational::tests::mapping::relation::testDB.personTable}# + ->join(#>{meta::relational::tests::mapping::relation::testDB.groupMembershipTable}#, JoinKind.INNER, {x,y| $x.ID == $y.PERSONID}) + ->extend(over(~GROUPID, ~SALARY->ascending()), ~[RANK:{p,w,r| $p->rank($w, $r)}]); +} diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/tests.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/tests.pure new file mode 100644 index 00000000000..42243aef855 --- /dev/null +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-pure/legend-engine-xt-relationalStore-core-pure/src/main/resources/core_relational/relational/tests/mapping/relation/tests.pure @@ -0,0 +1,164 @@ +// Copyright 2025 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +###Pure +import meta::relational::metamodel::execute::*; +import meta::relational::metamodel::*; +import meta::pure::router::*; +import meta::pure::mapping::*; +import meta::pure::metamodel::relation::*; +import meta::relational::tests::mapping::relation::*; + +function <> meta::relational::tests::mapping::relation::setUp():Boolean[1] +{ + createTablesAndFillDb(); +} + +function meta::relational::tests::mapping::relation::createTablesAndFillDb():Boolean[1] +{ + let connection = meta::external::store::relational::tests::testRuntime(testDB).connectionByElement(testDB)->cast(@meta::external::store::relational::runtime::TestDatabaseConnection); + + executeInDb('Drop table if exists PersonTable;', $connection); + executeInDb('Create Table PersonTable(id INT, firstName VARCHAR(200), age INT, firmId INT, birthdate DATE, salary DOUBLE, isMale BIT);', $connection); + executeInDb('insert into PersonTable (id, firstName, age, firmId, birthDate, salary, isMale) values (1, \'Peter\', 23, 1, \'2000-11-01\', 14.34, 1);', $connection); + executeInDb('insert into PersonTable (id, firstName, age, firmId, birthDate, salary, isMale) values (2, \'John\', 30, 1, \'1994-11-01\', 72.40, 1);', $connection); + executeInDb('insert into PersonTable (id, firstName, age, firmId, birthDate, salary, isMale) values (3, \'Jane\', 23, 2, \'2000-11-01\', 48.00, 0);', $connection); + executeInDb('insert into PersonTable (id, firstName, age, firmId, birthDate, salary, isMale) values (4, \'Anthony\', 19, 3, \'2005-11-01\', 64.90, 1);', $connection); + executeInDb('insert into PersonTable (id, firstName, age, firmId, birthDate, salary, isMale) values (5, \'Fabrice\', 45, 4, \'1979-11-01\', 19.29, 1);', $connection); + executeInDb('insert into PersonTable (id, firstName, age, firmId, birthDate, salary, isMale) values (6, \'Oliver\', 26, 4, \'1998-11-01\', 42.34, 1);', $connection); + executeInDb('insert into PersonTable (id, firstName, age, firmId, birthDate, salary, isMale) values (7, \'David\', 52, 5, \'1972-11-01\', 88.88, 1);', $connection); + + executeInDb('Drop table if exists FirmTable;', $connection); + executeInDb('Create Table FirmTable(id INT, legalName VARCHAR(200), addressId INT, ceoId INT);', $connection); + executeInDb('insert into FirmTable (id, legalName, addressId, ceoId) values (1, \'Firm X\', 8, 1);', $connection); + executeInDb('insert into FirmTable (id, legalName, addressId, ceoId) values (2, \'Firm A\', 9, 5);', $connection); + executeInDb('insert into FirmTable (id, legalName, addressId, ceoId) values (3, \'Firm B\', 10, 3);', $connection); + executeInDb('insert into FirmTable (id, legalName, addressId, ceoId) values (4, \'Firm C\', 11, 7);', $connection); + executeInDb('insert into FirmTable (id, legalName, addressId, ceoId) values (5, \'Firm D\', 11, 2);', $connection); + + executeInDb('Drop table if exists GroupMembershipTable;', $connection); + executeInDb('Create Table GroupMembershipTable(groupid INT, personid INT, groupname VARCHAR(30));', $connection); + executeInDb('insert into GroupMembershipTable (groupid, personid, groupname) values (1, 1, \'Group A\');', $connection); + executeInDb('insert into GroupMembershipTable (groupid, personid, groupname) values (1, 2, \'Group A\');', $connection); + executeInDb('insert into GroupMembershipTable (groupid, personid, groupname) values (2, 3, \'Group B\');', $connection); + executeInDb('insert into GroupMembershipTable (groupid, personid, groupname) values (3, 4, \'Group C\');', $connection); + executeInDb('insert into GroupMembershipTable (groupid, personid, groupname) values (3, 5, \'Group C\');', $connection); + executeInDb('insert into GroupMembershipTable (groupid, personid, groupname) values (3, 6, \'Group C\');', $connection); + executeInDb('insert into GroupMembershipTable (groupid, personid, groupname) values (4, 7, \'Group D\');', $connection); + + true; +} + +function <> meta::relational::tests::mapping::relation::testTds(func: FunctionDefinition<{->TabularDataSet[1]}>[1], mapping: Mapping[1], expectedRows:String[*]):Boolean[1] +{ + let result = execute($func, + $mapping, + meta::external::store::relational::tests::testRuntime(meta::relational::tests::mapping::relation::testDB), + meta::relational::extension::relationalExtensions() + ).values->at(0); + + assertSameElements($expectedRows, $result.rows->map(r|$r.values->makeString(' | '))); +} + +function <> meta::relational::tests::mapping::relation::testSimpleRelationMapping():Boolean[1] +{ + testTds(|Person.all()->project([x|$x.firstName, x|$x.age], ['name', 'age']), + meta::relational::tests::mapping::relation::simpleMapping, + ['David | 52', 'Fabrice | 45', 'John | 30', 'Oliver | 26'] + ); +} + +function <> meta::relational::tests::mapping::relation::testSimpleRelationMappingWithAssociation():Boolean[1] +{ + testTds(|Person.all()->project([x|$x.firstName, x|$x.firm.legalName], ['name', 'firmName']), + meta::relational::tests::mapping::relation::simpleMapping, + ['David | Firm D', 'Fabrice | Firm C', 'John | Firm X', 'Oliver | Firm C'] + ); +} + +function <> meta::relational::tests::mapping::relation::testSimpleRelationMappingWithFilter():Boolean[1] +{ + testTds(|Firm.all()->filter(x|$x.legalName != 'Firm X')->project([x|$x.legalName, x|$x.employees.firstName], ['firmName', 'name']), + meta::relational::tests::mapping::relation::simpleMapping, + ['Firm A | TDSNull', 'Firm B | TDSNull', 'Firm C | Fabrice', 'Firm C | Oliver', 'Firm D | David'] + ); +} + +function <> meta::relational::tests::mapping::relation::testSimpleRelationMappingWithSubFilter():Boolean[1] +{ + testTds(|Person.all()->project([x|$x.firstName, x|$x.firm.employees->filter(e|$e.age < 35).firstName], ['name', 'firmName']), + meta::relational::tests::mapping::relation::simpleMapping, + ['David | TDSNull', 'Fabrice | TDSNull', 'John | John', 'Oliver | Fabrice', 'Oliver | Oliver'] + ); +} + +function <> meta::relational::tests::mapping::relation::testSimpleRelationMappingWithGroupByAndProject():Boolean[1] +{ + testTds(|Person.all()->groupBy([p|$p.firm.legalName], [agg(p|$p.age, y | $y->average())], ['name', 'averageAge']) + ->project([col(r:TDSRow[1]|$r.getString('name'), 'name'), col(r:TDSRow[1]|$r.getString('averageAge'), 'averageAge')]), + meta::relational::tests::mapping::relation::simpleMapping, + ['Firm C | 35.5', 'Firm D | 52.0', 'Firm X | 30.0'] + ); +} + +function <> meta::relational::tests::mapping::relation::testMixedMapping():Boolean[1] +{ + testTds(|Person.all()->project([x|$x.firstName, x|$x.age], ['name', 'age']), + meta::relational::tests::mapping::relation::mixedMapping, + ['David | 52', 'Fabrice | 45', 'John | 30', 'Oliver | 26'] + ); +} + +function <> meta::relational::tests::mapping::relation::testMixedMappingWithAssociation():Boolean[1] +{ + testTds(|Person.all()->project([x|$x.firstName, x|$x.firm.legalName], ['name', 'firmName']), + meta::relational::tests::mapping::relation::mixedMapping, + ['David | Firm D', 'Fabrice | Firm C', 'John | Firm X', 'Oliver | Firm C'] + ); +} + +function <> meta::relational::tests::mapping::relation::testMixedMappingWithFilter():Boolean[1] +{ + testTds(|Firm.all()->filter(x|$x.legalName != 'Firm X')->project([x|$x.legalName, x|$x.employees.firstName], ['firmName', 'name']), + meta::relational::tests::mapping::relation::mixedMapping, + ['Firm A | TDSNull', 'Firm B | TDSNull', 'Firm C | Fabrice', 'Firm C | Oliver', 'Firm D | David'] + ); +} + +// SQL Gen fails during isolation when adding a self join, need to investigate further +function <> meta::relational::tests::mapping::relation::testMixedMappingWithSubFilter():Boolean[1] +{ + testTds(|Person.all()->project([x|$x.firstName, x|$x.firm.employees->filter(e|$e.age < 35).firstName], ['name', 'firmName']), + meta::relational::tests::mapping::relation::mixedMapping, + ['David | TDSNull', 'Fabrice | TDSNull', 'John | John', 'Oliver | Fabrice', 'Oliver | Oliver'] + ); +} + +function <> meta::relational::tests::mapping::relation::testMixedMappingWithGroupByAndProject():Boolean[1] +{ + testTds(|Person.all()->groupBy([p|$p.firm.legalName], [agg(p|$p.age, y | $y->average())], ['name', 'averageAge']) + ->project([col(r:TDSRow[1]|$r.getString('name'), 'name'), col(r:TDSRow[1]|$r.getString('averageAge'), 'averageAge')]), + meta::relational::tests::mapping::relation::mixedMapping, + ['Firm C | 35.5', 'Firm D | 52.0', 'Firm X | 30.0'] + ); +} + +function <> meta::relational::tests::mapping::relation::testComplexRelationMapping():Boolean[1] +{ + testTds(|ExtendedPerson.all()->filter(x|$x.age > 25)->project([x|$x.firstName, x|$x.groupName, x|$x.rank], ['name', 'groupName', 'rank']), + meta::relational::tests::mapping::relation::complexMapping, + ['David | Group D | 1', 'Fabrice | Group C | 1', 'John | Group A | 2', 'Oliver | Group C | 2'] + ); +} + diff --git a/pom.xml b/pom.xml index b8aaf6e6bd2..5bad9f9a5c7 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,7 @@ - 5.28.0 + 5.29.0 0.25.7 13.2.0