/** * Copyright (c) 2020-present, 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 { LogEvent, UnsupportedOperationError, assertType, isNonNullable, guaranteeType, assertNonEmptyString, assertTrue, guaranteeNonNullable, } from '@finos/legend-shared'; import { GRAPH_MANAGER_EVENT } from '../../../../../../../__lib__/GraphManagerEvent.js'; import type { Mapping } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/Mapping.js'; import type { SetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/SetImplementation.js'; import type { PurePropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/modelToModel/mapping/PurePropertyMapping.js'; import { OperationSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/OperationSetImplementation.js'; import { SetImplementationContainer } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/SetImplementationContainer.js'; import { PureInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/modelToModel/mapping/PureInstanceSetImplementation.js'; import { TableAlias } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/model/RelationalOperationElement.js'; import { GroupByMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/GroupByMapping.js'; import { FlatDataInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/flatData/mapping/FlatDataInstanceSetImplementation.js'; import { RootRelationalInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/RootRelationalInstanceSetImplementation.js'; import type { AbstractFlatDataPropertyMapping } from '../../../../../../../graph/metamodel/pure/packageableElements/store/flatData/mapping/AbstractFlatDataPropertyMapping.js'; import { SetImplementationImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/SetImplementationReference.js'; import type { EmbeddedRelationalInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/mapping/EmbeddedRelationalInstanceSetImplementation.js'; import type { V1_GraphBuilderContext } from './V1_GraphBuilderContext.js'; import { V1_buildRelationalOperationElement } from './helpers/V1_DatabaseBuilderHelper.js'; import { V1_buildRelationalClassMapping, V1_buildRelationalPrimaryKey, } from './helpers/V1_RelationalClassMappingBuilderHelper.js'; import type { V1_ClassMappingVisitor, V1_ClassMapping, } from '../../../model/packageableElements/mapping/V1_ClassMapping.js'; import type { V1_OperationClassMapping } from '../../../model/packageableElements/mapping/V1_OperationClassMapping.js'; import type { V1_PureInstanceClassMapping } from '../../../model/packageableElements/store/modelToModel/mapping/V1_PureInstanceClassMapping.js'; import type { V1_RelationalClassMapping } from '../../../model/packageableElements/store/relational/mapping/V1_RelationalClassMapping.js'; import { V1_PropertyMappingBuilder } from './V1_PropertyMappingBuilder.js'; import type { V1_RootFlatDataClassMapping } from '../../../model/packageableElements/store/flatData/mapping/V1_RootFlatDataClassMapping.js'; import type { V1_RootRelationalClassMapping } from '../../../model/packageableElements/store/relational/mapping/V1_RootRelationalClassMapping.js'; import type { V1_AggregationAwareClassMapping } from '../../../model/packageableElements/store/relational/mapping/aggregationAware/V1_AggregationAwareClassMapping.js'; import { V1_getInferredClassMappingId } from './helpers/V1_MappingBuilderHelper.js'; import { AggregationAwareSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/aggregationAware/AggregationAwareSetImplementation.js'; import type { V1_AggregateSetImplementationContainer } from '../../../model/packageableElements/store/relational/mapping/aggregationAware/V1_AggregateSetImplementationContainer.js'; import { extractClassMappingsFromAggregationAwareClassMappings, getAllClassMappings, getAllEnumerationMappings, getOwnClassMappingById, } from '../../../../../../../graph/helpers/DSL_Mapping_Helper.js'; import type { DSL_Mapping_PureProtocolProcessorPlugin_Extension } from '../../../../extensions/DSL_Mapping_PureProtocolProcessorPlugin_Extension.js'; import type { V1_MergeOperationClassMapping } from '../../../model/packageableElements/mapping/V1_MergeOperationClassMapping.js'; import type { V1_INTERNAL__UnknownClassMapping } from '../../../model/packageableElements/mapping/V1_INTERNAL__UnknownClassMapping.js'; import type { V1_RelationFunctionClassMapping } from '../../../model/packageableElements/mapping/V1_RelationFunctionClassMapping.js'; import { RelationFunctionInstanceSetImplementation } from '../../../../../../../graph/metamodel/pure/packageableElements/mapping/relationFunction/RelationFunctionInstanceSetImplementation.js'; import { generateFunctionPrettyName } from '../../../../../../../graph/helpers/PureLanguageHelper.js'; export class V1_ClassMappingSecondPassBuilder implements V1_ClassMappingVisitor { context: V1_GraphBuilderContext; parent: Mapping; constructor(context: V1_GraphBuilderContext, parent: Mapping) { this.context = context; this.parent = parent; } visit_ClassMapping(classMapping: V1_ClassMapping): void { const extraClassMappingBuilders = this.context.extensions.plugins.flatMap( (plugin) => ( plugin as DSL_Mapping_PureProtocolProcessorPlugin_Extension ).V1_getExtraClassMappingSecondPassBuilders?.() ?? [], ); for (const builder of extraClassMappingBuilders) { builder(classMapping, this.context, this.parent); } } visit_INTERNAL__UnknownClassMapping( classMapping: V1_INTERNAL__UnknownClassMapping, ): void { return; } visit_OperationClassMapping(classMapping: V1_OperationClassMapping): void { assertNonEmptyString( classMapping.class, `Operation class mapping 'class' field is missing or empty`, ); const id = V1_getInferredClassMappingId( this.context.resolveClass(classMapping.class).value, classMapping, ).value; const operationSetImplementation = getOwnClassMappingById(this.parent, id); assertType( operationSetImplementation, OperationSetImplementation, `Class mapping with ID '${id}' is not of type operation set implementation`, ); if (classMapping.extendsClassMappingId) { operationSetImplementation.superSetImplementationId = classMapping.extendsClassMappingId; } operationSetImplementation.parameters = classMapping.parameters .map((parameter) => { const setImplementation = getAllClassMappings(this.parent).find( (cm) => cm.id.value === parameter, ); if (!setImplementation) { // TODO: we will get these cases sometimes since we haven't supported includedMappings this.context.logService.debug( LogEvent.create(GRAPH_MANAGER_EVENT.GRAPH_BUILDER_FAILURE), `Can't find class mapping with ID '${parameter}' in mapping '${this.parent.path}' (perhaps because we haven't supported included mappings)`, ); return undefined; } return new SetImplementationContainer( SetImplementationImplicitReference.create( setImplementation, undefined, ), ); }) .filter(isNonNullable); } visit_MergeOperationClassMapping( classMapping: V1_MergeOperationClassMapping, ): void { this.visit_OperationClassMapping(classMapping); } visit_PureInstanceClassMapping( classMapping: V1_PureInstanceClassMapping, ): void { assertNonEmptyString( classMapping.class, `Pure instance class mapping 'class' field is missing or empty`, ); const pureInstanceSetImplementation = guaranteeType( getOwnClassMappingById( this.parent, V1_getInferredClassMappingId( this.context.resolveClass(classMapping.class).value, classMapping, ).value, ), PureInstanceSetImplementation, ); if (classMapping.extendsClassMappingId) { pureInstanceSetImplementation.superSetImplementationId = classMapping.extendsClassMappingId; } // NOTE: we have to process property mappings here instead of in the first pass like the backend because we actually resolve `target` and `source` // at this point instead of just passing in the IDs. This means we have to go through the first pass to create basic mapping elements first // before we can finally use/resolve them in this pass pureInstanceSetImplementation.propertyMappings = classMapping.propertyMappings.map((propertyMapping) => propertyMapping.accept_PropertyMappingVisitor( new V1_PropertyMappingBuilder( this.context, pureInstanceSetImplementation, pureInstanceSetImplementation, getAllEnumerationMappings(this.parent), ), ), ) as PurePropertyMapping[]; } visit_RootFlatDataClassMapping( classMapping: V1_RootFlatDataClassMapping, ): void { assertNonEmptyString( classMapping.class, `Flat-data class mapping 'class' field is missing or empty`, ); const flatDataInstanceSetImplementation = guaranteeType( getOwnClassMappingById( this.parent, V1_getInferredClassMappingId( this.context.resolveClass(classMapping.class).value, classMapping, ).value, ), FlatDataInstanceSetImplementation, ); if (classMapping.extendsClassMappingId) { flatDataInstanceSetImplementation.superSetImplementationId = classMapping.extendsClassMappingId; } flatDataInstanceSetImplementation.propertyMappings = classMapping.propertyMappings.map((propertyMapping) => propertyMapping.accept_PropertyMappingVisitor( new V1_PropertyMappingBuilder( this.context, flatDataInstanceSetImplementation, flatDataInstanceSetImplementation, getAllEnumerationMappings(this.parent), ), ), ) as AbstractFlatDataPropertyMapping[]; } visit_RelationalClassMapping(classMapping: V1_RelationalClassMapping): void { throw new UnsupportedOperationError(); } visit_RootRelationalClassMapping( classMapping: V1_RootRelationalClassMapping, ): SetImplementation { assertNonEmptyString( classMapping.class, `Relational class mapping 'class' field is missing or empty`, ); const rootRelationalInstanceSetImplementation = guaranteeType( getOwnClassMappingById( this.parent, V1_getInferredClassMappingId( this.context.resolveClass(classMapping.class).value, classMapping, ).value, ), RootRelationalInstanceSetImplementation, ); rootRelationalInstanceSetImplementation.distinct = classMapping.distinct; if (classMapping.extendsClassMappingId) { rootRelationalInstanceSetImplementation.superSetImplementationId = classMapping.extendsClassMappingId; } let mainTableAlias: TableAlias | undefined; if (classMapping.mainTable) { const relation = this.context.resolveRelation( classMapping.mainTable, undefined, true, ); mainTableAlias = new TableAlias(); mainTableAlias.relation = relation; mainTableAlias.name = relation.value.name; rootRelationalInstanceSetImplementation.mainTableAlias = mainTableAlias; } // TODO MappingClass if (classMapping.groupBy.length) { const groupBy = new GroupByMapping(); groupBy.columns = classMapping.groupBy.map((op) => V1_buildRelationalOperationElement( op, this.context, new Map(), [], ), ); rootRelationalInstanceSetImplementation.groupBy = groupBy; } const embedded: EmbeddedRelationalInstanceSetImplementation[] = []; const tableAliasIndex = new Map(); V1_buildRelationalClassMapping( classMapping, this.context, rootRelationalInstanceSetImplementation, rootRelationalInstanceSetImplementation, this.parent, embedded, getAllEnumerationMappings(this.parent), tableAliasIndex, ); // TODO filterMapping // NOTE: why are we adding embedded relational property mappings to class mapping in the backend???? if (!mainTableAlias && !classMapping.extendsClassMappingId) { const tables = new Set( Array.from(tableAliasIndex.values()).map((e) => e.relation), ); const dbs = new Set( Array.from(tableAliasIndex.values()).map( (e) => e.relation.value.schema._OWNER, ), ); assertTrue( tables.size === 1, `Can't find the main table for class '${rootRelationalInstanceSetImplementation.class.value.path}'. Please specify a main table using the ~mainTable directive`, ); assertTrue( dbs.size === 1, `Can't find the main table for class '${rootRelationalInstanceSetImplementation.class.value.path}': inconsistent database definitions for the mapping`, ); mainTableAlias = new TableAlias(); mainTableAlias.name = ''; mainTableAlias.relation = guaranteeNonNullable( Array.from(tables.values())[0], ); mainTableAlias.database = Array.from(dbs.values())[0]; rootRelationalInstanceSetImplementation.mainTableAlias = mainTableAlias; } if ( !rootRelationalInstanceSetImplementation.primaryKey.length && !classMapping.extendsClassMappingId ) { V1_buildRelationalPrimaryKey(rootRelationalInstanceSetImplementation); } return rootRelationalInstanceSetImplementation; } visit_AggregationAwareClassMapping( classMapping: V1_AggregationAwareClassMapping, ): SetImplementation { assertNonEmptyString( classMapping.class, `Aggregation-aware class mapping 'class' field is missing or empty`, ); const aggragetionAwareInstanceSetImplementation = guaranteeType( getOwnClassMappingById( this.parent, V1_getInferredClassMappingId( this.context.resolveClass(classMapping.class).value, classMapping, ).value, ), AggregationAwareSetImplementation, ); if (classMapping.extendsClassMappingId) { aggragetionAwareInstanceSetImplementation.superSetImplementationId = classMapping.extendsClassMappingId; } const mapping = this.context.graph.getMapping(this.parent.path); classMapping.mainSetImplementation.accept_ClassMappingVisitor( new V1_ClassMappingSecondPassBuilder(this.context, mapping), ); aggragetionAwareInstanceSetImplementation.propertyMappings = classMapping.propertyMappings.map((propertyMapping) => propertyMapping.accept_PropertyMappingVisitor( new V1_PropertyMappingBuilder( this.context, aggragetionAwareInstanceSetImplementation, aggragetionAwareInstanceSetImplementation, getAllEnumerationMappings(this.parent), new Map(), [ ...getAllClassMappings(mapping), ...extractClassMappingsFromAggregationAwareClassMappings(mapping), ], undefined, aggragetionAwareInstanceSetImplementation, ), ), ); classMapping.aggregateSetImplementations.forEach( (aggregateSetImpl: V1_AggregateSetImplementationContainer) => aggregateSetImpl.setImplementation.accept_ClassMappingVisitor( new V1_ClassMappingSecondPassBuilder(this.context, mapping), ), ); return aggragetionAwareInstanceSetImplementation; } visit_RelationFunctionClassMapping( classMapping: V1_RelationFunctionClassMapping, ): SetImplementation { assertNonEmptyString( classMapping.class, `Relation function class mapping 'class' field is missing or empty`, ); const relationFunctionInstanceSetImplementation = guaranteeType( getOwnClassMappingById( this.parent, V1_getInferredClassMappingId( this.context.resolveClass(classMapping.class).value, classMapping, ).value, ), RelationFunctionInstanceSetImplementation, ); relationFunctionInstanceSetImplementation.relationFunction = guaranteeNonNullable( this.context.graph.functions.find( (fn) => generateFunctionPrettyName(fn, { fullPath: true, spacing: false, notIncludeParamName: true, }).replaceAll(/\s/gu, '') === classMapping.relationFunction.path.replaceAll(/\s/gu, ''), ), ); relationFunctionInstanceSetImplementation.propertyMappings = classMapping.propertyMappings.map((propertyMapping) => propertyMapping.accept_PropertyMappingVisitor( new V1_PropertyMappingBuilder( this.context, relationFunctionInstanceSetImplementation, relationFunctionInstanceSetImplementation, getAllEnumerationMappings(this.parent), ), ), ); return relationFunctionInstanceSetImplementation; } }