@itwin/imodel-transformer 2.0.0-dev.7 → 2.0.0-dev.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -27
- package/lib/cjs/Algo.d.ts +0 -4
- package/lib/cjs/Algo.d.ts.map +1 -1
- package/lib/cjs/Algo.js +0 -11
- package/lib/cjs/Algo.js.map +1 -1
- package/lib/cjs/BigMap.d.ts.map +1 -1
- package/lib/cjs/BigMap.js.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
- package/lib/cjs/BranchProvenanceInitializer.js +26 -26
- package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
- package/lib/cjs/ChangedElementForest.d.ts +14 -0
- package/lib/cjs/ChangedElementForest.d.ts.map +1 -0
- package/lib/cjs/ChangedElementForest.js +86 -0
- package/lib/cjs/ChangedElementForest.js.map +1 -0
- package/lib/cjs/ChangesetScanner.d.ts +141 -0
- package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
- package/lib/cjs/ChangesetScanner.js +187 -0
- package/lib/cjs/ChangesetScanner.js.map +1 -0
- package/lib/cjs/ECReferenceTypesCache.d.ts +0 -4
- package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
- package/lib/cjs/ECReferenceTypesCache.js +61 -111
- package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -1
- package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
- package/lib/cjs/ElementAspectCleanup.d.ts +16 -0
- package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
- package/lib/cjs/ElementAspectCleanup.js +87 -0
- package/lib/cjs/ElementAspectCleanup.js.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts +44 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.js +148 -0
- package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts +44 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.js +152 -0
- package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
- package/lib/cjs/ElementBulkDelete.d.ts +30 -0
- package/lib/cjs/ElementBulkDelete.d.ts.map +1 -0
- package/lib/cjs/ElementBulkDelete.js +358 -0
- package/lib/cjs/ElementBulkDelete.js.map +1 -0
- package/lib/cjs/EntityExistenceCache.d.ts +43 -0
- package/lib/cjs/EntityExistenceCache.d.ts.map +1 -0
- package/lib/cjs/EntityExistenceCache.js +91 -0
- package/lib/cjs/EntityExistenceCache.js.map +1 -0
- package/lib/cjs/EntityUnifier.d.ts +7 -7
- package/lib/cjs/EntityUnifier.d.ts.map +1 -1
- package/lib/cjs/EntityUnifier.js +55 -24
- package/lib/cjs/EntityUnifier.js.map +1 -1
- package/lib/cjs/IModelCloneContext.d.ts +9 -3
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +56 -22
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +180 -43
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +946 -247
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +75 -30
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +292 -132
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformContext.d.ts +54 -0
- package/lib/cjs/IModelTransformContext.d.ts.map +1 -0
- package/lib/cjs/IModelTransformContext.js +3 -0
- package/lib/cjs/IModelTransformContext.js.map +1 -0
- package/lib/cjs/IModelTransformer.d.ts +184 -206
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +819 -1278
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/IModelTransformerError.d.ts +84 -0
- package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
- package/lib/cjs/IModelTransformerError.js +93 -0
- package/lib/cjs/IModelTransformerError.js.map +1 -0
- package/lib/cjs/ProvenanceManager.d.ts +177 -0
- package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
- package/lib/cjs/ProvenanceManager.js +719 -0
- package/lib/cjs/ProvenanceManager.js.map +1 -0
- package/lib/cjs/SourceClassKinds.d.ts +22 -0
- package/lib/cjs/SourceClassKinds.d.ts.map +1 -0
- package/lib/cjs/SourceClassKinds.js +60 -0
- package/lib/cjs/SourceClassKinds.js.map +1 -0
- package/lib/cjs/SyncTypeResolver.d.ts +32 -0
- package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
- package/lib/cjs/SyncTypeResolver.js +92 -0
- package/lib/cjs/SyncTypeResolver.js.map +1 -0
- package/lib/cjs/TransformerLoggerCategory.d.ts +1 -1
- package/lib/cjs/TransformerLoggerCategory.js +1 -1
- package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
- package/lib/cjs/TransformerPackageMetadata.d.ts +8 -0
- package/lib/cjs/TransformerPackageMetadata.d.ts.map +1 -0
- package/lib/cjs/TransformerPackageMetadata.js +47 -0
- package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
- package/lib/cjs/imodel-transformer.d.ts +28 -0
- package/lib/cjs/imodel-transformer.d.ts.map +1 -1
- package/lib/cjs/imodel-transformer.js +61 -34
- package/lib/cjs/imodel-transformer.js.map +1 -1
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts +29 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts.map +1 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js +212 -0
- package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts +25 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js +196 -0
- package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts +59 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts.map +1 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.js +28 -0
- package/lib/cjs/schema-processing/SchemaProcessingStrategy.js.map +1 -0
- package/lib/cjs/schema-processing.d.ts +7 -0
- package/lib/cjs/schema-processing.d.ts.map +1 -0
- package/lib/cjs/schema-processing.js +27 -0
- package/lib/cjs/schema-processing.js.map +1 -0
- package/package.json +70 -57
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -106
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ElementCascadingDeleter.d.ts +0 -22
- package/lib/cjs/ElementCascadingDeleter.d.ts.map +0 -1
- package/lib/cjs/ElementCascadingDeleter.js +0 -57
- package/lib/cjs/ElementCascadingDeleter.js.map +0 -1
- package/lib/cjs/EntityMap.d.ts +0 -26
- package/lib/cjs/EntityMap.d.ts.map +0 -1
- package/lib/cjs/EntityMap.js +0 -54
- package/lib/cjs/EntityMap.js.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
- package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
- package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -47
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +0 -1
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
4
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
|
+
*--------------------------------------------------------------------------------------------*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.planBulkDelete = planBulkDelete;
|
|
8
|
+
const core_bentley_1 = require("@itwin/core-bentley");
|
|
9
|
+
const core_common_1 = require("@itwin/core-common");
|
|
10
|
+
/** Plans native bulk deletion of the specified element trees.
|
|
11
|
+
*
|
|
12
|
+
* An element that something outside the deleted trees still references is kept, together with what it
|
|
13
|
+
* needs to stay valid, and the rest is deleted. Keeping elements can leave new outside references, so the
|
|
14
|
+
* plan repeats until none remain.
|
|
15
|
+
*
|
|
16
|
+
* Native validation checks each root against the iModel before the call, so a root is refused when its tree
|
|
17
|
+
* contains a definition, such as a category or a view, that a non-definition element in the same call still
|
|
18
|
+
* uses. The plan deletes those users in an earlier call than the definitions they use. Validation stays enabled
|
|
19
|
+
* for every call, so a reference this plan does not know about fails loudly instead of leaving a dangling
|
|
20
|
+
* reference. That includes elements outside the trees whose geometry uses a definition in them, such as a
|
|
21
|
+
* geometry part: only native code can read geometry, so those uses are not checked in advance.
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
async function planBulkDelete(targetDb, elementIds) {
|
|
25
|
+
const requestedIds = new Set(elementIds);
|
|
26
|
+
const keptReferences = new Map();
|
|
27
|
+
for (;;) {
|
|
28
|
+
const trees = await queryDeletionTrees(targetDb, requestedIds);
|
|
29
|
+
if (trees.roots.size === 0)
|
|
30
|
+
return { phases: [], keptReferences };
|
|
31
|
+
const references = await queryReferences(targetDb, trees.rootsByElement.keys());
|
|
32
|
+
const blockedIds = new Set();
|
|
33
|
+
for (const { referencingId, referencedId } of references.references) {
|
|
34
|
+
if (trees.rootsByElement.has(referencingId))
|
|
35
|
+
continue;
|
|
36
|
+
blockedIds.add(referencedId);
|
|
37
|
+
if (!keptReferences.has(referencedId))
|
|
38
|
+
keptReferences.set(referencedId, referencingId);
|
|
39
|
+
}
|
|
40
|
+
if (blockedIds.size === 0)
|
|
41
|
+
return {
|
|
42
|
+
phases: await planPhases(targetDb, trees, references),
|
|
43
|
+
keptReferences,
|
|
44
|
+
};
|
|
45
|
+
// Every blocked element is reached from a requested element, and queryKeptElements walks back up to it,
|
|
46
|
+
// so each pass removes at least one requested element and the loop ends.
|
|
47
|
+
const requestedCount = requestedIds.size;
|
|
48
|
+
for (const id of await queryKeptElements(targetDb, blockedIds))
|
|
49
|
+
requestedIds.delete(id);
|
|
50
|
+
(0, core_bentley_1.assert)(requestedIds.size < requestedCount, "Keeping referenced elements must remove a requested element");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Orders the roots of deletion trees that nothing outside them references into native calls. */
|
|
54
|
+
async function planPhases(targetDb, trees, { references, geometryDefinitionIds }) {
|
|
55
|
+
// Elements in the trees that use a definition that is also in the trees. Native validation refuses the definition while they exist.
|
|
56
|
+
const usersOfDeletedDefinitions = new Set();
|
|
57
|
+
// Roots whose trees contain such a definition. They must be deleted after its users.
|
|
58
|
+
const rootsWithUsedDefinitions = new Set();
|
|
59
|
+
const addUsedDefinition = (definitionId) => {
|
|
60
|
+
for (const rootId of trees.rootsByElement.get(definitionId) ?? [])
|
|
61
|
+
rootsWithUsedDefinitions.add(rootId);
|
|
62
|
+
};
|
|
63
|
+
const sameCallReferences = [];
|
|
64
|
+
for (const reference of references) {
|
|
65
|
+
if (reference.usersFirst) {
|
|
66
|
+
usersOfDeletedDefinitions.add(reference.referencingId);
|
|
67
|
+
addUsedDefinition(reference.referencedId);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
sameCallReferences.push(reference);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Only native code can tell which geometry uses a geometry part, material, texture, line style, or sub-category,
|
|
74
|
+
// so every geometric element in the trees counts as a user of every such definition. Deleting the trees that
|
|
75
|
+
// contain those definitions last is enough unless one of them also contains a geometric element; only then are
|
|
76
|
+
// all geometric elements queried, to delete them as roots of their own.
|
|
77
|
+
const geometryRoots = new Set();
|
|
78
|
+
for (const id of geometryDefinitionIds)
|
|
79
|
+
for (const rootId of trees.rootsByElement.get(id) ?? [])
|
|
80
|
+
geometryRoots.add(rootId);
|
|
81
|
+
if (geometryRoots.size > 0) {
|
|
82
|
+
const elementsInGeometryTrees = [...trees.rootsByElement]
|
|
83
|
+
.filter(([, rootIds]) => rootIds.some((id) => geometryRoots.has(id)))
|
|
84
|
+
.map(([id]) => id);
|
|
85
|
+
if ((await queryGeometricElements(targetDb, elementsInGeometryTrees)).length >
|
|
86
|
+
0)
|
|
87
|
+
for (const id of await queryGeometricElements(targetDb, trees.rootsByElement.keys()))
|
|
88
|
+
usersOfDeletedDefinitions.add(id);
|
|
89
|
+
for (const rootId of geometryRoots)
|
|
90
|
+
rootsWithUsedDefinitions.add(rootId);
|
|
91
|
+
}
|
|
92
|
+
if (rootsWithUsedDefinitions.size === 0)
|
|
93
|
+
return [[...trees.roots]];
|
|
94
|
+
// True when a root deleted in the last call removes the element.
|
|
95
|
+
const isInTreeWithUsedDefinition = (id) => (trees.rootsByElement.get(id) ?? []).some((rootId) => rootsWithUsedDefinitions.has(rootId));
|
|
96
|
+
// True when a root deleted in the first call removes the element. An element in overlapping trees can satisfy both checks.
|
|
97
|
+
const isInOtherTree = (id) => (trees.rootsByElement.get(id) ?? []).some((rootId) => !rootsWithUsedDefinitions.has(rootId));
|
|
98
|
+
// Deleting the other roots first works unless a user shares a tree with a used definition, or an other tree
|
|
99
|
+
// contains an element that an element deleted later references, such as its code scope.
|
|
100
|
+
const canDeleteOtherRootsFirst = ![...usersOfDeletedDefinitions].some(isInTreeWithUsedDefinition) &&
|
|
101
|
+
!sameCallReferences.some(({ referencingId, referencedId }) => isInTreeWithUsedDefinition(referencingId) && isInOtherTree(referencedId));
|
|
102
|
+
if (canDeleteOtherRootsFirst) {
|
|
103
|
+
// There are no other roots only when every tree contains a geometry definition and nothing in the trees uses a
|
|
104
|
+
// definition through a queried reference.
|
|
105
|
+
const otherRoots = [...trees.roots].filter((id) => !rootsWithUsedDefinitions.has(id));
|
|
106
|
+
return otherRoots.length > 0
|
|
107
|
+
? [otherRoots, [...rootsWithUsedDefinitions]]
|
|
108
|
+
: [[...rootsWithUsedDefinitions]];
|
|
109
|
+
}
|
|
110
|
+
// Otherwise delete the users and their code dependents as roots of their own. Core refuses to delete an
|
|
111
|
+
// element whose code scopes an element deleted in a later call, so the first call also takes code dependents
|
|
112
|
+
// that have a parent. They are all in the trees; one outside them would have kept its code scope above.
|
|
113
|
+
const userTrees = await queryDeletionTrees(targetDb, usersOfDeletedDefinitions, true);
|
|
114
|
+
const remainingRoots = [...trees.roots].filter((id) => !userTrees.rootsByElement.has(id));
|
|
115
|
+
const phases = [[...userTrees.roots]];
|
|
116
|
+
if (remainingRoots.length > 0)
|
|
117
|
+
phases.push(remainingRoots);
|
|
118
|
+
return phases;
|
|
119
|
+
}
|
|
120
|
+
/** Finds the native deletion roots and every element their deletion removes.
|
|
121
|
+
*
|
|
122
|
+
* Native deletion already cascades through child elements and modeled contents. This query follows
|
|
123
|
+
* those dependencies to find elements whose codes are scoped by an element in a deleted tree. Each
|
|
124
|
+
* code-dependent element becomes another native deletion root. Only top-level code dependents are
|
|
125
|
+
* included unless `includeParentedCodeDependents` is true.
|
|
126
|
+
*/
|
|
127
|
+
async function queryDeletionTrees(targetDb, elementIds, includeParentedCodeDependents = false) {
|
|
128
|
+
const roots = new Set();
|
|
129
|
+
// An element has more than one root only when requested trees overlap. UNION in the query already removes duplicate
|
|
130
|
+
// (element, root) pairs, so an array holds distinct roots without allocating a Set per element.
|
|
131
|
+
const rootsByElement = new Map();
|
|
132
|
+
// CascadeIds pairs each traversed element with a root that the native API must delete. The anchor
|
|
133
|
+
// adds requested elements that still exist, except those whose parent or modeled element is also
|
|
134
|
+
// requested: the recursion reaches them anyway, and anchoring them too would walk a nested tree once
|
|
135
|
+
// per requested ancestor. Recursive branches walk child elements and modeled contents while
|
|
136
|
+
// preserving DeleteRootId. An element whose code is scoped by anything already traversed becomes a
|
|
137
|
+
// new root, so recursion also covers its tree and code dependents.
|
|
138
|
+
const query = `
|
|
139
|
+
WITH RECURSIVE CascadeIds(Id, DeleteRootId) AS (
|
|
140
|
+
SELECT element.ECInstanceId, element.ECInstanceId
|
|
141
|
+
FROM bis.Element element
|
|
142
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
143
|
+
WHERE (element.Parent.Id IS NULL OR element.Parent.Id NOT IN (SELECT id FROM IdSet(:elementIds)))
|
|
144
|
+
AND element.Model.Id NOT IN (SELECT id FROM IdSet(:elementIds))
|
|
145
|
+
UNION
|
|
146
|
+
SELECT child.ECInstanceId, deletionParent.DeleteRootId
|
|
147
|
+
FROM bis.Element child
|
|
148
|
+
INNER JOIN CascadeIds deletionParent ON child.Parent.Id = deletionParent.Id
|
|
149
|
+
UNION
|
|
150
|
+
SELECT modelElement.ECInstanceId, modeledElement.DeleteRootId
|
|
151
|
+
FROM bis.Element modelElement
|
|
152
|
+
INNER JOIN CascadeIds modeledElement ON modelElement.Model.Id = modeledElement.Id
|
|
153
|
+
UNION
|
|
154
|
+
SELECT codeDependent.ECInstanceId, codeDependent.ECInstanceId
|
|
155
|
+
FROM bis.Element codeDependent
|
|
156
|
+
INNER JOIN CascadeIds scope ON codeDependent.CodeScope.Id = scope.Id
|
|
157
|
+
${includeParentedCodeDependents ? "" : "WHERE codeDependent.Parent.Id IS NULL"}
|
|
158
|
+
)
|
|
159
|
+
SELECT Id AS id, DeleteRootId AS rootId FROM CascadeIds
|
|
160
|
+
`;
|
|
161
|
+
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
162
|
+
for await (const row of targetDb.createQueryReader(query, params, {
|
|
163
|
+
usePrimaryConn: true,
|
|
164
|
+
})) {
|
|
165
|
+
roots.add(row.rootId);
|
|
166
|
+
const elementRoots = rootsByElement.get(row.id);
|
|
167
|
+
if (elementRoots)
|
|
168
|
+
elementRoots.push(row.rootId);
|
|
169
|
+
else
|
|
170
|
+
rootsByElement.set(row.id, [row.rootId]);
|
|
171
|
+
}
|
|
172
|
+
return { roots, rootsByElement };
|
|
173
|
+
}
|
|
174
|
+
/** Finds the elements to keep so that the blocked elements stay valid: each blocked element and its child
|
|
175
|
+
* elements and sub-model contents, plus every element whose deletion would delete a kept element or leave its
|
|
176
|
+
* parent, model, or code scope dangling.
|
|
177
|
+
*
|
|
178
|
+
* The whole subtree below a blocked element is kept, even when the element is blocked only as a code scope and
|
|
179
|
+
* its children could be deleted. A kept category needs its sub-categories, because the plan can't see geometry
|
|
180
|
+
* outside the trees that uses them, and keeping too much is safe.
|
|
181
|
+
*/
|
|
182
|
+
async function queryKeptElements(targetDb, blockedIds) {
|
|
183
|
+
const query = `
|
|
184
|
+
WITH RECURSIVE
|
|
185
|
+
Above(Id) AS (
|
|
186
|
+
SELECT ids.id FROM IdSet(:blockedIds) ids
|
|
187
|
+
UNION
|
|
188
|
+
SELECT element.Parent.Id FROM bis.Element element
|
|
189
|
+
INNER JOIN Above ON element.ECInstanceId = Above.Id
|
|
190
|
+
WHERE element.Parent.Id IS NOT NULL
|
|
191
|
+
UNION
|
|
192
|
+
SELECT element.Model.Id FROM bis.Element element
|
|
193
|
+
INNER JOIN Above ON element.ECInstanceId = Above.Id
|
|
194
|
+
UNION
|
|
195
|
+
SELECT element.CodeScope.Id FROM bis.Element element
|
|
196
|
+
INNER JOIN Above ON element.ECInstanceId = Above.Id
|
|
197
|
+
),
|
|
198
|
+
Below(Id) AS (
|
|
199
|
+
SELECT ids.id FROM IdSet(:blockedIds) ids
|
|
200
|
+
UNION
|
|
201
|
+
SELECT element.ECInstanceId FROM bis.Element element
|
|
202
|
+
INNER JOIN Below ON element.Parent.Id = Below.Id
|
|
203
|
+
UNION
|
|
204
|
+
SELECT element.ECInstanceId FROM bis.Element element
|
|
205
|
+
INNER JOIN Below ON element.Model.Id = Below.Id
|
|
206
|
+
)
|
|
207
|
+
SELECT Id AS id FROM Above
|
|
208
|
+
UNION
|
|
209
|
+
SELECT Id FROM Below
|
|
210
|
+
`;
|
|
211
|
+
const keptIds = new Set();
|
|
212
|
+
const params = new core_common_1.QueryBinder().bindIdSet("blockedIds", blockedIds);
|
|
213
|
+
for await (const row of targetDb.createQueryReader(query, params, {
|
|
214
|
+
usePrimaryConn: true,
|
|
215
|
+
}))
|
|
216
|
+
keptIds.add(row.id);
|
|
217
|
+
return keptIds;
|
|
218
|
+
}
|
|
219
|
+
/** Finds BisCore references to the specified elements that native deletion validates and that [[queryDeletionTrees]]
|
|
220
|
+
* does not follow. Parent and model references are not included because the deleted trees contain every child and
|
|
221
|
+
* modeled element.
|
|
222
|
+
*/
|
|
223
|
+
async function queryReferences(targetDb, elementIds) {
|
|
224
|
+
const references = [];
|
|
225
|
+
const geometryDefinitionIds = [];
|
|
226
|
+
// Any element can be a code scope. Every other validated reference targets a definition element, so those are
|
|
227
|
+
// queried only for the definitions among the elements, which are usually few or none.
|
|
228
|
+
const definitionIds = [];
|
|
229
|
+
const firstQuery = `
|
|
230
|
+
SELECT element.ECInstanceId AS referencingId, element.CodeScope.Id AS referencedId, 0 AS isDefinition
|
|
231
|
+
FROM bis.Element element
|
|
232
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.CodeScope.Id
|
|
233
|
+
UNION ALL
|
|
234
|
+
SELECT definition.ECInstanceId, definition.ECInstanceId, 1
|
|
235
|
+
FROM bis.DefinitionElement definition
|
|
236
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = definition.ECInstanceId
|
|
237
|
+
`;
|
|
238
|
+
for await (const row of targetDb.createQueryReader(firstQuery, new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds), { usePrimaryConn: true })) {
|
|
239
|
+
if (row.isDefinition === 1)
|
|
240
|
+
definitionIds.push(row.referencedId);
|
|
241
|
+
else
|
|
242
|
+
references.push({
|
|
243
|
+
referencingId: row.referencingId,
|
|
244
|
+
referencedId: row.referencedId,
|
|
245
|
+
usersFirst: false,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (definitionIds.length === 0)
|
|
249
|
+
return { references, geometryDefinitionIds };
|
|
250
|
+
// Classes and properties added in later BisCore versions.
|
|
251
|
+
const sectionReferences = await querySectionViewReferences(targetDb);
|
|
252
|
+
// Kind 1: native validation refuses the definition while the referencing element exists, even in the same call.
|
|
253
|
+
// Kind 0: deleting both in the same call is enough. Native validation treats a default sub-category, whose ID
|
|
254
|
+
// follows its category's, as used by the category unless both are deleted.
|
|
255
|
+
// Kind 2: a definition that geometry references, so its users can't be queried.
|
|
256
|
+
const definitionQuery = `
|
|
257
|
+
SELECT element.ECInstanceId AS referencingId, element.Category.Id AS referencedId, 1 AS kind
|
|
258
|
+
FROM bis.GeometricElement3d element
|
|
259
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id
|
|
260
|
+
UNION ALL
|
|
261
|
+
SELECT element.ECInstanceId, element.Category.Id, 1
|
|
262
|
+
FROM bis.GeometricElement2d element
|
|
263
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id
|
|
264
|
+
UNION ALL
|
|
265
|
+
SELECT element.ECInstanceId, element.View.Id, 1
|
|
266
|
+
FROM bis.ViewAttachment element
|
|
267
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.View.Id
|
|
268
|
+
${sectionReferences
|
|
269
|
+
.map(({ className, propertyName }) => `
|
|
270
|
+
UNION ALL
|
|
271
|
+
SELECT element.ECInstanceId, element.${propertyName}.Id, 1
|
|
272
|
+
FROM bis.${className} element
|
|
273
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.${propertyName}.Id`)
|
|
274
|
+
.join("")}
|
|
275
|
+
UNION ALL
|
|
276
|
+
SELECT viewDefinition.ECInstanceId, viewDefinition.DisplayStyle.Id, 0
|
|
277
|
+
FROM bis.ViewDefinition viewDefinition
|
|
278
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.DisplayStyle.Id
|
|
279
|
+
UNION ALL
|
|
280
|
+
SELECT viewDefinition.ECInstanceId, viewDefinition.CategorySelector.Id, 0
|
|
281
|
+
FROM bis.ViewDefinition viewDefinition
|
|
282
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.CategorySelector.Id
|
|
283
|
+
UNION ALL
|
|
284
|
+
SELECT viewDefinition.ECInstanceId, viewDefinition.ModelSelector.Id, 0
|
|
285
|
+
FROM bis.SpatialViewDefinition viewDefinition
|
|
286
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.ModelSelector.Id
|
|
287
|
+
UNION ALL
|
|
288
|
+
SELECT subCategory.Parent.Id, subCategory.ECInstanceId, 0
|
|
289
|
+
FROM bis.SubCategory subCategory
|
|
290
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId
|
|
291
|
+
WHERE CAST(subCategory.ECInstanceId AS INTEGER) = CAST(subCategory.Parent.Id AS INTEGER) + 1
|
|
292
|
+
UNION ALL
|
|
293
|
+
SELECT subCategory.ECInstanceId, subCategory.ECInstanceId, 2
|
|
294
|
+
FROM bis.SubCategory subCategory
|
|
295
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId
|
|
296
|
+
WHERE CAST(subCategory.ECInstanceId AS INTEGER) <> CAST(subCategory.Parent.Id AS INTEGER) + 1
|
|
297
|
+
${["GeometryPart", "RenderMaterial", "Texture", "LineStyle"]
|
|
298
|
+
.map((className) => `
|
|
299
|
+
UNION ALL
|
|
300
|
+
SELECT element.ECInstanceId, element.ECInstanceId, 2
|
|
301
|
+
FROM bis.${className} element
|
|
302
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId`)
|
|
303
|
+
.join("")}
|
|
304
|
+
`;
|
|
305
|
+
for await (const row of targetDb.createQueryReader(definitionQuery, new core_common_1.QueryBinder().bindIdSet("elementIds", definitionIds), { usePrimaryConn: true })) {
|
|
306
|
+
if (row.kind === 2)
|
|
307
|
+
geometryDefinitionIds.push(row.referencedId);
|
|
308
|
+
else
|
|
309
|
+
references.push({
|
|
310
|
+
referencingId: row.referencingId,
|
|
311
|
+
referencedId: row.referencedId,
|
|
312
|
+
usersFirst: row.kind === 1,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return { references, geometryDefinitionIds };
|
|
316
|
+
}
|
|
317
|
+
/** Finds the BisCore section classes and view properties that exist in the iModel's schema. */
|
|
318
|
+
async function querySectionViewReferences(targetDb) {
|
|
319
|
+
const query = `
|
|
320
|
+
SELECT classDef.Name AS className, propertyDef.Name AS propertyName
|
|
321
|
+
FROM meta.ECPropertyDef propertyDef
|
|
322
|
+
INNER JOIN meta.ECClassDef classDef ON classDef.ECInstanceId = propertyDef.Class.Id
|
|
323
|
+
INNER JOIN meta.ECSchemaDef schemaDef ON schemaDef.ECInstanceId = classDef.Schema.Id
|
|
324
|
+
WHERE schemaDef.Name = 'BisCore'
|
|
325
|
+
AND ((classDef.Name = 'SectionDrawing' AND propertyDef.Name = 'SpatialView')
|
|
326
|
+
OR (classDef.Name = 'SectionDrawingLocation' AND propertyDef.Name = 'SectionView'))
|
|
327
|
+
`;
|
|
328
|
+
const references = [];
|
|
329
|
+
for await (const row of targetDb.createQueryReader(query, undefined, {
|
|
330
|
+
usePrimaryConn: true,
|
|
331
|
+
}))
|
|
332
|
+
references.push({
|
|
333
|
+
className: row.className,
|
|
334
|
+
propertyName: row.propertyName,
|
|
335
|
+
});
|
|
336
|
+
return references;
|
|
337
|
+
}
|
|
338
|
+
/** Finds the 2D and 3D geometric elements among the specified elements. */
|
|
339
|
+
async function queryGeometricElements(targetDb, elementIds) {
|
|
340
|
+
// Joining the abstract bis.GeometricElement with an IdSet is quadratic, so query its 3D and 2D subclasses.
|
|
341
|
+
const query = `
|
|
342
|
+
SELECT element.ECInstanceId AS id
|
|
343
|
+
FROM bis.GeometricElement3d element
|
|
344
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
345
|
+
UNION ALL
|
|
346
|
+
SELECT element.ECInstanceId
|
|
347
|
+
FROM bis.GeometricElement2d element
|
|
348
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
349
|
+
`;
|
|
350
|
+
const ids = [];
|
|
351
|
+
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
352
|
+
for await (const row of targetDb.createQueryReader(query, params, {
|
|
353
|
+
usePrimaryConn: true,
|
|
354
|
+
}))
|
|
355
|
+
ids.push(row.id);
|
|
356
|
+
return ids;
|
|
357
|
+
}
|
|
358
|
+
//# sourceMappingURL=ElementBulkDelete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElementBulkDelete.js","sourceRoot":"","sources":["../../src/ElementBulkDelete.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { assert, Id64Array, Id64Set, Id64String } from \"@itwin/core-bentley\";\nimport { QueryBinder } from \"@itwin/core-common\";\nimport { IModelDb } from \"@itwin/core-backend\";\n\n/** Native deletion calls needed to delete a set of element trees without a partial result.\n * @internal\n */\nexport interface BulkDeletePlan {\n /** Roots for each native deletion call, in execution order. Empty when nothing needs deleting. */\n readonly phases: readonly Id64Array[];\n /** Elements kept because an element outside the deleted trees still references them, each mapped to one such\n * referencing element. Their child elements and sub-models, and the elements whose deletion would delete them,\n * are kept too.\n */\n readonly keptReferences: ReadonlyMap<Id64String, Id64String>;\n}\n\ninterface DeletionTrees {\n /** Roots that the native API must receive. */\n readonly roots: Id64Set;\n /** Every element that deleting the roots removes, mapped to the roots whose trees contain it. */\n readonly rootsByElement: ReadonlyMap<Id64String, readonly Id64String[]>;\n}\n\n/** Plans native bulk deletion of the specified element trees.\n *\n * An element that something outside the deleted trees still references is kept, together with what it\n * needs to stay valid, and the rest is deleted. Keeping elements can leave new outside references, so the\n * plan repeats until none remain.\n *\n * Native validation checks each root against the iModel before the call, so a root is refused when its tree\n * contains a definition, such as a category or a view, that a non-definition element in the same call still\n * uses. The plan deletes those users in an earlier call than the definitions they use. Validation stays enabled\n * for every call, so a reference this plan does not know about fails loudly instead of leaving a dangling\n * reference. That includes elements outside the trees whose geometry uses a definition in them, such as a\n * geometry part: only native code can read geometry, so those uses are not checked in advance.\n * @internal\n */\nexport async function planBulkDelete(\n targetDb: IModelDb,\n elementIds: ReadonlySet<Id64String>\n): Promise<BulkDeletePlan> {\n const requestedIds = new Set(elementIds);\n const keptReferences = new Map<Id64String, Id64String>();\n for (;;) {\n const trees = await queryDeletionTrees(targetDb, requestedIds);\n if (trees.roots.size === 0) return { phases: [], keptReferences };\n\n const references = await queryReferences(\n targetDb,\n trees.rootsByElement.keys()\n );\n const blockedIds = new Set<Id64String>();\n for (const { referencingId, referencedId } of references.references) {\n if (trees.rootsByElement.has(referencingId)) continue;\n blockedIds.add(referencedId);\n if (!keptReferences.has(referencedId))\n keptReferences.set(referencedId, referencingId);\n }\n if (blockedIds.size === 0)\n return {\n phases: await planPhases(targetDb, trees, references),\n keptReferences,\n };\n\n // Every blocked element is reached from a requested element, and queryKeptElements walks back up to it,\n // so each pass removes at least one requested element and the loop ends.\n const requestedCount = requestedIds.size;\n for (const id of await queryKeptElements(targetDb, blockedIds))\n requestedIds.delete(id);\n assert(\n requestedIds.size < requestedCount,\n \"Keeping referenced elements must remove a requested element\"\n );\n }\n}\n\n/** Orders the roots of deletion trees that nothing outside them references into native calls. */\nasync function planPhases(\n targetDb: IModelDb,\n trees: DeletionTrees,\n { references, geometryDefinitionIds }: TreeReferences\n): Promise<Id64Array[]> {\n // Elements in the trees that use a definition that is also in the trees. Native validation refuses the definition while they exist.\n const usersOfDeletedDefinitions = new Set<Id64String>();\n // Roots whose trees contain such a definition. They must be deleted after its users.\n const rootsWithUsedDefinitions = new Set<Id64String>();\n const addUsedDefinition = (definitionId: Id64String) => {\n for (const rootId of trees.rootsByElement.get(definitionId) ?? [])\n rootsWithUsedDefinitions.add(rootId);\n };\n const sameCallReferences: TreeReference[] = [];\n for (const reference of references) {\n if (reference.usersFirst) {\n usersOfDeletedDefinitions.add(reference.referencingId);\n addUsedDefinition(reference.referencedId);\n } else {\n sameCallReferences.push(reference);\n }\n }\n // Only native code can tell which geometry uses a geometry part, material, texture, line style, or sub-category,\n // so every geometric element in the trees counts as a user of every such definition. Deleting the trees that\n // contain those definitions last is enough unless one of them also contains a geometric element; only then are\n // all geometric elements queried, to delete them as roots of their own.\n const geometryRoots = new Set<Id64String>();\n for (const id of geometryDefinitionIds)\n for (const rootId of trees.rootsByElement.get(id) ?? [])\n geometryRoots.add(rootId);\n if (geometryRoots.size > 0) {\n const elementsInGeometryTrees = [...trees.rootsByElement]\n .filter(([, rootIds]) => rootIds.some((id) => geometryRoots.has(id)))\n .map(([id]) => id);\n if (\n (await queryGeometricElements(targetDb, elementsInGeometryTrees)).length >\n 0\n )\n for (const id of await queryGeometricElements(\n targetDb,\n trees.rootsByElement.keys()\n ))\n usersOfDeletedDefinitions.add(id);\n for (const rootId of geometryRoots) rootsWithUsedDefinitions.add(rootId);\n }\n if (rootsWithUsedDefinitions.size === 0) return [[...trees.roots]];\n // True when a root deleted in the last call removes the element.\n const isInTreeWithUsedDefinition = (id: Id64String) =>\n (trees.rootsByElement.get(id) ?? []).some((rootId) =>\n rootsWithUsedDefinitions.has(rootId)\n );\n // True when a root deleted in the first call removes the element. An element in overlapping trees can satisfy both checks.\n const isInOtherTree = (id: Id64String) =>\n (trees.rootsByElement.get(id) ?? []).some(\n (rootId) => !rootsWithUsedDefinitions.has(rootId)\n );\n // Deleting the other roots first works unless a user shares a tree with a used definition, or an other tree\n // contains an element that an element deleted later references, such as its code scope.\n const canDeleteOtherRootsFirst =\n ![...usersOfDeletedDefinitions].some(isInTreeWithUsedDefinition) &&\n !sameCallReferences.some(\n ({ referencingId, referencedId }) =>\n isInTreeWithUsedDefinition(referencingId) && isInOtherTree(referencedId)\n );\n if (canDeleteOtherRootsFirst) {\n // There are no other roots only when every tree contains a geometry definition and nothing in the trees uses a\n // definition through a queried reference.\n const otherRoots = [...trees.roots].filter(\n (id) => !rootsWithUsedDefinitions.has(id)\n );\n return otherRoots.length > 0\n ? [otherRoots, [...rootsWithUsedDefinitions]]\n : [[...rootsWithUsedDefinitions]];\n }\n\n // Otherwise delete the users and their code dependents as roots of their own. Core refuses to delete an\n // element whose code scopes an element deleted in a later call, so the first call also takes code dependents\n // that have a parent. They are all in the trees; one outside them would have kept its code scope above.\n const userTrees = await queryDeletionTrees(\n targetDb,\n usersOfDeletedDefinitions,\n true\n );\n const remainingRoots = [...trees.roots].filter(\n (id) => !userTrees.rootsByElement.has(id)\n );\n const phases = [[...userTrees.roots]];\n if (remainingRoots.length > 0) phases.push(remainingRoots);\n return phases;\n}\n\n/** Finds the native deletion roots and every element their deletion removes.\n *\n * Native deletion already cascades through child elements and modeled contents. This query follows\n * those dependencies to find elements whose codes are scoped by an element in a deleted tree. Each\n * code-dependent element becomes another native deletion root. Only top-level code dependents are\n * included unless `includeParentedCodeDependents` is true.\n */\nasync function queryDeletionTrees(\n targetDb: IModelDb,\n elementIds: ReadonlySet<Id64String>,\n includeParentedCodeDependents = false\n): Promise<DeletionTrees> {\n const roots: Id64Set = new Set<Id64String>();\n // An element has more than one root only when requested trees overlap. UNION in the query already removes duplicate\n // (element, root) pairs, so an array holds distinct roots without allocating a Set per element.\n const rootsByElement = new Map<Id64String, Id64String[]>();\n // CascadeIds pairs each traversed element with a root that the native API must delete. The anchor\n // adds requested elements that still exist, except those whose parent or modeled element is also\n // requested: the recursion reaches them anyway, and anchoring them too would walk a nested tree once\n // per requested ancestor. Recursive branches walk child elements and modeled contents while\n // preserving DeleteRootId. An element whose code is scoped by anything already traversed becomes a\n // new root, so recursion also covers its tree and code dependents.\n const query = `\n WITH RECURSIVE CascadeIds(Id, DeleteRootId) AS (\n SELECT element.ECInstanceId, element.ECInstanceId\n FROM bis.Element element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId\n WHERE (element.Parent.Id IS NULL OR element.Parent.Id NOT IN (SELECT id FROM IdSet(:elementIds)))\n AND element.Model.Id NOT IN (SELECT id FROM IdSet(:elementIds))\n UNION\n SELECT child.ECInstanceId, deletionParent.DeleteRootId\n FROM bis.Element child\n INNER JOIN CascadeIds deletionParent ON child.Parent.Id = deletionParent.Id\n UNION\n SELECT modelElement.ECInstanceId, modeledElement.DeleteRootId\n FROM bis.Element modelElement\n INNER JOIN CascadeIds modeledElement ON modelElement.Model.Id = modeledElement.Id\n UNION\n SELECT codeDependent.ECInstanceId, codeDependent.ECInstanceId\n FROM bis.Element codeDependent\n INNER JOIN CascadeIds scope ON codeDependent.CodeScope.Id = scope.Id\n ${includeParentedCodeDependents ? \"\" : \"WHERE codeDependent.Parent.Id IS NULL\"}\n )\n SELECT Id AS id, DeleteRootId AS rootId FROM CascadeIds\n `;\n const params = new QueryBinder().bindIdSet(\"elementIds\", elementIds);\n for await (const row of targetDb.createQueryReader(query, params, {\n usePrimaryConn: true,\n })) {\n roots.add(row.rootId);\n const elementRoots = rootsByElement.get(row.id);\n if (elementRoots) elementRoots.push(row.rootId);\n else rootsByElement.set(row.id, [row.rootId]);\n }\n return { roots, rootsByElement };\n}\n\n/** Finds the elements to keep so that the blocked elements stay valid: each blocked element and its child\n * elements and sub-model contents, plus every element whose deletion would delete a kept element or leave its\n * parent, model, or code scope dangling.\n *\n * The whole subtree below a blocked element is kept, even when the element is blocked only as a code scope and\n * its children could be deleted. A kept category needs its sub-categories, because the plan can't see geometry\n * outside the trees that uses them, and keeping too much is safe.\n */\nasync function queryKeptElements(\n targetDb: IModelDb,\n blockedIds: ReadonlySet<Id64String>\n): Promise<Id64Set> {\n const query = `\n WITH RECURSIVE\n Above(Id) AS (\n SELECT ids.id FROM IdSet(:blockedIds) ids\n UNION\n SELECT element.Parent.Id FROM bis.Element element\n INNER JOIN Above ON element.ECInstanceId = Above.Id\n WHERE element.Parent.Id IS NOT NULL\n UNION\n SELECT element.Model.Id FROM bis.Element element\n INNER JOIN Above ON element.ECInstanceId = Above.Id\n UNION\n SELECT element.CodeScope.Id FROM bis.Element element\n INNER JOIN Above ON element.ECInstanceId = Above.Id\n ),\n Below(Id) AS (\n SELECT ids.id FROM IdSet(:blockedIds) ids\n UNION\n SELECT element.ECInstanceId FROM bis.Element element\n INNER JOIN Below ON element.Parent.Id = Below.Id\n UNION\n SELECT element.ECInstanceId FROM bis.Element element\n INNER JOIN Below ON element.Model.Id = Below.Id\n )\n SELECT Id AS id FROM Above\n UNION\n SELECT Id FROM Below\n `;\n const keptIds: Id64Set = new Set<Id64String>();\n const params = new QueryBinder().bindIdSet(\"blockedIds\", blockedIds);\n for await (const row of targetDb.createQueryReader(query, params, {\n usePrimaryConn: true,\n }))\n keptIds.add(row.id);\n return keptIds;\n}\n\ninterface TreeReference {\n readonly referencingId: Id64String;\n readonly referencedId: Id64String;\n /** True when native validation refuses the referenced element while the referencing element exists, even when\n * both are deleted in the same call. False when deleting them in the same call is enough.\n */\n readonly usersFirst: boolean;\n}\n\ninterface TreeReferences {\n readonly references: readonly TreeReference[];\n /** Geometry parts, materials, textures, line styles, and non-default sub-categories in the trees. Geometry\n * references them, so their users can't be queried.\n */\n readonly geometryDefinitionIds: readonly Id64String[];\n}\n\n/** Finds BisCore references to the specified elements that native deletion validates and that [[queryDeletionTrees]]\n * does not follow. Parent and model references are not included because the deleted trees contain every child and\n * modeled element.\n */\nasync function queryReferences(\n targetDb: IModelDb,\n elementIds: Iterable<Id64String>\n): Promise<TreeReferences> {\n const references: TreeReference[] = [];\n const geometryDefinitionIds: Id64String[] = [];\n // Any element can be a code scope. Every other validated reference targets a definition element, so those are\n // queried only for the definitions among the elements, which are usually few or none.\n const definitionIds: Id64String[] = [];\n const firstQuery = `\n SELECT element.ECInstanceId AS referencingId, element.CodeScope.Id AS referencedId, 0 AS isDefinition\n FROM bis.Element element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.CodeScope.Id\n UNION ALL\n SELECT definition.ECInstanceId, definition.ECInstanceId, 1\n FROM bis.DefinitionElement definition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = definition.ECInstanceId\n `;\n for await (const row of targetDb.createQueryReader(\n firstQuery,\n new QueryBinder().bindIdSet(\"elementIds\", elementIds),\n { usePrimaryConn: true }\n )) {\n if (row.isDefinition === 1) definitionIds.push(row.referencedId);\n else\n references.push({\n referencingId: row.referencingId,\n referencedId: row.referencedId,\n usersFirst: false,\n });\n }\n if (definitionIds.length === 0) return { references, geometryDefinitionIds };\n\n // Classes and properties added in later BisCore versions.\n const sectionReferences = await querySectionViewReferences(targetDb);\n // Kind 1: native validation refuses the definition while the referencing element exists, even in the same call.\n // Kind 0: deleting both in the same call is enough. Native validation treats a default sub-category, whose ID\n // follows its category's, as used by the category unless both are deleted.\n // Kind 2: a definition that geometry references, so its users can't be queried.\n const definitionQuery = `\n SELECT element.ECInstanceId AS referencingId, element.Category.Id AS referencedId, 1 AS kind\n FROM bis.GeometricElement3d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id\n UNION ALL\n SELECT element.ECInstanceId, element.Category.Id, 1\n FROM bis.GeometricElement2d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.Category.Id\n UNION ALL\n SELECT element.ECInstanceId, element.View.Id, 1\n FROM bis.ViewAttachment element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.View.Id\n ${sectionReferences\n .map(\n ({ className, propertyName }) => `\n UNION ALL\n SELECT element.ECInstanceId, element.${propertyName}.Id, 1\n FROM bis.${className} element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.${propertyName}.Id`\n )\n .join(\"\")}\n UNION ALL\n SELECT viewDefinition.ECInstanceId, viewDefinition.DisplayStyle.Id, 0\n FROM bis.ViewDefinition viewDefinition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.DisplayStyle.Id\n UNION ALL\n SELECT viewDefinition.ECInstanceId, viewDefinition.CategorySelector.Id, 0\n FROM bis.ViewDefinition viewDefinition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.CategorySelector.Id\n UNION ALL\n SELECT viewDefinition.ECInstanceId, viewDefinition.ModelSelector.Id, 0\n FROM bis.SpatialViewDefinition viewDefinition\n INNER JOIN IdSet(:elementIds) ids ON ids.id = viewDefinition.ModelSelector.Id\n UNION ALL\n SELECT subCategory.Parent.Id, subCategory.ECInstanceId, 0\n FROM bis.SubCategory subCategory\n INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId\n WHERE CAST(subCategory.ECInstanceId AS INTEGER) = CAST(subCategory.Parent.Id AS INTEGER) + 1\n UNION ALL\n SELECT subCategory.ECInstanceId, subCategory.ECInstanceId, 2\n FROM bis.SubCategory subCategory\n INNER JOIN IdSet(:elementIds) ids ON ids.id = subCategory.ECInstanceId\n WHERE CAST(subCategory.ECInstanceId AS INTEGER) <> CAST(subCategory.Parent.Id AS INTEGER) + 1\n ${[\"GeometryPart\", \"RenderMaterial\", \"Texture\", \"LineStyle\"]\n .map(\n (className) => `\n UNION ALL\n SELECT element.ECInstanceId, element.ECInstanceId, 2\n FROM bis.${className} element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId`\n )\n .join(\"\")}\n `;\n for await (const row of targetDb.createQueryReader(\n definitionQuery,\n new QueryBinder().bindIdSet(\"elementIds\", definitionIds),\n { usePrimaryConn: true }\n )) {\n if (row.kind === 2) geometryDefinitionIds.push(row.referencedId);\n else\n references.push({\n referencingId: row.referencingId,\n referencedId: row.referencedId,\n usersFirst: row.kind === 1,\n });\n }\n return { references, geometryDefinitionIds };\n}\n\n/** Finds the BisCore section classes and view properties that exist in the iModel's schema. */\nasync function querySectionViewReferences(\n targetDb: IModelDb\n): Promise<{ className: string; propertyName: string }[]> {\n const query = `\n SELECT classDef.Name AS className, propertyDef.Name AS propertyName\n FROM meta.ECPropertyDef propertyDef\n INNER JOIN meta.ECClassDef classDef ON classDef.ECInstanceId = propertyDef.Class.Id\n INNER JOIN meta.ECSchemaDef schemaDef ON schemaDef.ECInstanceId = classDef.Schema.Id\n WHERE schemaDef.Name = 'BisCore'\n AND ((classDef.Name = 'SectionDrawing' AND propertyDef.Name = 'SpatialView')\n OR (classDef.Name = 'SectionDrawingLocation' AND propertyDef.Name = 'SectionView'))\n `;\n const references: { className: string; propertyName: string }[] = [];\n for await (const row of targetDb.createQueryReader(query, undefined, {\n usePrimaryConn: true,\n }))\n references.push({\n className: row.className,\n propertyName: row.propertyName,\n });\n return references;\n}\n\n/** Finds the 2D and 3D geometric elements among the specified elements. */\nasync function queryGeometricElements(\n targetDb: IModelDb,\n elementIds: Iterable<Id64String>\n): Promise<Id64String[]> {\n // Joining the abstract bis.GeometricElement with an IdSet is quadratic, so query its 3D and 2D subclasses.\n const query = `\n SELECT element.ECInstanceId AS id\n FROM bis.GeometricElement3d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId\n UNION ALL\n SELECT element.ECInstanceId\n FROM bis.GeometricElement2d element\n INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId\n `;\n const ids: Id64String[] = [];\n const params = new QueryBinder().bindIdSet(\"elementIds\", elementIds);\n for await (const row of targetDb.createQueryReader(query, params, {\n usePrimaryConn: true,\n }))\n ids.push(row.id);\n return ids;\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** @packageDocumentation
|
|
2
|
+
* @module Utils
|
|
3
|
+
*/
|
|
4
|
+
import { EntityReference } from "@itwin/core-common";
|
|
5
|
+
import { IModelDb } from "@itwin/core-backend";
|
|
6
|
+
/** A positive-only read-through cache of entity existence checks, scoped to a single transformation run.
|
|
7
|
+
*
|
|
8
|
+
* Entities are created continuously as a transformation progresses, so a negative
|
|
9
|
+
* ("does not exist") result may become stale at any moment — therefore only positive
|
|
10
|
+
* results are ever cached. Positive results can only be invalidated by deletes, which the
|
|
11
|
+
* transformer routes through [[invalidate]] and [[clearDb]].
|
|
12
|
+
*
|
|
13
|
+
* The cache is keyed by [IModelDb]($core-backend) instance, so same-iModel transformations
|
|
14
|
+
* (where sourceDb === targetDb) share a single set and source/target roles cannot be confused.
|
|
15
|
+
*
|
|
16
|
+
* [IModelImporter]($transformer) delete operations invalidate registered caches. Deletes performed
|
|
17
|
+
* directly through an `EditTxn` while a transformation is active are out-of-band and must not be
|
|
18
|
+
* mixed with transformer operations. A successful high-level `process()` clears the cache when the
|
|
19
|
+
* transformation finalizes; low-level or failed transformations clear it when the transformer is
|
|
20
|
+
* disposed.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export declare class EntityExistenceCache {
|
|
24
|
+
private _existsByDb;
|
|
25
|
+
private getDbSet;
|
|
26
|
+
/** Check whether an entity exists in `db`, consulting the cache first.
|
|
27
|
+
* A `true` result is cached; a `false` result is never cached and will be re-queried.
|
|
28
|
+
*/
|
|
29
|
+
exists(db: IModelDb, entityReference: EntityReference): Promise<boolean>;
|
|
30
|
+
/** Read through the cache to check which of the supplied entities exist in `db`, populating it on a miss. */
|
|
31
|
+
existsAll(db: IModelDb, entityReferences: Iterable<EntityReference>): Promise<ReadonlySet<EntityReference>>;
|
|
32
|
+
/** Record that an entity is known to exist in `db` (e.g. it was just imported). */
|
|
33
|
+
markExists(db: IModelDb, entityReference: EntityReference): void;
|
|
34
|
+
/** Remove a single entity from the cache for `db`, forcing a re-query on next check. */
|
|
35
|
+
invalidate(db: IModelDb, entityReference: EntityReference): void;
|
|
36
|
+
/** Drop all cached results for `db`. Used when a delete may cascade to entities the
|
|
37
|
+
* caller cannot enumerate (e.g. element tree deletes that remove descendants and submodels).
|
|
38
|
+
*/
|
|
39
|
+
clearDb(db: IModelDb): void;
|
|
40
|
+
/** Drop all cached results. */
|
|
41
|
+
clear(): void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=EntityExistenceCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityExistenceCache.d.ts","sourceRoot":"","sources":["../../src/EntityExistenceCache.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,WAAW,CAA6C;IAEhE,OAAO,CAAC,QAAQ;IAShB;;OAEG;IACU,MAAM,CACjB,EAAE,EAAE,QAAQ,EACZ,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,OAAO,CAAC,CAMlB;IAED,6GAA6G;IAChG,SAAS,CACpB,EAAE,EAAE,QAAQ,EACZ,gBAAgB,EAAE,QAAQ,CAAC,eAAe,CAAC,GAC1C,OAAO,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,CAkBvC;IAED,mFAAmF;IAC5E,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,GAAG,IAAI,CAEtE;IAED,wFAAwF;IACjF,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,GAAG,IAAI,CAEtE;IAED;;OAEG;IACI,OAAO,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAEjC;IAED,+BAA+B;IACxB,KAAK,IAAI,IAAI,CAEnB;CACF"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
4
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
|
+
*--------------------------------------------------------------------------------------------*/
|
|
6
|
+
/** @packageDocumentation
|
|
7
|
+
* @module Utils
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.EntityExistenceCache = void 0;
|
|
11
|
+
const EntityUnifier_1 = require("./EntityUnifier");
|
|
12
|
+
/** A positive-only read-through cache of entity existence checks, scoped to a single transformation run.
|
|
13
|
+
*
|
|
14
|
+
* Entities are created continuously as a transformation progresses, so a negative
|
|
15
|
+
* ("does not exist") result may become stale at any moment — therefore only positive
|
|
16
|
+
* results are ever cached. Positive results can only be invalidated by deletes, which the
|
|
17
|
+
* transformer routes through [[invalidate]] and [[clearDb]].
|
|
18
|
+
*
|
|
19
|
+
* The cache is keyed by [IModelDb]($core-backend) instance, so same-iModel transformations
|
|
20
|
+
* (where sourceDb === targetDb) share a single set and source/target roles cannot be confused.
|
|
21
|
+
*
|
|
22
|
+
* [IModelImporter]($transformer) delete operations invalidate registered caches. Deletes performed
|
|
23
|
+
* directly through an `EditTxn` while a transformation is active are out-of-band and must not be
|
|
24
|
+
* mixed with transformer operations. A successful high-level `process()` clears the cache when the
|
|
25
|
+
* transformation finalizes; low-level or failed transformations clear it when the transformer is
|
|
26
|
+
* disposed.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
class EntityExistenceCache {
|
|
30
|
+
_existsByDb = new Map();
|
|
31
|
+
getDbSet(db) {
|
|
32
|
+
let set = this._existsByDb.get(db);
|
|
33
|
+
if (set === undefined) {
|
|
34
|
+
set = new Set();
|
|
35
|
+
this._existsByDb.set(db, set);
|
|
36
|
+
}
|
|
37
|
+
return set;
|
|
38
|
+
}
|
|
39
|
+
/** Check whether an entity exists in `db`, consulting the cache first.
|
|
40
|
+
* A `true` result is cached; a `false` result is never cached and will be re-queried.
|
|
41
|
+
*/
|
|
42
|
+
async exists(db, entityReference) {
|
|
43
|
+
const set = this.getDbSet(db);
|
|
44
|
+
if (set.has(entityReference))
|
|
45
|
+
return true;
|
|
46
|
+
const found = await EntityUnifier_1.EntityUnifier.exists(db, { entityReference });
|
|
47
|
+
if (found)
|
|
48
|
+
set.add(entityReference);
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
/** Read through the cache to check which of the supplied entities exist in `db`, populating it on a miss. */
|
|
52
|
+
async existsAll(db, entityReferences) {
|
|
53
|
+
const dbSet = this.getDbSet(db);
|
|
54
|
+
const found = new Set();
|
|
55
|
+
const unverified = [];
|
|
56
|
+
for (const entityReference of entityReferences) {
|
|
57
|
+
if (dbSet.has(entityReference))
|
|
58
|
+
found.add(entityReference);
|
|
59
|
+
else
|
|
60
|
+
unverified.push(entityReference);
|
|
61
|
+
}
|
|
62
|
+
if (unverified.length === 0)
|
|
63
|
+
return found;
|
|
64
|
+
const queried = await EntityUnifier_1.EntityUnifier.existsAll(db, unverified);
|
|
65
|
+
for (const entityReference of queried) {
|
|
66
|
+
dbSet.add(entityReference);
|
|
67
|
+
found.add(entityReference);
|
|
68
|
+
}
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
/** Record that an entity is known to exist in `db` (e.g. it was just imported). */
|
|
72
|
+
markExists(db, entityReference) {
|
|
73
|
+
this.getDbSet(db).add(entityReference);
|
|
74
|
+
}
|
|
75
|
+
/** Remove a single entity from the cache for `db`, forcing a re-query on next check. */
|
|
76
|
+
invalidate(db, entityReference) {
|
|
77
|
+
this._existsByDb.get(db)?.delete(entityReference);
|
|
78
|
+
}
|
|
79
|
+
/** Drop all cached results for `db`. Used when a delete may cascade to entities the
|
|
80
|
+
* caller cannot enumerate (e.g. element tree deletes that remove descendants and submodels).
|
|
81
|
+
*/
|
|
82
|
+
clearDb(db) {
|
|
83
|
+
this._existsByDb.delete(db);
|
|
84
|
+
}
|
|
85
|
+
/** Drop all cached results. */
|
|
86
|
+
clear() {
|
|
87
|
+
this._existsByDb.clear();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.EntityExistenceCache = EntityExistenceCache;
|
|
91
|
+
//# sourceMappingURL=EntityExistenceCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityExistenceCache.js","sourceRoot":"","sources":["../../src/EntityExistenceCache.ts"],"names":[],"mappings":";AAAA;;;gGAGgG;AAChG;;GAEG;;;AAIH,mDAAgD;AAEhD;;;;;;;;;;;;;;;;GAgBG;AACH;IACU,WAAW,GAAG,IAAI,GAAG,EAAkC,CAAC;IAExD,QAAQ,CAAC,EAAY;QAC3B,IAAI,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,MAAM,CACjB,EAAY,EACZ,eAAgC;QAEhC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,6BAAa,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,CAAC;QAClE,IAAI,KAAK;YAAE,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,6GAA6G;IACtG,KAAK,CAAC,SAAS,CACpB,EAAY,EACZ,gBAA2C;QAE3C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmB,CAAC;QACzC,MAAM,UAAU,GAAsB,EAAE,CAAC;QAEzC,KAAK,MAAM,eAAe,IAAI,gBAAgB,EAAE,CAAC;YAC/C,IAAI,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;;gBACtD,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAE1C,MAAM,OAAO,GAAG,MAAM,6BAAa,CAAC,SAAS,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QAC9D,KAAK,MAAM,eAAe,IAAI,OAAO,EAAE,CAAC;YACtC,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YAC3B,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,mFAAmF;IAC5E,UAAU,CAAC,EAAY,EAAE,eAAgC;QAC9D,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACzC,CAAC;IAED,wFAAwF;IACjF,UAAU,CAAC,EAAY,EAAE,eAAgC;QAC9D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACI,OAAO,CAAC,EAAY;QACzB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,+BAA+B;IACxB,KAAK;QACV,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n/** @packageDocumentation\n * @module Utils\n */\n\nimport { EntityReference } from \"@itwin/core-common\";\nimport { IModelDb } from \"@itwin/core-backend\";\nimport { EntityUnifier } from \"./EntityUnifier\";\n\n/** A positive-only read-through cache of entity existence checks, scoped to a single transformation run.\n *\n * Entities are created continuously as a transformation progresses, so a negative\n * (\"does not exist\") result may become stale at any moment — therefore only positive\n * results are ever cached. Positive results can only be invalidated by deletes, which the\n * transformer routes through [[invalidate]] and [[clearDb]].\n *\n * The cache is keyed by [IModelDb]($core-backend) instance, so same-iModel transformations\n * (where sourceDb === targetDb) share a single set and source/target roles cannot be confused.\n *\n * [IModelImporter]($transformer) delete operations invalidate registered caches. Deletes performed\n * directly through an `EditTxn` while a transformation is active are out-of-band and must not be\n * mixed with transformer operations. A successful high-level `process()` clears the cache when the\n * transformation finalizes; low-level or failed transformations clear it when the transformer is\n * disposed.\n * @internal\n */\nexport class EntityExistenceCache {\n private _existsByDb = new Map<IModelDb, Set<EntityReference>>();\n\n private getDbSet(db: IModelDb): Set<EntityReference> {\n let set = this._existsByDb.get(db);\n if (set === undefined) {\n set = new Set();\n this._existsByDb.set(db, set);\n }\n return set;\n }\n\n /** Check whether an entity exists in `db`, consulting the cache first.\n * A `true` result is cached; a `false` result is never cached and will be re-queried.\n */\n public async exists(\n db: IModelDb,\n entityReference: EntityReference\n ): Promise<boolean> {\n const set = this.getDbSet(db);\n if (set.has(entityReference)) return true;\n const found = await EntityUnifier.exists(db, { entityReference });\n if (found) set.add(entityReference);\n return found;\n }\n\n /** Read through the cache to check which of the supplied entities exist in `db`, populating it on a miss. */\n public async existsAll(\n db: IModelDb,\n entityReferences: Iterable<EntityReference>\n ): Promise<ReadonlySet<EntityReference>> {\n const dbSet = this.getDbSet(db);\n const found = new Set<EntityReference>();\n const unverified: EntityReference[] = [];\n\n for (const entityReference of entityReferences) {\n if (dbSet.has(entityReference)) found.add(entityReference);\n else unverified.push(entityReference);\n }\n\n if (unverified.length === 0) return found;\n\n const queried = await EntityUnifier.existsAll(db, unverified);\n for (const entityReference of queried) {\n dbSet.add(entityReference);\n found.add(entityReference);\n }\n return found;\n }\n\n /** Record that an entity is known to exist in `db` (e.g. it was just imported). */\n public markExists(db: IModelDb, entityReference: EntityReference): void {\n this.getDbSet(db).add(entityReference);\n }\n\n /** Remove a single entity from the cache for `db`, forcing a re-query on next check. */\n public invalidate(db: IModelDb, entityReference: EntityReference): void {\n this._existsByDb.get(db)?.delete(entityReference);\n }\n\n /** Drop all cached results for `db`. Used when a delete may cascade to entities the\n * caller cannot enumerate (e.g. element tree deletes that remove descendants and submodels).\n */\n public clearDb(db: IModelDb): void {\n this._existsByDb.delete(db);\n }\n\n /** Drop all cached results. */\n public clear(): void {\n this._existsByDb.clear();\n }\n}\n"]}
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
* for entity-generic operations in the transformer
|
|
5
5
|
*/
|
|
6
6
|
import { EntityReference } from "@itwin/core-common";
|
|
7
|
-
import { ConcreteEntity,
|
|
7
|
+
import { ConcreteEntity, IModelDb } from "@itwin/core-backend";
|
|
8
8
|
/** @internal */
|
|
9
9
|
export declare namespace EntityUnifier {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/** needs to return a widened type otherwise typescript complains when result is used with a narrow type */
|
|
13
|
-
export function updaterFor(db: IModelDb, entity: ConcreteEntity): EntityUpdater;
|
|
14
|
-
export function exists(db: IModelDb, arg: {
|
|
10
|
+
function getReadableType(entity: ConcreteEntity): "element" | "element aspect" | "relationship" | "unknown entity type";
|
|
11
|
+
function exists(db: IModelDb, arg: {
|
|
15
12
|
entity: ConcreteEntity;
|
|
16
13
|
} | {
|
|
17
14
|
entityReference: EntityReference;
|
|
18
15
|
}): Promise<boolean>;
|
|
19
|
-
|
|
16
|
+
/** Batched existence check: returns the subset of `entityReferences` that exist in `db`,
|
|
17
|
+
* using one query per concrete entity type instead of one query per reference.
|
|
18
|
+
*/
|
|
19
|
+
function existsAll(db: IModelDb, entityReferences: Iterable<EntityReference>): Promise<Set<EntityReference>>;
|
|
20
20
|
}
|
|
21
21
|
//# sourceMappingURL=EntityUnifier.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityUnifier.d.ts","sourceRoot":"","sources":["../../src/EntityUnifier.ts"],"names":[],"mappings":"AAIA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"EntityUnifier.d.ts","sourceRoot":"","sources":["../../src/EntityUnifier.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EAEL,eAAe,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EAMd,QAAQ,EAGT,MAAM,qBAAqB,CAAC;AAU7B,gBAAgB;AAChB,yBAAiB,aAAa,CAAC;IAC7B,SAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,yEAKrD;IAED,SAAsB,MAAM,CAC1B,EAAE,EAAE,QAAQ,EACZ,GAAG,EAAE;QAAE,MAAM,EAAE,cAAc,CAAA;KAAE,GAAG;QAAE,eAAe,EAAE,eAAe,CAAA;KAAE,oBAqBvE;IAED;;OAEG;IACH,SAAsB,SAAS,CAC7B,EAAE,EAAE,QAAQ,EACZ,gBAAgB,EAAE,QAAQ,CAAC,eAAe,CAAC,GAC1C,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CA8B/B;CACF"}
|