@finos/legend-graph
Version:
Legend graph and graph manager
306 lines • 18.3 kB
JavaScript
/**
* 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 { PRIMITIVE_TYPE, ELEMENT_PATH_DELIMITER, ROOT_PACKAGE_NAME, } from '../../../../../../../graph/MetaModelConst.js';
import { uniq, guaranteeNonNullable, assertNonEmptyString, guaranteeType, } from '@finos/legend-shared';
import { GenericType } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/GenericType.js';
import { ImportAwareCodeSection, } from '../../../../../../../graph/metamodel/pure/packageableElements/section/Section.js';
import { StereotypeImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/StereotypeReference.js';
import { GenericTypeImplicitReference, } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/GenericTypeReference.js';
import { PackageableElementImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/PackageableElementReference.js';
import { TagImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/TagReference.js';
import { PropertyImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/PropertyReference.js';
import { JoinImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/model/JoinReference.js';
import { FilterImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/model/FilterReference.js';
import { RootFlatDataRecordTypeImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/store/flatData/model/RootFlatDataRecordTypeReference.js';
import { createImplicitRelationReference } from '../../../../../../../graph/metamodel/pure/packageableElements/store/relational/model/RelationReference.js';
import { EnumValueImplicitReference } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/EnumValueReference.js';
import { V1_getRelation } from './helpers/V1_DatabaseBuilderHelper.js';
import { DataType } from '../../../../../../../graph/metamodel/pure/packageableElements/domain/DataType.js';
import { GraphBuilderError } from '../../../../../../../graph-manager/GraphManagerUtils.js';
import { getClassProperty, getEnumValue, getOwnProperty, getStereotype, getTag, newGenericType, } from '../../../../../../../graph/helpers/DomainHelper.js';
import { getFilter, getJoin, } from '../../../../../../../graph/helpers/STO_Relational_Helper.js';
import { getRootRecordType, getSection, } from '../../../../../../../graph/helpers/STO_FlatData_Helper.js';
import { V1_getGenericTypeFullPath } from '../../../helpers/V1_DomainHelper.js';
export const V1_buildFullPath = (packagePath, name) => `${guaranteeNonNullable(packagePath, 'Package path is required')}${ELEMENT_PATH_DELIMITER}${guaranteeNonNullable(name, 'Name is required')}`;
export class V1_GraphBuilderContext {
autoImports;
sectionImports = [];
logService;
currentSubGraph;
extensions;
graph;
section;
options;
constructor(builder) {
this.logService = builder.logService;
this.graph = builder.graph;
this.autoImports = this.graph.autoImports;
this.currentSubGraph = builder.currentSubGraph;
this.extensions = builder.extensions;
this.sectionImports = builder.sectionImports;
this.section = builder.section;
this.options = builder.options;
}
/**
* Since we haven't fully supported section index, shortened paths
* using imports in the graph might need to be fully resolved.
*
* To handle this need, we make use of references. References can auto
* resolve full paths when the section index is deleted. But, when
* building the graph, we leave the value specifications
* raw/unprocessed. Hence, we cannot make use of references to do full
* path resolution, as such, we make a best effort traversal in the model
* of raw value specifications to resolve path automatically.
*
* We create this flag to control the behavior of lambda auto path-resolution.
* This rewriting behavior should not be done for immutable graphs, such as
* system, depdendencies, and generation. However, in overall, it would be controlled
* also by the `TEMPORARY__preserveSectionIndex` flag.
*
* NOTE: When we fully support section index, we would certainly need to
* revise the usefullness of this flag, perhaps, we don't auto-resolve anymore,
* but this mechanism would still be beneficial as we can keep it as an utility
* to resolve raw lambdas' paths when the user deliberately delete the section
* index, for example.
*
* https://github.com/finos/legend-studio/issues/1067
*/
get enableRawLambdaAutoPathResolution() {
return (this.graph.root.name === ROOT_PACKAGE_NAME.MAIN &&
!this.options?.TEMPORARY__preserveSectionIndex);
}
resolve(path, resolverFn) {
// Try the find from special types (not user-defined top level types)
const SPECIAL_TYPES = Object.values(PRIMITIVE_TYPE).concat([]);
if (SPECIAL_TYPES.includes(path)) {
return {
element: resolverFn(path),
};
}
// if the path is a path with package, no resolution from section imports is needed
if (path.includes(ELEMENT_PATH_DELIMITER)) {
return {
element: resolverFn(path),
isFullPath: true,
};
}
// NOTE: here we make the assumption that we have populated the indices properly so the same element
// is not referred using 2 different paths in the same element index
const results = new Map();
this.autoImports.forEach((importPackage) => {
try {
const fullPath = importPackage.path + ELEMENT_PATH_DELIMITER + path;
const element = resolverFn(fullPath);
if (element) {
results.set(fullPath, {
element,
resolvedUsingSectionImports: false,
});
}
}
catch {
// do nothing
}
});
// only resolve section imports if there is a section
if (this.section) {
this.sectionImports.forEach((importPackage) => {
try {
const fullPath = importPackage.path + ELEMENT_PATH_DELIMITER + path;
const element = resolverFn(fullPath);
if (element) {
results.set(fullPath, {
element,
resolvedUsingSectionImports: true,
});
}
}
catch {
// do nothing
}
});
}
switch (results.size) {
/**
* NOTE: if nothing is found then we will try to find user-defined elements at root package (i.e. no package)
* We place this after import resolution since we want to emphasize that this type of element has the lowest precedence
* In fact, due to the restriction that Alloy imposes on element path, the only kinds of element
* we could find at this level are packages, but they will not fit the type we look for
* in PURE, since we resolve to CoreInstance, further validation needs to be done to make the resolution complete
* here we count on the `resolver` to do the validation of the type of element instead
*/
case 0:
return {
element: resolverFn(path),
isFullPath: true,
};
case 1:
return guaranteeNonNullable(Array.from(results.values())[0]);
default:
throw new GraphBuilderError(undefined, `Can't resolve element with path '${path}' - multiple matches found [${Array.from(results.keys()).join(', ')}]`);
}
}
/**
* This method and this class in general demonstrates the difference
* between explicit and implicit reference.
* See {@link PackageableElementImplicitReference} for more details.
*
* Notice that every method in the resolver ends up creating an implicit reference.
* It does not matter whether the full path is specified or not (i.e. so almost
* no inference was done), the resulting reference must be implicit, as we took the
* input into account when creating this reference.
*/
createImplicitPackageableElementReference = (path, resolverFn) => {
const { element, resolvedUsingSectionImports, isFullPath } = this.resolve(path, resolverFn);
if (!resolvedUsingSectionImports && !isFullPath) {
return PackageableElementImplicitReference.create(element, path);
}
return PackageableElementImplicitReference.resolveFromSection(element, path, resolvedUsingSectionImports ? this.section : undefined);
};
resolveStereotype = (stereotypePtr) => {
assertNonEmptyString(stereotypePtr.profile, `Steoreotype pointer 'profile' field is missing or empty`);
assertNonEmptyString(stereotypePtr.value, `Steoreotype pointer 'value' field is missing or empty`);
const ownerReference = this.resolveProfile(stereotypePtr.profile);
const value = getStereotype(ownerReference.value, stereotypePtr.value);
return StereotypeImplicitReference.create(ownerReference, value);
};
resolveTag = (tagPtr) => {
assertNonEmptyString(tagPtr.profile, `Tag pointer 'profile' field is missing or empty`);
assertNonEmptyString(tagPtr.value, `Tag pointer 'value' field is missing or empty`);
const ownerReference = this.resolveProfile(tagPtr.profile);
const value = getTag(ownerReference.value, tagPtr.value);
return TagImplicitReference.create(ownerReference, value);
};
resolveGenericType = (path) => {
const ownerReference = this.resolveType(path);
const value = new GenericType(ownerReference.value);
return GenericTypeImplicitReference.create(ownerReference, value);
};
resolveGenericTypeFromProtocol = (genericType) => {
const ownerReference = this.resolveType(V1_getGenericTypeFullPath(genericType));
const typeArguments = genericType.typeArguments.map((g) => this.resolveGenericTypeFromProtocol(g));
const value = newGenericType(ownerReference.value, typeArguments);
return GenericTypeImplicitReference.create(ownerReference, value);
};
resolveOwnProperty = (pointer) => {
assertNonEmptyString(pointer.class, `Property pointer 'class' field is missing or empty`);
assertNonEmptyString(pointer.property, `Property pointer 'property' field is missing or empty`);
const ownerReference = this.resolvePropertyOwner(pointer.class);
const value = getOwnProperty(ownerReference.value, pointer.property);
return PropertyImplicitReference.create(ownerReference, value);
};
resolveProperty = (pointer) => {
assertNonEmptyString(pointer.class, `Property pointer 'class' field is missing or empty`);
assertNonEmptyString(pointer.property, `Property pointer 'property' field is missing or empty`);
const ownerReference = this.resolveClass(pointer.class);
const value = getClassProperty(ownerReference.value, pointer.property);
return PropertyImplicitReference.create(ownerReference, value);
};
resolveRootFlatDataRecordType = (classMapping) => {
assertNonEmptyString(classMapping.flatData, `Flat-data class mapping 'flatData' field is missing or empty`);
assertNonEmptyString(classMapping.sectionName, `Flat-data class mapping 'sectionName' field is missing or empty`);
const ownerReference = this.resolveFlatDataStore(classMapping.flatData);
const value = getRootRecordType(getSection(ownerReference.value, classMapping.sectionName));
return RootFlatDataRecordTypeImplicitReference.create(ownerReference, value);
};
resolveRelation = (tablePtr) => {
assertNonEmptyString(tablePtr.database, `Table pointer 'database' field is missing or empty`);
assertNonEmptyString(tablePtr.schema, `Table pointer 'schema' field is missing or empty`);
assertNonEmptyString(tablePtr.table, `Table pointer 'table' field is missing or empty`);
const ownerReference = this.resolveDatabase(tablePtr.database);
const value = V1_getRelation(ownerReference.value, tablePtr.schema, tablePtr.table);
return createImplicitRelationReference(ownerReference, value);
};
resolveJoin = (joinPtr) => {
assertNonEmptyString(joinPtr.db, `Join pointer 'db' field is missing or empty`);
assertNonEmptyString(joinPtr.name, `Join pointer 'name' field is missing or empty`);
const ownerReference = this.resolveDatabase(joinPtr.db);
const value = getJoin(ownerReference.value, joinPtr.name);
return JoinImplicitReference.create(ownerReference, value);
};
resolveFilter = (filterPtr) => {
assertNonEmptyString(filterPtr.db, `Filter pointer 'db' field is missing or empty`);
assertNonEmptyString(filterPtr.name, `Filter pointer 'name' field is missing or empty`);
const ownerReference = this.resolveDatabase(filterPtr.db);
const value = getFilter(ownerReference.value, filterPtr.name);
return FilterImplicitReference.create(ownerReference, value);
};
resolveEnumValue = (enumeration, enumValue) => {
const ownerReference = this.resolveEnumeration(enumeration);
const value = getEnumValue(ownerReference.value, enumValue);
return EnumValueImplicitReference.create(ownerReference, value);
};
resolveElement = (path, includePackage) => this.createImplicitPackageableElementReference(path, (_path) => this.graph.getElement(_path, includePackage));
resolveType = (path) => this.createImplicitPackageableElementReference(path, this.graph.getType);
resolveDataType = (path) => this.createImplicitPackageableElementReference(path, (_path) => guaranteeType(this.graph.getType(_path), DataType, `Can't find data type '${_path}'`));
resolveProfile = (path) => this.createImplicitPackageableElementReference(path, this.graph.getProfile);
resolveClass = (path) => this.createImplicitPackageableElementReference(path, this.graph.getClass);
resolveEnumeration = (path) => this.createImplicitPackageableElementReference(path, this.graph.getEnumeration);
resolveMeasure = (path) => this.createImplicitPackageableElementReference(path, this.graph.getMeasure);
resolveUnit = (path) => this.createImplicitPackageableElementReference(path, this.graph.getUnit);
resolveAssociation = (path) => this.createImplicitPackageableElementReference(path, this.graph.getAssociation);
resolvePropertyOwner = (path) => this.createImplicitPackageableElementReference(path, this.graph.getPropertyOwner);
resolveFunction = (path) => this.createImplicitPackageableElementReference(path, this.graph.getFunction);
resolveStore = (path) => this.createImplicitPackageableElementReference(path, this.graph.getStore);
resolveFlatDataStore = (path) => this.createImplicitPackageableElementReference(path, this.graph.getFlatDataStore);
resolveDatabase = (path) => this.createImplicitPackageableElementReference(path, this.graph.getDatabase);
resolveMapping = (path) => this.createImplicitPackageableElementReference(path, this.graph.getMapping);
resolveService = (path) => this.createImplicitPackageableElementReference(path, this.graph.getService);
resolveConnection = (path) => this.createImplicitPackageableElementReference(path, this.graph.getConnection);
resolveRuntime = (path) => this.createImplicitPackageableElementReference(path, this.graph.getRuntime);
resolveGenerationSpecification = (path) => this.createImplicitPackageableElementReference(path, this.graph.getGenerationSpecification);
resolveFileGeneration = (path) => this.createImplicitPackageableElementReference(path, this.graph.getFileGeneration);
resolveDataElement = (path) => this.createImplicitPackageableElementReference(path, this.graph.getDataElement);
}
export class V1_GraphBuilderContextBuilder {
logService;
/**
* The (sub) graph where the current processing is taking place.
* This information is important because each sub-graph holds their
* own indexes for elements they are responsible for.
*
* e.g. dependency graph, generation graph, system graph, etc.
*/
currentSubGraph;
extensions;
graph;
sectionImports = [];
section;
options;
constructor(graph, currentSubGraph, extensions, logService, options) {
this.graph = graph;
this.currentSubGraph = currentSubGraph;
this.extensions = extensions;
this.logService = logService;
this.options = options;
}
withElement(element) {
const section = this.graph.getOwnNullableSection(element.path);
return this.withSection(section);
}
withSection(section) {
this.section = section;
if (section instanceof ImportAwareCodeSection) {
this.sectionImports = this.sectionImports.concat(section.imports.map((i) => i.value));
}
this.sectionImports = uniq(this.sectionImports); // remove duplicates
return this;
}
build() {
return new V1_GraphBuilderContext(this);
}
}
//# sourceMappingURL=V1_GraphBuilderContext.js.map