core.pure.mapping.modelToModel.pure Maven / Gradle / Ivy
// Copyright 2020 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.
import meta::pure::metamodel::treepath::*;
import meta::pure::extension::*;
import meta::pure::dataQuality::*;
import meta::pure::graphFetch::*;
import meta::pure::graphFetch::routing::*;
import meta::pure::graphFetch::execution::*;
import meta::pure::graphFetch::executionPlan::*;
import meta::pure::executionPlan::toString::*;
import meta::pure::executionPlan::*;
import meta::pure::mapping::modelToModel::chain::*;
import meta::pure::mapping::modelToModel::inMemory::*;
import meta::external::store::model::*;
import meta::pure::mapping::modelToModel::graphFetch::executionPlan::*;
import meta::core::runtime::*;
import meta::pure::router::store::metamodel::*;
import meta::pure::router::store::metamodel::clustering::*;
import meta::pure::mapping::*;
import meta::pure::executionPlan::engine::java::*;
import meta::json::*;
Class meta::external::store::model::ModelStore extends meta::pure::store::Store
{
toString(){'ModelStore'}:String[1];
}
// TODO Is there a better way of breaking ModelConnection into Instances/Json
Class meta::external::store::model::PureModelConnection extends Connection
{
}
Class meta::external::store::model::ModelConnection extends PureModelConnection
{
instances : Map, List>[1];
}
Class meta::external::store::model::ModelQueryConnection extends PureModelConnection
{
instancesProvider : Map, FunctionDefinition<{RootGraphFetchTree[1] -> Any[*]}>>[1];
}
// Only supported for Alloy
Class meta::external::store::model::JsonModelConnection extends PureModelConnection
{
class: Class[1];
url : String[1];
}
Class meta::pure::mapping::modelToModel::JsonDataRecord
{
number : Integer[1];
record : String[1];
}
Class meta::external::store::model::XmlModelConnection extends PureModelConnection
{
class: Class[1];
url : String[1];
}
Class meta::pure::mapping::modelToModel::XmlDataRecord
{
number : Integer[1];
record : String[1];
}
Class meta::external::store::model::ModelChainConnection extends Connection
{
mappings : Mapping[*];
}
Class meta::pure::mapping::M2MEmbeddedSetImplementation extends EmbeddedSetImplementation
{
}
Class meta::external::store::model::ModelToModelExecutionNode extends ExecutionNode
{
fd: FunctionDefinition<{->Any[*]}>[1];
classes:Class[*];
jsonPropertyPaths : LambdaFunction<{Nil[1]->Any[*]}>[*];
connection: Connection[1];
mapping: Mapping[1];
}
Class <> meta::external::store::model::GraphFetchM2MExecutionNode extends ExecutionNode
{
trees: RoutedGraphFetchTree[*];
connections: Connection[*];
mapping: Mapping[1];
enableConstraints: Boolean[1];
}
Class meta::external::store::model::JsonFeWithPropertyPaths
{
fe: FunctionExpression[1];
jsonPropertyPaths : LambdaFunction<{Nil[1]->Any[*]}>[*];
}
function meta::pure::mapping::modelToModel::inMemory::isNoMappingDefaultToEmpty(setImpl:SetImplementation[1], property:AbstractProperty[1]):Boolean[1]
{
!$property.multiplicity->hasLowerBound() && $property.owner->instanceOf(Class);
}
function meta::pure::mapping::modelToModel::inMemory::isPartOfMerge(setImpl:SetImplementation[1]):Boolean[1]
{
$setImpl.parent.classMappings->filter(m|$m->instanceOf(MergeOperationSetImplementation))->cast(@MergeOperationSetImplementation)->filter(o| $setImpl.id->in($o.parameters.id))->isNotEmpty();
}
function meta::pure::mapping::modelToModel::inMemory::noMappingPassThruSourceProperty(setImpl:SetImplementation[1], property:AbstractProperty[1]):AbstractProperty[0..1]
{
$setImpl->match([
{pisi:PureInstanceSetImplementation[1] |
let propertyType = $property.genericType.rawType->toOne();
let srcProperty = $pisi.srcClass->filter(c| $c->instanceOf(Class))->cast(@Class)->map(c | $c->propertyByName($property.name->toOne()));
let valid = $propertyType->instanceOf(DataType) && $srcProperty->match([
s:Property[1] | $s.genericType.rawType->toOne()->_subTypeOf($propertyType) && $property.multiplicity->multiplicitySubsumes($s.multiplicity),
a:Any[*] | false
]);
if($valid, |$srcProperty->toOne(), |[]);
},
si: SetImplementation[1] | [];
]);
}
function meta::pure::mapping::modelToModel::findMainClassInGetAllExpression(vs:ValueSpecification[1]):Class[1]
{
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)->byPassRouterInfo()->cast(@InstanceValue).values->toOne()->cast(@Class));
}
function meta::external::store::model::planExecutionPure(sq:meta::pure::mapping::StoreQuery[1], ext:RoutedValueSpecification[0..1], m:Mapping[0..1], runtime:Runtime[0..1], exeCtx:meta::pure::runtime::ExecutionContext[1], extensions:meta::pure::extension::Extension[*], debug:DebugContext[1]):ExecutionNode[1]
{
let connection = $runtime->toOne()->connectionByElement($sq.store);
$connection->match([
mc: ModelConnection[1]| planExecutionInMemory($sq, $ext->cast(@StoreMappingRoutedValueSpecification), $m, $mc, $runtime, $exeCtx, $extensions, $debug),
mcc: ModelChainConnection[1]| planExecutionChain($sq, $ext->cast(@StoreMappingRoutedValueSpecification), $m, $mcc, $runtime, $exeCtx, $extensions, $debug)
]);
}
function meta::pure::mapping::modelToModel::plan::modelToModelPlanNodeToString(c:ModelToModelExecutionNode[1], space:String[1], extensions:meta::pure::extension::Extension[*]):String[1]
{
'M2M\n'+$space+'('+header($c, $space, $extensions)+'\n'+$space+ ' connection = '+$c.connection->meta::pure::executionPlan::toString::connectionToString($extensions)+'\n' + if($c.jsonPropertyPaths->isNotEmpty(),| ' propertyPath = '+$c.jsonPropertyPaths->buildPropertyPathTree()->toJSON()+'\n',|'') + $space+')\n'
}
function meta::external::store::model::modelToModelConnectionToString(c:Connection[1], extensions:meta::pure::extension::Extension[*]):String[1]
{
$c->match([mc:ModelConnection[1] | 'instances = "' + $mc.instances->keyValues()->map(p|$p.second.values->map(v|pair($p.first->elementToPath(), $v->toJSON())))->makeString('{',',','}'),
jmc:JsonModelConnection[1] | 'url = ' + $jmc.url + ', class = ' + $jmc.class->elementToPath(),
xmc:XmlModelConnection[1] | 'url = ' + $xmc.url + ', class = ' + $xmc.class->elementToPath(),
mcc:ModelChainConnection[1] | fail('ModelChainConnection not supported in Execution Plan');'';])
}
// Strategic in memory graph fetch flow
###Pure
import meta::pure::router::metamodel::clustering::*;
import meta::pure::router::platform::metamodel::clustering::*;
import meta::pure::dataQuality::*;
import meta::pure::executionPlan::*;
import meta::pure::executionPlan::toString::*;
import meta::pure::graphFetch::*;
import meta::pure::graphFetch::execution::*;
import meta::pure::graphFetch::executionPlan::*;
import meta::pure::graphFetch::routing::*;
import meta::pure::mapping::*;
import meta::external::store::model::*;
import meta::pure::mapping::modelToModel::graphFetch::executionPlan::*;
import meta::pure::mapping::xStore::*;
import meta::pure::milestoning::*;
import meta::pure::router::clustering::*;
import meta::pure::extension::*;
import meta::pure::router::routing::*;
import meta::pure::router::store::metamodel::*;
import meta::pure::router::store::metamodel::clustering::*;
import meta::core::runtime::*;
import meta::pure::store::*;
Class meta::pure::mapping::modelToModel::graphFetch::executionPlan::StoreStreamReadingExecutionNode extends ExecutionNode
{
graphFetchTree : RootGraphFetchTree[1];
store : Store[0..1];
connection : Connection[1];
enableConstraints : Boolean[1];
checked : Boolean[1];
}
Class <> meta::pure::mapping::modelToModel::graphFetch::executionPlan::InMemoryGraphFetchExecutionNode extends LocalGraphFetchExecutionNode
{
children : InMemoryGraphFetchExecutionNode[*];
}
Class meta::pure::mapping::modelToModel::graphFetch::executionPlan::InMemoryPropertyGraphFetchExecutionNode extends InMemoryGraphFetchExecutionNode
{
}
Class meta::pure::mapping::modelToModel::graphFetch::executionPlan::InMemoryRootGraphFetchExecutionNode extends InMemoryGraphFetchExecutionNode
{
batchSize : Integer[0..1];
checked : Boolean[1];
filter : LambdaFunction[0..1];
}
Class meta::pure::mapping::modelToModel::graphFetch::executionPlan::InMemoryCrossStoreGraphFetchExecutionNode extends InMemoryRootGraphFetchExecutionNode
{
supportsBatching : Boolean[1];
xStorePropertyMapping : meta::pure::mapping::xStore::XStorePropertyMapping[1];
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::planInMemoryGraphFetchExecution(sq: StoreQuery[1], ext: RoutedValueSpecification[0..1], mapping: Mapping[1], runtime: Runtime[1], exeCtx: meta::pure::runtime::ExecutionContext[1], extensions: Extension[*], debug: DebugContext[1]): ExecutionNode[1]
{
let fe = $sq.vs->evaluateAndDeactivate()->cast(@FunctionExpression);
if ($fe->meta::pure::router::utils::isUnionOnGraphFetch(true),
| planRouterUnionGraphFetchExecution($sq, $ext, $mapping->toOne(), $runtime->toOne(), $exeCtx, $extensions, $debug),
| assert($fe.func->in(graphFetchFunctions()), | 'Non graphFetch function encountered');
let sets = $fe.parametersValues->evaluateAndDeactivate()->at(1)->cast(@InstanceValue).values->at(0)->cast(@ClusteredGraphFetchTree).tree->cast(@RoutedRootGraphFetchTree).sets;
let isUnion = ($sets->size() > 1) || ($sets->toOne()->instanceOf(OperationSetImplementation) && $sets->toOne()->cast(@OperationSetImplementation).operation == meta::pure::router::operations::union_OperationSetImplementation_1__SetImplementation_MANY_);
let isMerge = ($sets->size() > 1) || ($sets->toOne()->instanceOf(OperationSetImplementation) && $sets->toOne()->cast(@OperationSetImplementation).operation == meta::pure::router::operations::merge_OperationSetImplementation_1__SetImplementation_MANY_);
if ($isUnion ||$isMerge,
| let resolvedSets = if($sets->size() > 1, | $sets, | $sets->toOne()->cast(@OperationSetImplementation)->meta::pure::router::routing::resolveOperation($mapping->toOne()))->cast(@InstanceSetImplementation);
if($isUnion,
|planStoreUnionGraphFetchExecution($sq, $ext, $resolvedSets, $mapping->toOne(), $runtime->toOne(), $exeCtx, $extensions, $debug),
|planInMemoryStoreMergeGraphFetchExecution($sq, $ext, $resolvedSets, $mapping->toOne(), $runtime->toOne(), $exeCtx, $extensions, $debug)
);,
| planGraphFetchExecution($sq, $ext, $mapping->toOne(), $runtime->toOne(), $exeCtx, $extensions, $debug)
);
);
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::planRootGraphFetchExecutionInMemory(sq: StoreQuery[1], ext: RoutedValueSpecification[0..1], clusteredTree: StoreMappingClusteredGraphFetchTree[1], orderedPaths: String[*], mapping: Mapping[1], runtime: Runtime[1], exeCtx: meta::pure::runtime::ExecutionContext[1], enableConstraints: Boolean[1], checked: Boolean[1], extensions: meta::pure::extension::Extension[*], debug: DebugContext[1]): InMemoryRootGraphFetchExecutionNode[1]
{
let store = $sq.store;
let fe = $sq.vs->evaluateAndDeactivate()->cast(@FunctionExpression);
let rootTree = $clusteredTree->byPassClusteringInfo()->cast(@RoutedRootGraphFetchTree);
let batchSize = if($fe.func == graphFetch_T_MANY__RootGraphFetchTree_1__Integer_1__T_MANY_ || $fe.func == graphFetchChecked_T_MANY__RootGraphFetchTree_1__Integer_1__Checked_MANY_,
| $fe->instanceValuesAtParameter(2, $sq.inScopeVars)->toOne()->cast(@Integer),
| 1);
let connection = if ($sq.store->instanceOf(ModelStore),
| let sourceClass = $rootTree.sets->toOne()->cast(@PureInstanceSetImplementation).srcClass->toOne();
$runtime.connectionStores->filter(c|$c.element->instanceOf(ModelStore) && $c.connection->match([
j : JsonModelConnection[1] | $j.class == $sourceClass,
x : XmlModelConnection[1] | $x.class == $sourceClass,
m : ModelConnection[1] | $m.instances->get($sourceClass)->isNotEmpty(),
m : ModelQueryConnection[1] | $m.instancesProvider->get($sourceClass)->isNotEmpty(),
m : ModelChainConnection[1] | false
])).connection;,
| $runtime->connectionByElement($store);
);
assert($connection->size() <= 1, | 'Multiple connections available for source class - ' + $rootTree.sets->toOne()->cast(@PureInstanceSetImplementation).srcClass.name->toOne());
let chainConnection = if ($sq.store->instanceOf(ModelStore),
| $runtime.connectionStores->filter(c|$c.element->instanceOf(ModelStore) && $c.connection->instanceOf(ModelChainConnection)).connection;,
| []
);
assert($chainConnection->size() <= 1, | 'Multiple ModelChainConnection\'s not supported yet! Please merge your ModelChainConnection\'s.');
assert($connection->concatenate($chainConnection)->isNotEmpty(), | 'No connection available for source class - ' + $rootTree.sets->toOne()->cast(@PureInstanceSetImplementation).srcClass.name->toOne());
let updatedConnection = $connection->concatenate($chainConnection)->first()->toOne();
let sourceTree = calculateSourceTree($rootTree, $mapping, $extensions);
let sourceTreeWithConstraint = if($enableConstraints, | $sourceTree->ensureConstraintsRequirements(), | $sourceTree);
let possibleFilter = $fe->graphFetchFilter()->evaluateAndDeactivate();
let includeFilter = $possibleFilter->isNotEmpty()&& $chainConnection ->isEmpty(); // On chain connections, the filter gets pushed to the target
let sourceTreeExtended = if($includeFilter,
| meta::pure::graphFetch::ensureFunctionRequirementsFromLambda($sourceTreeWithConstraint->cast(@GraphFetchTree), $sourceTreeWithConstraint.class, $possibleFilter.parametersValues->at(1)->cast(@FunctionRoutedValueSpecification).originalFunction,[], true),
|$sourceTreeWithConstraint
)->cast(@RootGraphFetchTree);
let chainConnectionNodeGeneration = {mcc: ModelChainConnection[1] |
$fe->planModelChainConnectionGraphFetchExecution($mcc, $sourceTreeExtended, $mapping, $runtime, $exeCtx, $ext, $sq.inScopeVars, [], $enableConstraints, $checked, $extensions, $debug)
};
let modelQueryConnectionNodeGeneration = {mc: ModelQueryConnection[1] |
planModelQueryConnectionGraphFetchExecution($mc, $store, $sourceTreeExtended, $exeCtx, $sq.inScopeVars, $enableConstraints, $checked, $extensions, $debug)
};
let defaultGenerateExecutionNode = {c:Connection[1]|
^StoreStreamReadingExecutionNode
(
resultType = ^PartialClassResultType
(
type = $sourceTreeExtended->typeFromGraphFetchTree(),
propertiesWithParameters = $sourceTreeExtended.subTrees->cast(@PropertyGraphFetchTree)->map(x | $x->map(x | ^PropertyWithParameters(property = $x.property, parameters = $x.parameters)))
),
graphFetchTree = $sourceTreeExtended,
store = $store,
connection = $updatedConnection,
enableConstraints = $enableConstraints,
checked = $checked
)
};
^InMemoryRootGraphFetchExecutionNode
(
resultType = $rootTree->resultTypeFromGraphFetchTree(),
nodeIndex = 0,
graphFetchTree = $rootTree,
batchSize = $batchSize,
checked = $checked,
children = $rootTree->generateInMemoryChildGraphNodes($rootTree->nodePathName(), $orderedPaths, $debug),
executionNodes = $updatedConnection->meta::pure::executionPlan::nodeFromConnection($sourceTreeExtended, $enableConstraints, $checked, $extensions, [$chainConnectionNodeGeneration, $modelQueryConnectionNodeGeneration, $defaultGenerateExecutionNode]),
filter = if($includeFilter,| $possibleFilter->toOne().parametersValues->at(1)->cast(@FunctionRoutedValueSpecification).originalFunction->cast(@LambdaFunction),|[]),
requiredVariableInputs = $sq.inScopeVars->keyValues()->filter(v|$v.second.values->at(0)->instanceOf(PlanVarPlaceHolder))
->map(var | let placeholder = $var.second.values->at(0)->cast(@PlanVarPlaceHolder);
^VariableInput(name = $var.first,
type = $placeholder.type,
multiplicity = $placeholder.multiplicity->toOne());
)
);
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::graphFetchFilter(fe:FunctionExpression[1]):SimpleFunctionExpression[0..1]
{
let func = $fe.parametersValues->at(0)->cast(@StoreMappingRoutedValueSpecification).value;
if($func->instanceOf(SimpleFunctionExpression) && $func->cast(@SimpleFunctionExpression).functionName=='filter',
| $func->cast(@SimpleFunctionExpression),
| []);
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::planCrossStoreGraphFetchExecutionInMemory(clusteredTree: StoreMappingClusteredGraphFetchTree[1], orderedPaths: String[*], parentPath: String[1], inScopeVars: Map>[1], mapping: Mapping[1], runtime: Runtime[1], exeCtx: meta::pure::runtime::ExecutionContext[1], enableConstraints: Boolean[1], checked: Boolean[1], extensions: Extension[*], debug: DebugContext[1]): InMemoryCrossStoreGraphFetchExecutionNode[1]
{
let store = $clusteredTree.store;
let rootTree = $clusteredTree->byPassClusteringInfo()->cast(@RoutedPropertyGraphFetchTree);
let updatedRootTree = ^RoutedRootGraphFetchTree
(
subTrees = $rootTree.subTrees,
class = $rootTree->typeFromGraphFetchTree()->cast(@Class),
sets = $rootTree.sets,
requiredQualifiedProperties = $rootTree.requiredQualifiedProperties
);
let rootPath = $parentPath + '.' + $rootTree->nodePathName();
/* Assertions */
assert($parentPath->in($orderedPaths), | 'Unknown path ' + $parentPath + '; known are: ' + $orderedPaths->joinStrings('[', '; ', ']'));
assert($rootPath->in($orderedPaths), | 'Unknown path ' + $rootPath + '; known are: ' + $orderedPaths->joinStrings('[', '; ', ']'));
assertFalse($checked, | 'graphFetchChecked is not supported with M2M cross store');
let parentIdx = $orderedPaths->indexOf($parentPath);
let currentIdx = $orderedPaths->indexOf($rootPath);
let xStorePropertyMapping = $rootTree.propertyMapping->toOne()->cast(@XStorePropertyMapping);
let connection = if ($store->instanceOf(ModelStore),
| let sourceClass = $rootTree.sets->toOne()->cast(@PureInstanceSetImplementation).srcClass->toOne();
$runtime.connectionStores->filter(c|$c.element->instanceOf(ModelStore) && $c.connection->match([
j : JsonModelConnection[1] | $j.class == $sourceClass,
x : XmlModelConnection[1] | $x.class == $sourceClass,
m : ModelConnection[1] | $m.instances->get($sourceClass)->isNotEmpty(),
m : ModelQueryConnection[1] | $m.instancesProvider->get($sourceClass)->isNotEmpty(),
m : ModelChainConnection[1] | false
])).connection;,
| $runtime->connectionByElement($store)
);
assert($connection->size() <= 1, | 'Multiple connections available for source class - ' + $rootTree.sets->toOne()->cast(@PureInstanceSetImplementation).srcClass.name->toOne());
let chainConnection = if ($store->instanceOf(ModelStore),
| $runtime.connectionStores->filter(c|$c.element->instanceOf(ModelStore) && $c.connection->instanceOf(ModelChainConnection)).connection;,
| []
);
assert($chainConnection->size() <= 1, | 'Multiple ModelChainConnection\'s not supported yet! Please merge your ModelChainConnection\'s.');
assert($connection->concatenate($chainConnection)->isNotEmpty(), | 'No connection available for source class - ' + $rootTree.sets->toOne()->cast(@PureInstanceSetImplementation).srcClass.name->toOne());
let updatedConnection = $connection->concatenate($chainConnection)->first()->toOne();
let sourceTree = calculateSourceTree($updatedRootTree, $mapping, $extensions);
let sourceTreeExtended = if($enableConstraints, | $sourceTree->ensureConstraintsRequirements(), | $sourceTree);
let modelQueryConnectionNodeGeneration = {mc: ModelQueryConnection[1] |
planModelQueryConnectionGraphFetchExecution($mc, $store, $sourceTreeExtended, $exeCtx, $inScopeVars, $enableConstraints, $checked, $extensions, $debug)
};
let populatedXStorePropertiesInScope = $xStorePropertyMapping->getPopulatedXStorePropertiesInScope($rootTree->typeFromGraphFetchTree()->cast(@Class));
let chainConnectionNodeGeneration = {mcc: ModelChainConnection[1] |
let getAllExpr = $updatedRootTree->createGetAllApplicationForRootGraphFetchTree();
let genericType = $getAllExpr.genericType;
let newGraphFetchExpr = ^SimpleFunctionExpression
(
func = if($checked, | meta::pure::graphFetch::execution::graphFetchChecked_T_MANY__RootGraphFetchTree_1__Checked_MANY_, | meta::pure::graphFetch::execution::graphFetch_T_MANY__RootGraphFetchTree_1__T_MANY_),
functionName = if($checked, | meta::pure::graphFetch::execution::graphFetchChecked_T_MANY__RootGraphFetchTree_1__Checked_MANY_.name, | meta::pure::graphFetch::execution::graphFetch_T_MANY__RootGraphFetchTree_1__T_MANY_.name),
importGroup = system::imports::coreImport,
genericType = if($checked, | ^GenericType(rawType = Checked, typeArguments = [$genericType]), | $genericType),
multiplicity = ZeroMany,
parametersValues = [
$getAllExpr,
^InstanceValue(values = $updatedRootTree, multiplicity = PureOne, genericType = ^GenericType(rawType = RootGraphFetchTree, typeArguments = [$genericType]))
]
)->evaluateAndDeactivate();
$newGraphFetchExpr->planModelChainConnectionGraphFetchExecution($mcc, $sourceTreeExtended, $mapping, $runtime, $exeCtx, [], $inScopeVars, $populatedXStorePropertiesInScope, $enableConstraints, $checked, $extensions, $debug);
};
let defaultGenerateExecutionNode = {c:Connection[1]|
^StoreStreamReadingExecutionNode
(
resultType = ^PartialClassResultType
(
type = $sourceTreeExtended->typeFromGraphFetchTree(),
propertiesWithParameters = $sourceTreeExtended.subTrees->cast(@PropertyGraphFetchTree)->map(x | $x->map(x | ^PropertyWithParameters(property = $x.property, parameters = $x.parameters)))
),
graphFetchTree = $sourceTreeExtended,
store = $store,
connection = $updatedConnection,
enableConstraints = $enableConstraints,
checked = $checked
)
};
^InMemoryCrossStoreGraphFetchExecutionNode
(
resultType = $rootTree->resultTypeFromGraphFetchTree(),
parentIndex = $parentIdx,
nodeIndex = $currentIdx,
graphFetchTree = $rootTree,
checked = $checked,
xStorePropertyMapping = $xStorePropertyMapping,
supportsBatching = $updatedConnection->sourceSupportsBatching($updatedRootTree.class, $mapping, $populatedXStorePropertiesInScope, $extensions),
children = $rootTree->generateInMemoryChildGraphNodes($rootPath, $orderedPaths, $debug),
executionNodes = $updatedConnection->meta::pure::executionPlan::nodeFromConnection($sourceTreeExtended, $enableConstraints, $checked, $extensions, [$chainConnectionNodeGeneration, $modelQueryConnectionNodeGeneration, $defaultGenerateExecutionNode])
);
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::getPopulatedXStorePropertiesInScope(pm: XStorePropertyMapping[1], rootClass:Class[1]):AbstractProperty[*]
{
$pm.crossExpression.expressionSequence->evaluateAndDeactivate()->toOne()->findPropertiesInValueSpecification()->filter(p | $p.owner == $rootClass || $p.owner->cast(@Class)->meta::pure::functions::meta::_subTypeOf($rootClass));
}
function <> meta::pure::mapping::modelToModel::graphFetch::executionPlan::planModelChainConnectionGraphFetchExecution(fe: FunctionExpression[1], mcc: ModelChainConnection[1], sourceTreeExtended: RootGraphFetchTree[1], mapping: Mapping[1], runtime: Runtime[1], exeCtx: meta::pure::runtime::ExecutionContext[1], ext: RoutedValueSpecification[0..1], inScopeVars: Map>[1], populatedXStorePropertiesInScope:AbstractProperty[*], enableConstraints: Boolean[1], checked: Boolean[1], extensions: Extension[*], debug: DebugContext[1]): ExecutionNode[1]
{
let functionParams = $fe.parametersValues->evaluateAndDeactivate();
let leftExpr = $functionParams->at(0)->byPassRouterInfo()->cast(@FunctionExpression);
let extraParams = if($functionParams->size() >= 2, | $functionParams->drop(2), | []);
let processedLeftExpr = $leftExpr->meta::pure::mapping::modelToModel::chain::allReprocess([], $mapping, $extensions, $debug);
let setsProcessed = $processedLeftExpr.ex.sets->filter(set | $set->instanceOf(PureInstanceSetImplementation))->cast(@PureInstanceSetImplementation);
let genericType = $processedLeftExpr.res.genericType;
let sourceTreeType = ^GenericType(rawType = RootGraphFetchTree, typeArguments = [$genericType]);
let newGraphFetchExpr = ^$fe
(
genericType = if($checked, | ^GenericType(rawType = Checked, typeArguments = [$genericType]), | $genericType),
parametersValues = [
$processedLeftExpr.res,
^InstanceValue(values = $sourceTreeExtended, multiplicity = PureOne, genericType = $sourceTreeType)
]->concatenate($extraParams)
);
let newFunction = ^LambdaFunction<{->Any[*]}>(expressionSequence = $newGraphFetchExpr);
let nonMCCs = $runtime.connectionStores->filter(x | !$x.connection->instanceOf(ModelChainConnection));
let newRuntime = ^$runtime
(
connectionStores = if ($mcc.mappings->size() >= 2,
| $runtime.connectionStores->filter(c|$c.connection==$mcc)->map(c|^$c(connection=^$mcc(mappings = $mcc.mappings->tail())))->concatenate($nonMCCs),
| $nonMCCs
)
);
let routed = $newFunction->routeFunction($mcc.mappings->at(0), $newRuntime, $exeCtx, $extensions, $debug);
let routedFn = $routed->evaluateAndDeactivate();
let clusters = $routedFn.expressionSequence->evaluateAndDeactivate()->match([
sm: StoreMappingClusteredValueSpecification[*] | $sm->map(c | $c->updateClusterWithChainProcessingInfo($ext, $setsProcessed)),
p : PlatformClusteredValueSpecification[1] | $p,
c : ClusteredValueSpecification[*] | fail('Unsupported model chain connection element %s in graph fetch execution', [$c->type()->elementToPath()]); $c;
]);
let fnParams = $newFunction->stubFuncParameters();
let varsFromXStoreProperties = $populatedXStorePropertiesInScope->map(prop | let varName = $prop->propertyToVarName();
pair($varName, ^List(values=$prop)););
let updatedInScopeVars = $inScopeVars->putAll($fnParams->map(ep|pair($ep.name, ^List(values=$ep)))->concatenate($varsFromXStoreProperties));
meta::pure::executionPlan::executionPlan($clusters, $routedFn, $newFunction, $mcc.mappings->at(0), $newRuntime, $exeCtx, $updatedInScopeVars, $extensions, $debug).rootExecutionNode;
}
function <> meta::pure::mapping::modelToModel::graphFetch::executionPlan::planModelQueryConnectionGraphFetchExecution(mc: ModelQueryConnection[1], store:Store[1], sourceTree: RootGraphFetchTree[1], exeCtx: meta::pure::runtime::ExecutionContext[1], inScopeVars: Map>[1], enableConstraints: Boolean[1], checked: Boolean[1], extensions: Extension[*], debug: DebugContext[1]): ExecutionNode[1]
{
let instancesProvider = $mc.instancesProvider->get($sourceTree.class)->evaluateAndDeactivate();
let varName = $instancesProvider.classifierGenericType.typeArguments.rawType->cast(@FunctionType).parameters.name->toOne();
let expressionWithTreeInjected = $instancesProvider.expressionSequence->toOne()->meta::pure::functions::meta::reprocessVSWithInScopeVars(newMap(pair($varName, list($sourceTree))));
let expressionWithChecked = if($checked,
| ^SimpleFunctionExpression
(
func = checked_T_MANY__Checked_MANY_,
functionName = 'checked',
genericType = ^GenericType(rawType = Checked, typeArguments = $expressionWithTreeInjected.genericType),
multiplicity = ZeroMany,
importGroup = system::imports::coreImport,
resolvedTypeParameters = ^GenericType(rawType = $expressionWithTreeInjected.genericType.rawType->orElse(Any)),
parametersValues = $expressionWithTreeInjected
)->evaluateAndDeactivate(),
| $expressionWithTreeInjected);
let fd = ^LambdaFunction<{->Any[*]}>(expressionSequence = $expressionWithChecked);
meta::pure::executionPlan::executionPlan($fd, $exeCtx, $extensions, $debug).rootExecutionNode;
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::updateClusterWithChainProcessingInfo(cluster:StoreMappingClusteredValueSpecification[1], ext:RoutedValueSpecification[0..1], setsProcessed:SetImplementation[*]): StoreMappingClusteredValueSpecification[1]
{
let updatedValue = $cluster.val->match([
e:StoreMappingRoutedValueSpecification[1] | ^$e(processedChainSets = $e.processedChainSets->concatenate($setsProcessed)),
v:ValueSpecification[1] | $v
]);
^$cluster(val = $updatedValue);
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::generateInMemoryChildGraphNodes(tree: RoutedGraphFetchTree[1], parentPath: String[1], orderedPaths: String[*], debug: DebugContext[1]): InMemoryGraphFetchExecutionNode[*]
{
$tree.subTrees->cast(@RoutedPropertyGraphFetchTree)->map({st |
if (!$st.property->isPrimitiveValueProperty() && (!$st.property->instanceOf(QualifiedProperty) || $st.property->hasGeneratedMilestoningPropertyStereotype()) && generatePropertyNodeForComplexProperty($st, $tree.sets),
{|
let property = $st.property;
let currentPath = $parentPath + '.' + $st->nodePathName();
assert($parentPath->in($orderedPaths), | 'Unknown path ' + $parentPath + '; known are: ' + $orderedPaths->joinStrings('[', '; ', ']'));
assert($currentPath->in($orderedPaths), | 'Unknown path ' + $currentPath + '; known are: ' + $orderedPaths->joinStrings('[', '; ', ']'));
^InMemoryPropertyGraphFetchExecutionNode
(
resultType = $st->resultTypeFromGraphFetchTree(),
nodeIndex = $orderedPaths->indexOf($currentPath),
parentIndex = $orderedPaths->indexOf($parentPath),
graphFetchTree = $st,
children = $st->generateInMemoryChildGraphNodes($currentPath, $orderedPaths, $debug)
);
},
{|
[]
}
)
})
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::generatePropertyNodeForComplexProperty(pgft:RoutedPropertyGraphFetchTree[1], sets: SetImplementation[*]): Boolean[1]
{
!($sets->size() == 1 && $sets->toOne()->instanceOf(InstanceSetImplementation)
&& $pgft.sets->isEmpty()
&& $pgft.property->meta::pure::mapping::modelToModel::contract::isPropertyAutoMapped($sets->cast(@InstanceSetImplementation)->toOne()));
}
function <> meta::pure::mapping::modelToModel::graphFetch::executionPlan::planInMemoryStoreMergeGraphFetchExecution(sq: StoreQuery[1], ext: RoutedValueSpecification[0..1], sets: InstanceSetImplementation[*], mapping: Mapping[1], runtime: Runtime[1], exeCtx: meta::pure::runtime::ExecutionContext[1], extensions: meta::pure::extension::Extension[*], debug: DebugContext[1]): ExecutionNode[1]
{
$mapping->meta::pure::mapping::modelToModel::validateMergeMapping();
let fe = $sq.vs->evaluateAndDeactivate()->cast(@FunctionExpression);
let params = $fe.parametersValues->evaluateAndDeactivate();
let p1 = $params->at(0);
let p2 = $params->at(1)->cast(@InstanceValue);
let tree = $p2.values->at(0)->cast(@ClusteredGraphFetchTree);
let rf = {set: InstanceSetImplementation[1] | ^$fe(parametersValues = [$p1, ^$p2(values = ^$tree(tree = $tree->byPassClusteringInfoDeep()->cast(@RootGraphFetchTree)->routeRootGraphFetchTree($set, $mapping, $exeCtx, $extensions)))]->evaluateAndDeactivate())->evaluateAndDeactivate()};
let newFe =
^SimpleFunctionExpression
(
func = mergeInstance_Any_MANY__Any_1_,
functionName = 'mergeInstance',
genericType = $fe.genericType,
multiplicity = ZeroMany,
importGroup = system::imports::coreImport,
resolvedTypeParameters = ^GenericType(rawType=Any),
parametersValues = ^InstanceValue(genericType=^GenericType(rawType=ValueSpecification),
multiplicity=OneMany,
values = $sets->map(set | $rf->eval($set))->evaluateAndDeactivate())
)->evaluateAndDeactivate();
planInMemoryRouterMergeGraphFetchExecution(^$sq(vs = $newFe), $ext, $mapping, $runtime, $exeCtx, $extensions, $debug,$sq,$tree);
}
function <> meta::pure::mapping::modelToModel::validateMergeMapping(mapping:Mapping[1]):Boolean[1]
{
let allOps = $mapping.classMappings->filter(m|$m->instanceOf(OperationSetImplementation))->cast(@OperationSetImplementation);
let mergeSets = $allOps->filter(o|$o.operation==meta::pure::router::operations::merge_OperationSetImplementation_1__SetImplementation_MANY_);
$mergeSets->map(s | assert($s.parameters.setImplementation->cast(@PureInstanceSetImplementation).class->distinct()->size()==1,'Found an attempt to merge different classes, Merge operation ids must all be the same class');
let groupedProperties = $s.parameters.setImplementation->cast(@PureInstanceSetImplementation).propertyMappings->filter(p|!$p.targetSetImplementationId->in($allOps.parameters.id))->groupBy(p|$p.property);
$groupedProperties->values() ->map(p | assert( $p.values ->size()<=1 ,'Found property with multiple mappings: ' + $p.values->at(0).property.owner->elementToPath()+'.'+$p.values->at(0).property.name->toOne() + ' in ' + $p.values.owner.id->makeString(', ') ) ;
);
);
true;
}
function <> meta::pure::mapping::modelToModel::graphFetch::executionPlan::planInMemoryRouterMergeGraphFetchExecution(sq: StoreQuery[1], ext: RoutedValueSpecification[0..1], mapping: Mapping[1], runtime: Runtime[1], exeCtx: meta::pure::runtime::ExecutionContext[1], extensions: Extension[*], debug: DebugContext[1],rootSQ:StoreQuery[1],rootTree:ClusteredGraphFetchTree[1]): ExecutionNode[1]
{
let fe = $sq.vs->evaluateAndDeactivate()->cast(@FunctionExpression);
let subClusters = $fe.parametersValues->at(0)->evaluateAndDeactivate()->cast(@InstanceValue).values->cast(@ValueSpecification)->map({p | $p->cluster($mapping, $runtime, $sq.inScopeVars, $exeCtx, $extensions, $debug).cluster->evaluateAndDeactivate()})->cast(@StoreMappingClusteredValueSpecification);
let firstCluster = $subClusters->at(0);
let childNodes = $subClusters->map(cls | $cls->plan($sq.inScopeVars, $exeCtx, $extensions, $debug)->cast(@GlobalGraphFetchExecutionNode))->mergeGraphFetchPlans($rootTree);
^PlatformMergeExecutionNode
(
fromCluster = ^$firstCluster(val = $fe),
resultType = $childNodes->at(0).resultType,
executionNodes = $childNodes
);
}
function <> meta::pure::mapping::modelToModel::graphFetch::executionPlan::mergeGraphFetchPlans(nodes: GlobalGraphFetchExecutionNode[*],rootTree:ClusteredGraphFetchTree[1]): ExecutionNode[1]
{
let firstNode = $nodes->at(0)->cast(@GlobalGraphFetchExecutionNode);
let firstlocal = $firstNode.localGraphFetchExecutionNode->cast(@InMemoryRootGraphFetchExecutionNode);
let initialTree = $firstlocal.graphFetchTree;
let updatedTree = ^$initialTree(sets=$rootTree.tree.sets, subTrees=$rootTree.tree.subTrees);
let newLocal = ^InMemoryRootGraphFetchExecutionNode(graphFetchTree=$updatedTree,
resultType=$firstNode.resultType,
executionNodes = $nodes.localGraphFetchExecutionNode->cast(@InMemoryRootGraphFetchExecutionNode),
checked=$firstlocal.checked,
nodeIndex= $firstlocal.nodeIndex,
batchSize=1,
filter =$firstlocal.filter,
requiredVariableInputs = $firstlocal.requiredVariableInputs
);
^$firstNode(localGraphFetchExecutionNode=$newLocal );
}
function <> meta::pure::mapping::modelToModel::graphFetch::executionPlan::explodeMultiPropertyMappingSubTreesToMultiSubTrees(tree: RoutedGraphFetchTree[1]): RoutedGraphFetchTree[1]
{
^$tree
(
subTrees = $tree.subTrees->match([
rp : RoutedPropertyGraphFetchTree[1] | if ($rp.propertyMapping->size() > 1,
| $rp.propertyMapping->map(pm | ^$rp(propertyMapping = $pm, sets = $rp.sets->filter(x | $x.id == $pm.targetSetImplementationId))),
| $rp
)->map(x | $x->explodeMultiPropertyMappingSubTreesToMultiSubTrees()),
g : GraphFetchTree[1] | $g
])
)
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::sourceSupportsBatching(c:Connection[1], rootClass:Class[1], mapping:Mapping[1], populatedXStorePropertiesInScope:AbstractProperty[*], extensions:meta::pure::extension::Extension[*]):Boolean[1]
{
$c->match([mcc:ModelChainConnection[1] |
let chainedMappings = $mapping->concatenate($mcc.mappings->take($mcc.mappings->size() - 1));
let sourceClassWitSetsProcessed = $chainedMappings->fold({m, c | let srcClass = $m->rootClassMappingByClass($c.first)->cast(@PureInstanceSetImplementation).srcClass->toOne()->cast(@Class);
let processedSets = $c.second;
pair($srcClass, ^$processedSets(values += $m->rootClassMappingByClass($c.first)->cast(@PureInstanceSetImplementation)));
}, pair($rootClass, ^List(values = [])));
let sourceClass = $sourceClassWitSetsProcessed.first;
let setsProcessed = $sourceClassWitSetsProcessed.second;
let sourceSetImpl = $mcc.mappings->last()->toOne()->rootClassMappingByClass($sourceClass);
let inScopeVars = $populatedXStorePropertiesInScope->map(p |
pair($p->propertyToVarName(), ^List(values=$p))
)->newMap();
// resolve Params
let resolvedSourceParams = $setsProcessed.values->resolveParamMapForChainProcessing($inScopeVars, noDebug());
let resolvedSourceProperties = $inScopeVars->keyValues()
->filter(kv | $kv.second.values->size() == 1 && $kv.second.values->toOne()->instanceOf(AbstractProperty))
.second.values->cast(@AbstractProperty)
->map(targetProp | $targetProp->resolveSourcePropertyFromTargetProperty($setsProcessed.values->cast(@meta::external::store::model::PureInstanceSetImplementation)));
$sourceSetImpl->match(
[
i:InstanceSetImplementation[1] |
let f = $extensions->storeContractForSetImplementation($i).crossStoreSourceSupportsBatching;
if ($f->isEmpty(),
| false,
| $f->toOne()->eval($i, $resolvedSourceProperties, $resolvedSourceParams)
);,
s:SetImplementation[1] | false
]
);,
c:Connection[1] | false
]);
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::resolveParamMapForChainProcessing(setsProcessed:SetImplementation[*], inScopeVars:Map>[1], debug:DebugContext[1]):Map[1]
{
let populatedXStorePropertiesInScope = $inScopeVars->keyValues()->filter(kv | $kv.second.values->size() == 1 && $kv.second.values->toOne()->instanceOf(AbstractProperty)).second.values->cast(@AbstractProperty);
assert($populatedXStorePropertiesInScope->forAll(prop | $prop.multiplicity->hasToOneUpperBound()), 'All properties in XStore relationship should have 1 as multiplicty upper bound for service store.');
$populatedXStorePropertiesInScope->map(p | let targetPath = $p->propertyToVarName();
let srcProperty = resolveSourcePropertyFromTargetProperty($p, $setsProcessed->cast(@PureInstanceSetImplementation));
let srcPath = $srcProperty->propertyToVarName();
pair($srcPath, $targetPath);)->newMap();
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::resolveSourcePropertyFromTargetProperty(property:AbstractProperty[1], sets:PureInstanceSetImplementation[*]):AbstractProperty[1]
{
let set = $sets->filter(set | $set.class == $property.owner || $set.mappingClass == $property.owner);
if($set->isEmpty(),
| $property,
| assert($set->size() == 1, 'Expected 1 mapping for class - ' + $property.owner.name->toOne());
let pm = $set.propertyMappings->filter(pm | $pm.property == $property);
assert($pm->cast(@PurePropertyMapping).transform.expressionSequence->toOne()->instanceOf(SimpleFunctionExpression) && $pm->cast(@PurePropertyMapping).transform.expressionSequence->toOne()->cast(@SimpleFunctionExpression)->evaluateAndDeactivate().func->instanceOf(AbstractProperty), 'Expected target property : ' + $property.name->toOne() + ' to be mapped directly to a source property for resolving service parameter input');
let srcProperty = $pm->cast(@PurePropertyMapping).transform.expressionSequence->toOne()->cast(@SimpleFunctionExpression)->evaluateAndDeactivate().func->cast(@AbstractProperty);
$srcProperty->resolveSourcePropertyFromTargetProperty($sets);
);
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::propertyToVarName(property:AbstractProperty[1]):String[1]
{
if($property.owner->toOne()->instanceOf(MappingClass),
|$property.owner->cast(@MappingClass).generalizations.general.rawType.name->toOne(),
|$property.owner.name->toOne()) + '_' + $property.name->toOne();
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::inMemoryGraphFetchExecutionNodeToString(node: InMemoryGraphFetchExecutionNode[1], space: String[1], extensions:meta::pure::extension::Extension[*]):String[1]
{
$node->match([
g : InMemoryCrossStoreGraphFetchExecutionNode[1] |
'InMemoryCrossStoreGraphFetch\n' +
$space + '(' + header($g, $space, $extensions) + '\n'+
$space + ' graphFetchTree = ' + $g.graphFetchTree->sortTree()->asString(true)->replace('\n', '')->replace(' ', '') + '\n' +
$space + ' nodeIndex = ' + $g.nodeIndex->toString() + '\n' +
if($g.parentIndex->isEmpty(), | '', | $space + ' parentIndex = ' + $g.parentIndex->toOne()->toString() + '\n') +
if($g.batchSize->isEmpty(), | '', | $space + ' batchSize = ' + $g.batchSize->toOne()->toString() + '\n') +
$space + ' xStorePropertyMapping = ' + $g.xStorePropertyMapping.property.name->toOne() + '[' + $g.xStorePropertyMapping.sourceSetImplementationId->toOne() + ' -> ' + $g.xStorePropertyMapping.targetSetImplementationId->toOne() + ']' + '\n' +
$space + ' supportsBatching = ' + $g.supportsBatching->toString() +
$space + ' checked = ' + $g.checked->toString() +
$g->childrenToString($space+' ', $extensions)+'\n'+
$space + ' children = ' + $g.children->map(x | inMemoryGraphFetchExecutionNodeToString($x, $space + ' ', $extensions))->joinStrings('[\n'+ $space + ' ', '\n' + $space + ' ', '\n' + $space + ' ]\n') +
$g.implementation->printImplementation('implementation', $space+' ', $extensions)+
$space + ')\n',
g : InMemoryRootGraphFetchExecutionNode[1] |
'InMemoryRootGraphFetch\n' +
$space + '(' + header($g, $space, $extensions) + '\n'+
$space + ' graphFetchTree = ' + $g.graphFetchTree->sortTree()->asString(true)->replace('\n', '')->replace(' ', '') + '\n' +
$space + ' nodeIndex = ' + $g.nodeIndex->toString() + '\n' +
if($g.parentIndex->isEmpty(), | '', | $space + ' parentIndex = ' + $g.parentIndex->toOne()->toString() + '\n') +
if($g.batchSize->isEmpty(), | '', | $space + ' batchSize = ' + $g.batchSize->toOne()->toString() + '\n') +
$space + ' checked = ' + $g.checked->toString() +
$g->childrenToString($space+' ', $extensions)+'\n'+
$space + ' children = ' + $g.children->map(x | inMemoryGraphFetchExecutionNodeToString($x, $space + ' ', $extensions))->joinStrings('[\n'+ $space + ' ', '\n' + $space + ' ', '\n' + $space + ' ]\n') +
$g.implementation->printImplementation('implementation', $space+' ', $extensions)+
$space + ')\n',
g : InMemoryPropertyGraphFetchExecutionNode[1] |
'InMemoryPropertyGraphFetch\n' +
$space + '(' + header($g, $space, $extensions) + '\n'+
$space + ' graphFetchTree = ' + $g.graphFetchTree->sortTree()->asString(true)->replace('\n', '')->replace(' ', '') + '\n' +
$space + ' nodeIndex = ' + $g.nodeIndex->toString() + '\n' +
if($g.parentIndex->isEmpty(), | '', | $space + ' parentIndex = ' + $g.parentIndex->toOne()->toString() + '\n') +
$space + ' children = ' + $g.children->map(x | inMemoryGraphFetchExecutionNodeToString($x, $space + ' ', $extensions))->joinStrings('[\n'+ $space + ' ', '\n' + $space + ' ', '\n' + $space + ' ]\n') +
$g.implementation->printImplementation('implementation', $space+' ', $extensions)+
$space + ')\n'
])
}
function meta::pure::mapping::modelToModel::graphFetch::executionPlan::storeStreamReadingExecutionNodeToString(node: StoreStreamReadingExecutionNode[1], space: String[1], extensions:meta::pure::extension::Extension[*]):String[1]
{
'StoreStreamReading\n' +
$space + '(' + header($node, $space, $extensions) + '\n'+
$space + ' graphFetchTree = ' + $node.graphFetchTree->sortTree()->asString(true)->replace('\n', '')->replace(' ', '') + '\n' +
$space + ' connection = ' + $node.connection->meta::pure::executionPlan::toString::connectionToString($extensions) + '\n' +
$space + ' enableConstraints = ' + $node.enableConstraints->toString() + '\n' +
$space + ' checked = ' + $node.checked->toString() + '\n' +
$node.implementation->printImplementation('implementation', $space+' ', $extensions)+
$space + ')\n'
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy