UNPKG

@itwin/presentation-hierarchies

Version:

A package for creating hierarchies based on data in iTwin.js iModels.

449 lines 26.7 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. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.createIModelHierarchyProvider = createIModelHierarchyProvider; require("../internal/DisposePolyfill.js"); const rxjs_1 = require("rxjs"); const core_bentley_1 = require("@itwin/core-bentley"); const presentation_shared_1 = require("@itwin/presentation-shared"); const HierarchyErrors_js_1 = require("../HierarchyErrors.js"); const HierarchyNode_js_1 = require("../HierarchyNode.js"); const HierarchyNodeKey_js_1 = require("../HierarchyNodeKey.js"); const Common_js_1 = require("../internal/Common.js"); const EachValueFrom_js_1 = require("../internal/EachValueFrom.js"); const LoggingUtils_js_1 = require("../internal/LoggingUtils.js"); const Partition_js_1 = require("../internal/operators/Partition.js"); const ReduceToMergeMap_js_1 = require("../internal/operators/ReduceToMergeMap.js"); const ShareReplayWithErrors_js_1 = require("../internal/operators/ShareReplayWithErrors.js"); const Sorting_js_1 = require("../internal/operators/Sorting.js"); const RxjsHierarchyDefinition_js_1 = require("../internal/RxjsHierarchyDefinition.js"); const SubscriptionScheduler_js_1 = require("../internal/SubscriptionScheduler.js"); const FilteringHierarchyDefinition_js_1 = require("./FilteringHierarchyDefinition.js"); const HierarchyCache_js_1 = require("./HierarchyCache.js"); const IModelHierarchyDefinition_js_1 = require("./IModelHierarchyDefinition.js"); const NodeSelectQueryFactory_js_1 = require("./NodeSelectQueryFactory.js"); const DetermineChildren_js_1 = require("./operators/DetermineChildren.js"); const Grouping_js_1 = require("./operators/Grouping.js"); const HideIfNoChildren_js_1 = require("./operators/HideIfNoChildren.js"); const HideNodesInHierarchy_js_1 = require("./operators/HideNodesInHierarchy.js"); const TreeNodesReader_js_1 = require("./TreeNodesReader.js"); const LOGGING_NAMESPACE = `${Common_js_1.LOGGING_NAMESPACE}.IModelHierarchyProvider`; const LOGGING_NAMESPACE_INTERNAL = `${Common_js_1.LOGGING_NAMESPACE_INTERNAL}.IModelHierarchyProvider`; const LOGGING_NAMESPACE_PERFORMANCE = `${Common_js_1.LOGGING_NAMESPACE_PERFORMANCE}.IModelHierarchyProvider`; const DEFAULT_QUERY_CONCURRENCY = 10; const DEFAULT_QUERY_CACHE_SIZE = 1; /** * Creates an instance of `HierarchyProvider` that creates a hierarchy based on given iModel and * a hierarchy definition, which defines each hierarchy level through ECSQL queries. * * @public */ function createIModelHierarchyProvider(props) { return new IModelHierarchyProviderImpl(props); } class IModelHierarchyProviderImpl { _imodelAccess; _hierarchyChanged; _valuesFormatter; _sourceHierarchyDefinition; _activeHierarchyDefinition; _localizedStrings; _queryScheduler; _nodesCache; _unsubscribe; _dispose = new rxjs_1.Subject(); constructor(props) { this._imodelAccess = props.imodelAccess; this._hierarchyChanged = new core_bentley_1.BeEvent(); this._activeHierarchyDefinition = this._sourceHierarchyDefinition = (0, RxjsHierarchyDefinition_js_1.getRxjsHierarchyDefinition)(props.hierarchyDefinition); this._valuesFormatter = props?.formatter ?? (0, presentation_shared_1.createDefaultValueFormatter)(); this._localizedStrings = { other: "Other", unspecified: "Not specified", ...props?.localizedStrings }; this._queryScheduler = new SubscriptionScheduler_js_1.SubscriptionScheduler(props.queryConcurrency ?? DEFAULT_QUERY_CONCURRENCY); this.setHierarchyFilter(props.filtering); const queryCacheSize = props.queryCacheSize ?? DEFAULT_QUERY_CACHE_SIZE; if (queryCacheSize !== 0) { this._nodesCache = new HierarchyCache_js_1.HierarchyCache({ // we divide the size by 2, because each variation also counts as a query that we cache size: Math.ceil(queryCacheSize / 2), variationsCount: 1, }); } this._unsubscribe = props.imodelChanged?.addListener(() => { this.invalidateHierarchyCache("Data source changed"); this._dispose.next(); this._hierarchyChanged.raiseEvent(); }); } [Symbol.dispose]() { this._dispose.next(); this._unsubscribe?.(); } /* c8 ignore next 3 */ dispose() { this[Symbol.dispose](); } get hierarchyChanged() { return this._hierarchyChanged; } invalidateHierarchyCache(reason) { (0, LoggingUtils_js_1.doLog)({ category: `${LOGGING_NAMESPACE}.Events`, message: /* c8 ignore next */ () => (reason ? `${reason}: clear hierarchy cache` : `Clear hierarchy cache`), }); this._nodesCache?.clear(); } /** * Sets `HierarchyProvider` values formatter that formats nodes' labels. If provided `undefined`, then defaults to the * result of `createDefaultValueFormatter` called with default parameters. */ setFormatter(formatter) { this._valuesFormatter = formatter ?? (0, presentation_shared_1.createDefaultValueFormatter)(); this._hierarchyChanged.raiseEvent({ formatterChange: { newFormatter: this._valuesFormatter } }); } setHierarchyFilter(props) { if (!props) { if (this._sourceHierarchyDefinition !== this._activeHierarchyDefinition) { this._activeHierarchyDefinition = this._sourceHierarchyDefinition; this.invalidateHierarchyCache("Hierarchy filter reset"); } this._hierarchyChanged.raiseEvent({ filterChange: { newFilter: undefined } }); return; } this._activeHierarchyDefinition = new FilteringHierarchyDefinition_js_1.FilteringHierarchyDefinition({ imodelAccess: this._imodelAccess, source: this._sourceHierarchyDefinition, nodeIdentifierPaths: props.paths, }); this.invalidateHierarchyCache("Hierarchy filter set"); this._dispose.next(); this._hierarchyChanged.raiseEvent({ filterChange: { newFilter: props } }); } onGroupingNodeCreated(groupingNode, props) { this._nodesCache?.set({ ...props, parentNode: groupingNode }, { observable: (0, rxjs_1.from)(groupingNode.children), processingStatus: "pre-processed" }); } createHierarchyLevelDefinitionsObservable(props) { const { requestContext, ...defineHierarchyLevelProps } = props; (0, LoggingUtils_js_1.doLog)({ category: LOGGING_NAMESPACE_PERFORMANCE, message: /* c8 ignore next */ () => `[${requestContext.requestId}] Requesting hierarchy level definitions for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, }); let parentNode = props.parentNode; if (parentNode && HierarchyNode_js_1.HierarchyNode.isGeneric(parentNode) && parentNode.key.source && parentNode.key.source !== this._imodelAccess.imodelKey) { return rxjs_1.EMPTY; } if (parentNode && HierarchyNode_js_1.HierarchyNode.isInstancesNode(parentNode)) { parentNode = { ...parentNode, key: { ...parentNode.key, instanceKeys: parentNode.key.instanceKeys.filter((ik) => !ik.imodelKey || ik.imodelKey === this._imodelAccess.imodelKey), }, }; (0, core_bentley_1.assert)(HierarchyNodeKey_js_1.HierarchyNodeKey.isInstances(parentNode.key)); if (parentNode.key.instanceKeys.length === 0) { return rxjs_1.EMPTY; } } return this._activeHierarchyDefinition.defineHierarchyLevel({ ...defineHierarchyLevelProps, parentNode }).pipe((0, rxjs_1.mergeAll)(), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({ category: LOGGING_NAMESPACE_PERFORMANCE, message: /* c8 ignore next */ () => `[${requestContext.requestId}] Received all hierarchy level definitions for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, }))); } createSourceNodesObservable(props) { // pipe definitions to nodes and put "share replay" on it return this.createHierarchyLevelDefinitionsObservable(props).pipe((0, rxjs_1.mergeMap)((def) => { if (IModelHierarchyDefinition_js_1.HierarchyNodesDefinition.isGenericNode(def)) { return (0, rxjs_1.of)({ ...def.node, key: { type: "generic", id: def.node.key, source: this._imodelAccess.imodelKey } }); } return this._queryScheduler.scheduleSubscription((0, rxjs_1.of)(def.query).pipe((0, rxjs_1.map)((query) => filterQueryByInstanceKeys(query, props.filteredInstanceKeys)), (0, rxjs_1.mergeMap)((query) => (0, TreeNodesReader_js_1.readNodes)({ queryExecutor: this._imodelAccess, query, limit: props.hierarchyLevelSizeLimit, parser: this._activeHierarchyDefinition.parseNode ? (row) => this._activeHierarchyDefinition.parseNode(row, props.parentNode) : undefined, })), (0, rxjs_1.map)((node) => ({ ...node, key: { ...node.key, instanceKeys: node.key.instanceKeys.map((key) => ({ ...key, imodelKey: this._imodelAccess.imodelKey })) }, })))); }), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({ category: LOGGING_NAMESPACE_PERFORMANCE, message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Read all child nodes ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, })), (0, ShareReplayWithErrors_js_1.shareReplayWithErrors)()); } createPreProcessedNodesObservable(queryNodesObservable, props) { return queryNodesObservable.pipe( // we're going to be mutating the nodes, but don't want to mutate the original one, so just clone it here once (0, rxjs_1.map)((node) => ({ ...node })), // set parent node keys on the source node (0, rxjs_1.map)((node) => Object.assign(node, { parentKeys: createParentNodeKeysList(props.parentNode) })), // format `ConcatenatedValue` labels into string labels (0, rxjs_1.mergeMap)((node) => applyLabelsFormatting(node, this._valuesFormatter)), // we have `ProcessedHierarchyNode` from here preProcessNodes(this._activeHierarchyDefinition), // process hiding (0, HideIfNoChildren_js_1.createHideIfNoChildrenOperator)((n) => this.getChildNodesObservables({ parentNode: n, requestContext: props.requestContext }).hasNodes), (0, HideNodesInHierarchy_js_1.createHideNodesInHierarchyOperator)( // note: for child nodes created because of hidden parent, we want to use parent's request props (instance filter, limit) (n) => this.getChildNodesObservables({ ...props, parentNode: n }).processedNodes, false), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({ category: LOGGING_NAMESPACE_PERFORMANCE, message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Finished pre-processing child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, }))); } createProcessedNodesObservable(preprocessedNodesObservable, props) { return preprocessedNodesObservable.pipe((0, Grouping_js_1.createGroupingOperator)(this._imodelAccess, props.parentNode, this._valuesFormatter, this._localizedStrings, undefined, (gn) => this.onGroupingNodeCreated(gn, props)), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({ category: LOGGING_NAMESPACE_PERFORMANCE, message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Finished grouping child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, }))); } createFinalizedNodesObservable(processedNodesObservable, props) { return processedNodesObservable.pipe((0, DetermineChildren_js_1.createDetermineChildrenOperator)((n) => this.getChildNodesObservables({ parentNode: n, requestContext: props.requestContext }).hasNodes), postProcessNodes(this._activeHierarchyDefinition), Sorting_js_1.sortNodesByLabelOperator, (0, rxjs_1.map)((n) => { if ("processingParams" in n) { delete n.processingParams; } return Object.assign(n, { children: (0, Common_js_1.hasChildren)(n), }); }), (0, rxjs_1.takeUntil)(this._dispose), (0, rxjs_1.finalize)(() => (0, LoggingUtils_js_1.doLog)({ category: LOGGING_NAMESPACE_PERFORMANCE, message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Finished finalizing child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, }))); } createHasNodesObservable(preprocessedNodesObservable, possiblyKnownChildrenObservable, props) { const loggingCategory = `${LOGGING_NAMESPACE_INTERNAL}.HasNodes`; return (0, rxjs_1.concat)((possiblyKnownChildrenObservable ?? rxjs_1.EMPTY).pipe((0, rxjs_1.filter)((n) => (0, Common_js_1.hasChildren)(n))), preprocessedNodesObservable).pipe((0, LoggingUtils_js_1.log)({ category: loggingCategory, message: /* c8 ignore next */ (n) => `[${props.requestContext.requestId}] Node before mapping to 'true': ${(0, Common_js_1.createNodeIdentifierForLogging)(n)}`, }), (0, rxjs_1.take)(1), (0, rxjs_1.map)(() => true), (0, rxjs_1.defaultIfEmpty)(false), (0, rxjs_1.catchError)((e) => { (0, LoggingUtils_js_1.doLog)({ category: loggingCategory, message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Error while determining children: ${e.message}`, }); if (e instanceof HierarchyErrors_js_1.RowsLimitExceededError) { return (0, rxjs_1.of)(true); } throw e; }), (0, LoggingUtils_js_1.log)({ category: loggingCategory, message: /* c8 ignore next */ (r) => `[${props.requestContext.requestId}] Result: ${r}` })); } getCachedObservableEntry(props) { const loggingCategory = `${LOGGING_NAMESPACE}.QueryResultsCache`; const { parentNode, ...restProps } = props; const cached = props.ignoreCache || !this._nodesCache ? undefined : this._nodesCache.get(props); if (cached) { (0, LoggingUtils_js_1.doLog)({ category: loggingCategory, message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Found query nodes observable for ${(0, Common_js_1.createNodeIdentifierForLogging)(parentNode)}`, }); return cached; } // if we don't find an entry for a grouping node, we load its instances by getting a query and applying // a filter based on grouped instance keys let filteredInstanceKeys; let parentNonGroupingNode; if (parentNode) { if (HierarchyNode_js_1.HierarchyNode.isGroupingNode(parentNode)) { parentNonGroupingNode = parentNode.nonGroupingAncestor; filteredInstanceKeys = parentNode.groupedInstanceKeys; } else { // not sure why type checker doesn't pick this up (0, core_bentley_1.assert)(HierarchyNode_js_1.HierarchyNode.isGeneric(parentNode) || HierarchyNode_js_1.HierarchyNode.isInstancesNode(parentNode)); parentNonGroupingNode = parentNode; } } const nonGroupingNodeChildrenRequestProps = { ...restProps, parentNode: parentNonGroupingNode, ...(filteredInstanceKeys ? { filteredInstanceKeys } : undefined), }; const value = { observable: this.createSourceNodesObservable(nonGroupingNodeChildrenRequestProps), processingStatus: "none" }; this._nodesCache?.set(nonGroupingNodeChildrenRequestProps, value); (0, LoggingUtils_js_1.doLog)({ category: loggingCategory, message: /* c8 ignore next */ () => `[${props.requestContext.requestId}] Saved query nodes observable for ${(0, Common_js_1.createNodeIdentifierForLogging)(parentNode)}`, }); return value; } getChildNodesObservables(props) { const entry = this.getCachedObservableEntry(props); switch (entry.processingStatus) { case "none": { const pre = this.createPreProcessedNodesObservable(entry.observable, props); const post = this.createProcessedNodesObservable(pre, props); return { processedNodes: post, hasNodes: this.createHasNodesObservable(pre, entry.observable, props), finalizedNodes: this.createFinalizedNodesObservable(post, props), }; } case "pre-processed": { const post = this.createProcessedNodesObservable(entry.observable, props); return { processedNodes: post, hasNodes: this.createHasNodesObservable(entry.observable, undefined, props), finalizedNodes: this.createFinalizedNodesObservable(post, props), }; } } } /** * Creates and runs a query based on provided props, then processes retrieved nodes and returns them. */ getNodes(props) { const requestContext = { requestId: core_bentley_1.Guid.createValue() }; const loggingCategory = `${LOGGING_NAMESPACE}.GetNodes`; const timer = new core_bentley_1.StopWatch(undefined, true); let error; let nodesCount = 0; (0, LoggingUtils_js_1.doLog)({ category: loggingCategory, message: /* c8 ignore next */ () => `[${requestContext.requestId}] Requesting child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, }); return (0, EachValueFrom_js_1.eachValueFrom)(this.getChildNodesObservables({ ...props, requestContext }).finalizedNodes.pipe((0, rxjs_1.tap)(() => ++nodesCount), (0, rxjs_1.catchError)((e) => { error = e; throw e; }), (0, rxjs_1.finalize)(() => { (0, LoggingUtils_js_1.doLog)({ category: loggingCategory, message: /* c8 ignore next */ () => /* c8 ignore next 3 */ error ? `[${requestContext.requestId}] Error creating child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}: ${error instanceof Error ? error.message : error.toString()}` : `[${requestContext.requestId}] Returned ${nodesCount} child nodes for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)} in ${timer.currentSeconds.toFixed(2)} s.`, }); }))); } getNodeInstanceKeysObs(props) { const { parentNode, instanceFilter, hierarchyLevelSizeLimit = "unbounded", requestContext } = props; if (parentNode && HierarchyNode_js_1.HierarchyNode.isGroupingNode(parentNode)) { return (0, rxjs_1.from)(parentNode.groupedInstanceKeys); } (0, core_bentley_1.assert)(!parentNode || HierarchyNode_js_1.HierarchyNode.isGeneric(parentNode) || HierarchyNode_js_1.HierarchyNode.isInstancesNode(parentNode)); const hierarchyLevelDefinitions = this.createHierarchyLevelDefinitionsObservable({ parentNode, instanceFilter, requestContext }); // split the definitions based on whether they're for generic nodes or for instance nodes const [genericDefs, instanceDefs] = (0, Partition_js_1.partition)(hierarchyLevelDefinitions, IModelHierarchyDefinition_js_1.HierarchyNodesDefinition.isGenericNode); // query instance keys and a flag whether they should be hidden const instanceKeys = instanceDefs.pipe((0, rxjs_1.mergeMap)((def) => this._queryScheduler.scheduleSubscription((0, rxjs_1.of)(def.query).pipe((0, rxjs_1.mergeMap)(async (query) => { const ecsql = ` SELECT ${NodeSelectQueryFactory_js_1.NodeSelectClauseColumnNames.FullClassName}, ${NodeSelectQueryFactory_js_1.NodeSelectClauseColumnNames.ECInstanceId}, ${NodeSelectQueryFactory_js_1.NodeSelectClauseColumnNames.HideNodeInHierarchy} FROM ( ${query.ecsql} ) `; const reader = this._imodelAccess.createQueryReader({ ...query, ecsql }, { rowFormat: "Indexes", limit: hierarchyLevelSizeLimit }); return (0, rxjs_1.from)(reader).pipe((0, rxjs_1.map)((row) => ({ key: { className: (0, presentation_shared_1.normalizeFullClassName)(row[0]), id: row[1], imodelKey: this._imodelAccess.imodelKey, }, hide: !!row[2], }))); }), (0, rxjs_1.mergeAll)())))); // split the instance keys observable based on whether they should be hidden or not const [visibleNodeInstanceKeys, hiddenNodeInstanceKeys] = (0, Partition_js_1.partition)(instanceKeys, ({ hide }) => !hide); // hidden items' handling: // - if a generic node is hidden, we'll want to retrieve instance keys of its children, otherwise we don't care about it, // - if an instance key is hidden, we want to create a merged node similar to what we do in `createHideNodesInHierarchyOperator` const hiddenParentNodes = (0, rxjs_1.merge)(genericDefs.pipe((0, rxjs_1.filter)((def) => !!def.node.processingParams?.hideInHierarchy), (0, rxjs_1.map)((def) => ({ type: "generic", id: def.node.key, source: this._imodelAccess.imodelKey, }))), hiddenNodeInstanceKeys.pipe( // first merge all keys by class (0, ReduceToMergeMap_js_1.reduceToMergeMapList)(({ key }) => key.className, ({ key }) => key.id), // then, for each class, create an instance key (0, rxjs_1.mergeMap)((mergedMap) => [...mergedMap.entries()].map(([className, ids]) => ({ type: "instances", instanceKeys: ids.map((id) => ({ className, id, imodelKey: this._imodelAccess.imodelKey })), }))))).pipe((0, rxjs_1.map)((key) => ({ key, parentKeys: [], label: "", }))); // merge visible instance keys from this level & the ones we get recursively requesting from deeper levels return (0, rxjs_1.merge)(visibleNodeInstanceKeys.pipe((0, rxjs_1.map)(({ key }) => key)), hiddenParentNodes.pipe((0, rxjs_1.mergeMap)((hiddenNode) => this.getNodeInstanceKeysObs({ parentNode: hiddenNode, requestContext })))).pipe((0, rxjs_1.takeUntil)(this._dispose)); } /** * Creates an iterator for all child hierarchy level instance keys, taking into account any hidden hierarchy levels * that there may be under the given parent node. */ getNodeInstanceKeys(props) { const requestContext = { requestId: core_bentley_1.Guid.createValue() }; const loggingCategory = `${LOGGING_NAMESPACE}.GetNodeInstanceKeys`; const timer = new core_bentley_1.StopWatch(undefined, true); (0, LoggingUtils_js_1.doLog)({ category: loggingCategory, message: /* c8 ignore next */ () => `[${requestContext.requestId}] Requesting keys for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}`, }); let error; let keysCount = 0; return (0, EachValueFrom_js_1.eachValueFrom)(this.getNodeInstanceKeysObs({ ...props, requestContext }).pipe((0, rxjs_1.tap)(() => ++keysCount), (0, rxjs_1.catchError)((e) => { error = e; throw e; }), (0, rxjs_1.finalize)(() => { (0, LoggingUtils_js_1.doLog)({ category: loggingCategory, message: /* c8 ignore next */ () => /* c8 ignore next 3 */ error ? `[${requestContext.requestId}] Error creating node instance keys for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)}: ${error instanceof Error ? error.message : error.toString()}` : `[${requestContext.requestId}] Returned ${keysCount} instance keys for ${(0, Common_js_1.createNodeIdentifierForLogging)(props.parentNode)} in ${timer.currentSeconds.toFixed(2)} s.`, }); }))); } } function preProcessNodes(hierarchyFactory) { return hierarchyFactory.preProcessNode ? processNodes(hierarchyFactory.preProcessNode) : rxjs_1.identity; } function postProcessNodes(hierarchyFactory) { return hierarchyFactory.postProcessNode ? processNodes(hierarchyFactory.postProcessNode) : rxjs_1.identity; } function processNodes(processor) { return (nodes) => nodes.pipe((0, rxjs_1.mergeMap)(processor)); } function applyLabelsFormatting(node, valueFormatter) { return (0, rxjs_1.from)((0, presentation_shared_1.formatConcatenatedValue)({ value: node.label, valueFormatter, })).pipe((0, rxjs_1.map)((label) => ({ ...node, label, }))); } function createParentNodeKeysList(parentNode) { if (!parentNode) { return []; } return [...parentNode.parentKeys, parentNode.key]; } function filterQueryByInstanceKeys(query, filteredInstanceKeys) { if (!filteredInstanceKeys || !filteredInstanceKeys.length) { return query; } const MAX_ALLOWED_BINDINGS = 1000; if (filteredInstanceKeys.length < MAX_ALLOWED_BINDINGS) { return { ...query, ecsql: ` SELECT * FROM (${query.ecsql}) q WHERE q.ECInstanceId IN (${filteredInstanceKeys.map(() => "?").join(",")}) `, bindings: [...(query.bindings ?? []), ...filteredInstanceKeys.map((k) => ({ type: "id", value: k.id }))], }; } /* c8 ignore start */ return { ...query, ecsql: ` SELECT * FROM (${query.ecsql}) q WHERE InVirtualSet(?, q.ECInstanceId) `, bindings: [...(query.bindings ?? []), { type: "idset", value: filteredInstanceKeys.map((k) => k.id) }], }; /* c8 ignore end */ } //# sourceMappingURL=IModelHierarchyProvider.js.map