generated from finos/software-project-blueprint
-
Notifications
You must be signed in to change notification settings - Fork 237
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
support mapping relations to classes
- Loading branch information
1 parent
b47114a
commit b6eff6f
Showing
58 changed files
with
2,202 additions
and
273 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
...ient/src/main/java/org/finos/legend/engine/repl/core/commands/PromoteRelationToModel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <relation name> <package prefix>"; | ||
} | ||
|
||
@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<Candidate> complete(String cmd, LineReader lineReader, ParsedLine parsedLine) | ||
{ | ||
return null; | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
...ient/src/main/java/org/finos/legend/engine/repl/shared/RelationClassMappingGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.