Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support mapping relations to classes #3306

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
<groupId>org.finos.legend.pure</groupId>
<artifactId>legend-pure-m3-core</artifactId>
</dependency>
<dependency>
<groupId>org.finos.legend.pure</groupId>
<artifactId>legend-pure-m2-dsl-mapping-pure</artifactId>
</dependency>

<!-- Pure Grammar -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public Client(MutableList<ReplExtension> replExtensions, MutableList<CompleterEx
new Debug(this),
new Doc(this),
new Graph(this),
new SnapRelationToClass(this),
new Execute(this)
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
package org.finos.legend.engine.repl.client;

import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.map.MutableMap;
import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel;
import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData;
import org.finos.legend.engine.repl.core.ReplExtension;
import org.finos.legend.engine.repl.core.legend.LegendInterface;
Expand All @@ -25,7 +28,8 @@ public class ModelState
{
private final LegendInterface legendInterface;
private final MutableList<ReplExtension> replExtensions;
private MutableList<String> state = Lists.mutable.empty();
private final MutableList<String> state = Lists.mutable.empty();
private final MutableMap<String, String> namedState = Maps.mutable.empty();

public ModelState(LegendInterface legendInterface, MutableList<ReplExtension> replExtensions)
{
Expand All @@ -39,19 +43,40 @@ public ModelState addElement(String element)
return this;
}

public ModelState addNamedElement(String name, String element)
{
this.namedState.put(name, element);
return this;
}

public String getNamedElement(String name)
{
return this.namedState.get(name);
}

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);
}

