UNPKG

@itwin/presentation-backend

Version:

Backend of iTwin.js Presentation library

195 lines • 9.67 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Core */ Object.defineProperty(exports, "__esModule", { value: true }); exports.parseFullClassName = parseFullClassName; exports.getContentItemsObservableFromElementIds = getContentItemsObservableFromElementIds; exports.getContentItemsObservableFromClassNames = getContentItemsObservableFromClassNames; exports.createIdBatches = createIdBatches; exports.getBatchedClassElementIds = getBatchedClassElementIds; exports.getElementsCount = getElementsCount; const rxjs_1 = require("rxjs"); const core_bentley_1 = require("@itwin/core-bentley"); const presentation_common_1 = require("@itwin/presentation-common"); /** @internal */ function parseFullClassName(fullClassName) { const [schemaName, className] = fullClassName.split(/[:\.]/); return [schemaName, className]; } function getECSqlName(fullClassName) { const [schemaName, className] = parseFullClassName(fullClassName); return `[${schemaName}].[${className}]`; } /** @internal */ function getContentItemsObservableFromElementIds(imodel, contentDescriptorGetter, contentSetGetter, elementIds, classParallelism, batchesParallelism, batchSize) { return { itemBatches: getElementClassesFromIds(imodel, elementIds).pipe((0, rxjs_1.mergeMap)(({ classFullName, ids }) => getBatchedClassContentItems(classFullName, contentDescriptorGetter, contentSetGetter, () => createIdBatches(core_bentley_1.OrderedId64Iterable.sortArray(ids), batchSize), batchesParallelism), classParallelism)), count: (0, rxjs_1.of)(elementIds.length), }; } /** @internal */ function getContentItemsObservableFromClassNames(imodel, contentDescriptorGetter, contentSetGetter, elementClasses, classParallelism, batchesParallelism, batchSize) { return { itemBatches: getClassesWithInstances(imodel, elementClasses).pipe((0, rxjs_1.mergeMap)((classFullName) => getBatchedClassContentItems(classFullName, contentDescriptorGetter, contentSetGetter, () => getBatchedClassElementIds(imodel, classFullName, batchSize), batchesParallelism), classParallelism)), count: (0, rxjs_1.from)(getElementsCount(imodel, elementClasses)), }; } function getBatchedClassContentItems(classFullName, contentDescriptorGetter, contentSetGetter, batcher, batchesParallelism) { return (0, rxjs_1.defer)(async () => { const ruleset = createClassContentRuleset(classFullName); const keys = new presentation_common_1.KeySet(); const descriptor = await contentDescriptorGetter({ rulesetOrId: ruleset, keys }); if (!descriptor) { throw new presentation_common_1.PresentationError(presentation_common_1.PresentationStatus.Error, `Failed to get descriptor for class ${classFullName}`); } return { descriptor, keys, ruleset }; }).pipe( // create elements' id batches (0, rxjs_1.mergeMap)((x) => batcher().pipe((0, rxjs_1.map)((batch) => ({ ...x, batch })))), // request content for each batch, filter by IDs for performance (0, rxjs_1.mergeMap)(({ descriptor, keys, ruleset, batch }) => (0, rxjs_1.defer)(async () => { const filteringDescriptor = new presentation_common_1.Descriptor(descriptor); filteringDescriptor.instanceFilter = { selectClassName: classFullName, expression: createElementIdsECExpressionFilter(batch), }; return contentSetGetter({ rulesetOrId: ruleset, keys, descriptor: filteringDescriptor, }); }).pipe((0, rxjs_1.map)((items) => ({ descriptor, items }))), batchesParallelism)); } function createElementIdsECExpressionFilter(batch) { let filter = ""; function appendCondition(cond) { if (filter.length > 0) { filter += " OR "; } filter += cond; } for (const item of batch) { if (item.from === item.to) { appendCondition(`this.ECInstanceId = ${item.from}`); } else { appendCondition(`this.ECInstanceId >= ${item.from} AND this.ECInstanceId <= ${item.to}`); } } return filter; } function createClassContentRuleset(fullClassName) { const [schemaName, className] = parseFullClassName(fullClassName); return { id: `content/class-descriptor/${fullClassName}`, rules: [ { ruleType: "Content", specifications: [ { specType: "ContentInstancesOfSpecificClasses", classes: { schemaName, classNames: [className], arePolymorphic: false, }, handlePropertiesPolymorphically: true, }, ], }, ], }; } /** Given a list of element ids, group them by class name. */ function getElementClassesFromIds(imodel, elementIds) { const elementIdsBatchSize = 5000; return (0, rxjs_1.range)(0, elementIds.length / elementIdsBatchSize).pipe((0, rxjs_1.mergeMap)((batchIndex) => { const idsFrom = batchIndex * elementIdsBatchSize; const idsTo = Math.min(idsFrom + elementIdsBatchSize, elementIds.length); return (0, rxjs_1.from)(imodel.createQueryReader(` SELECT ec_classname(e.ECClassId) className, GROUP_CONCAT(IdToHex(e.ECInstanceId)) ids FROM bis.Element e WHERE e.ECInstanceId IN (${elementIds.slice(idsFrom, idsTo).join(",")}) GROUP BY e.ECClassId `)); }), (0, rxjs_1.map)((row) => ({ className: row.className, ids: row.ids.split(",") })), (0, rxjs_1.groupBy)(({ className }) => className), (0, rxjs_1.mergeMap)((groups) => groups.pipe((0, rxjs_1.reduce)((acc, g) => { g.ids.forEach((id) => acc.ids.push(id)); return { classFullName: g.className, ids: acc.ids, }; }, { classFullName: "", ids: [] })))); } /** Given a list of full class names, get a stream of actual class names that have instances. */ function getClassesWithInstances(imodel, fullClassNames) { return (0, rxjs_1.from)(fullClassNames).pipe((0, rxjs_1.mergeMap)((fullClassName) => (0, rxjs_1.from)(imodel.createQueryReader(` SELECT ec_classname(e.ECClassId, 's.c') className FROM ${getECSqlName(fullClassName)} e GROUP BY e.ECClassId `))), (0, rxjs_1.map)((row) => row.className)); } /** * Given a sorted list of ECInstanceIds and a batch size, create a stream of batches. Because the IDs won't necessarily * be sequential, a batch is defined a list of from-to pairs. * @internal */ function createIdBatches(sortedIds, batchSize) { return (0, rxjs_1.range)(0, sortedIds.length / batchSize).pipe((0, rxjs_1.map)((batchIndex) => { const sequences = new Array(); const startIndex = batchIndex * batchSize; const endIndex = Math.min((batchIndex + 1) * batchSize, sortedIds.length) - 1; let fromId = sortedIds[startIndex]; let to = { id: sortedIds[startIndex], localId: core_bentley_1.Id64.getLocalId(sortedIds[startIndex]), }; for (let i = startIndex + 1; i <= endIndex; ++i) { const currLocalId = core_bentley_1.Id64.getLocalId(sortedIds[i]); if (currLocalId !== to.localId + 1) { sequences.push({ from: fromId, to: sortedIds[i - 1] }); fromId = sortedIds[i]; } to = { id: sortedIds[i], localId: currLocalId }; } sequences.push({ from: fromId, to: sortedIds[endIndex] }); return sequences; })); } /** * Query all ECInstanceIds from given class and stream from-to pairs that batch the items into batches of `batchSize` size. * @internal */ function getBatchedClassElementIds(imodel, fullClassName, batchSize) { return (0, rxjs_1.from)(imodel.createQueryReader(`SELECT IdToHex(ECInstanceId) id FROM ${getECSqlName(fullClassName)} ORDER BY ECInstanceId`)).pipe((0, rxjs_1.map)((row) => row.id), (0, rxjs_1.bufferCount)(batchSize), (0, rxjs_1.map)((batch) => [{ from: batch[0], to: batch[batch.length - 1] }])); } /** @internal */ async function getElementsCount(db, classNames) { const whereClause = (() => { if (classNames === undefined || classNames.length === 0) { return undefined; } // check if list contains only valid class names const classNameRegExp = new RegExp(/^[\w]+[.:][\w]+$/); const invalidName = classNames.find((name) => !name.match(classNameRegExp)); if (invalidName) { throw new presentation_common_1.PresentationError(presentation_common_1.PresentationStatus.InvalidArgument, `Encountered invalid class name - ${invalidName}. Valid class name formats: "<schema name or alias>.<class name>", "<schema name or alias>:<class name>"`); } return `e.ECClassId IS (${classNames.join(",")})`; })(); const query = ` SELECT COUNT(e.ECInstanceId) AS elementCount FROM bis.Element e ${whereClause ? `WHERE ${whereClause}` : ""} `; for await (const row of db.createQueryReader(query)) { return row.elementCount; } return 0; } //# sourceMappingURL=ElementPropertiesHelper.js.map