public String getText()
{
String code = Lists.mutable.withAll(state).makeString("\n");
String code = Lists.mutable.withAll(state).withAll(namedState.values()).makeString("\n");
return code + replExtensions.flatCollect(r -> r.generateDynamicContent(code)).makeString("\n");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2024 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.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.m3.coreinstance.meta.pure.mapping.Mapping;
import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.FunctionDefinition;
import org.finos.legend.pure.m3.execution.ExecutionSupport;
import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;

public class SnapRelationToClass implements Command
{
private final Client client;

public SnapRelationToClass(Client client)
{
this.client = client;
}

@Override
public boolean process(String line) throws Exception
{
if (line.startsWith("snapRelationToClass"))
{
String[] tokens = line.split(" ");
if (tokens.length < 1 || tokens.length > 4)
{
throw new RuntimeException("Command should be used as '" + this.documentation() + "'");
}

String functionName = tokens[1];
String className = tokens[2];
String mappingName = tokens[3];

String expression = this.client.getLastCommand(1);
if (expression == null)
{
this.client.printError("Failed to retrieve the last command");
return true;
}

try
{
String functionBody = "###Pure\n" +
"function " + functionName + "():Any[1]\n" +
"{" +
expression + ";\n" +
"}\n";
String functionPath = functionName + "__Any_1_";
PureModel model = client.getModelState().compileWithTransient(functionBody);
ExecutionSupport executionSupport = model.getExecutionSupport();
FunctionDefinition<?> relationFunction = model.getConcreteFunctionDefinition_safe(functionPath);

String classBody = RelationClassMappingGenerator.generateClass(relationFunction, className, executionSupport);
String mappingBody = RelationClassMappingGenerator.generateClassMapping(relationFunction, className, functionPath, executionSupport);

Mapping mappingFromState = model.getMapping_safe(mappingName);
String generatedMapping = client.getModelState().getNamedElement(mappingName);
if (generatedMapping != null)
{
int lastClosingBracketPosition = generatedMapping.lastIndexOf(")");
String newMapping = generatedMapping.substring(0, lastClosingBracketPosition) + mappingBody + ")\n";
client.getModelState().addNamedElement(mappingName, newMapping);
}
else if (mappingFromState != null)
{
throw new RuntimeException("Existing mapping " + mappingName + "can't re-used for this feature. Need to generate a new mapping with the provided name.");
}
else
{
String mapping = "###Mapping\n" +
"Mapping " + mappingName + "\n" +
"(\n" +
mappingBody +
")\n";
client.getModelState().addNamedElement(mappingName, mapping);
}
client.getModelState().addElement(classBody);
client.getModelState().addElement(functionBody);
client.getModelState().parse();
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;
}

@Override
public String documentation()
{
return "snapRelationToClass <function name> <class name> <mapping name>";
}

@Override
public String description()
{
return "generate model class and corresponding relation expression mapping for last run relation expression. Example Usage: snapRelationToClass my::firmFn my::Firm my::testMapping";
}

@Override
public MutableList<Candidate> complete(String cmd, LineReader lineReader, ParsedLine parsedLine)
{
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2024 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.pure.generated.core_pure_corefunctions_stringExtension;
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.generics.GenericType;
import org.finos.legend.pure.m3.execution.ExecutionSupport;
import org.finos.legend.pure.m3.navigation.multiplicity.Multiplicity;
import org.finos.legend.pure.m3.navigation.relation._Column;

public class RelationClassMappingGenerator
{
public static RelationType<?> getRelationType(FunctionDefinition<?> relationFunction)
{
GenericType lastExpressionType = relationFunction._expressionSequence().toList().getLast()._genericType();
return (RelationType<?>) lastExpressionType._typeArguments().toList().getFirst()._rawType();
}

public static String generateClass(FunctionDefinition<?> relationFunction, String className, ExecutionSupport es)
{
RelationType<?> relationType = getRelationType(relationFunction);
return generateClass(relationType, className, es);
}

private static String getColumnPureType(Column<?, ?> c)
{
return _Column.getColumnType(c)._rawType()._name();
}

private static String getColumnPureMultiplicity(Column<?, ?> c)
{
return Multiplicity.print(_Column.getColumnMultiplicity(c));
}

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 generateClass(RelationType<?> relationType, String className, ExecutionSupport es)
{
StringBuilder properties = new StringBuilder();
relationType._columns().collect(c -> "\t" + getPropertyName(c._name(), es) + ": " + getColumnPureType(c) + getColumnPureMultiplicity(c) + ";\n").appendString(properties, "");

return "###Pure\n" +
"Class " + className + "\n" +
"{\n" +
properties +
"}\n";
}

public static String generateClassMapping(FunctionDefinition<?> relationFunction, String className, String functionName, ExecutionSupport es)
{
RelationType<?> relationType = getRelationType(relationFunction);
return generateClassMapping(relationType, className, functionName, es);
}

public static String generateClassMapping(RelationType<?> relationType, String className, String functionName, ExecutionSupport es)
{
StringBuilder propertyMappings = new StringBuilder();
relationType._columns().collect(c -> "\t" + getPropertyName(c._name(), es) + ": " + c._name()).appendString(propertyMappings, ",\n");

return className + ": Relation\n" +
"{\n" +
" ~func " + functionName + "\n" +
propertyMappings + "\n" +
"}\n";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import org.eclipse.collections.api.tuple.Pair;
import org.eclipse.collections.impl.tuple.Tuples;
import org.eclipse.collections.impl.utility.ListIterate;
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.domain.Multiplicity;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.ClassMapping;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.ClassMappingVisitor;
Expand All @@ -29,27 +27,32 @@
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;
import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.Variable;
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;
import org.finos.legend.pure.m3.coreinstance.meta.pure.mapping.InstanceSetImplementation;
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;

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<Pair<SetImplementation, RichIterable<EmbeddedSetImplementation>>>
{
private final CompileContext context;
Expand Down Expand Up @@ -166,4 +169,21 @@ public Pair<SetImplementation, RichIterable<EmbeddedSetImplementation>> visit(Ag
}
return Tuples.pair(res, Lists.immutable.empty());
}

@Override
public Pair<SetImplementation, RichIterable<EmbeddedSetImplementation>> 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());
}
}
Loading
Loading