@itwin/imodel-transformer 2.0.0-dev.7 → 2.0.0-dev.70

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.
Files changed (132) hide show
  1. package/README.md +13 -27
  2. package/lib/cjs/Algo.d.ts +0 -4
  3. package/lib/cjs/Algo.d.ts.map +1 -1
  4. package/lib/cjs/Algo.js +0 -11
  5. package/lib/cjs/Algo.js.map +1 -1
  6. package/lib/cjs/BigMap.d.ts.map +1 -1
  7. package/lib/cjs/BigMap.js.map +1 -1
  8. package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
  9. package/lib/cjs/BranchProvenanceInitializer.js +26 -26
  10. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  11. package/lib/cjs/ChangedElementForest.d.ts +14 -0
  12. package/lib/cjs/ChangedElementForest.d.ts.map +1 -0
  13. package/lib/cjs/ChangedElementForest.js +86 -0
  14. package/lib/cjs/ChangedElementForest.js.map +1 -0
  15. package/lib/cjs/ChangesetScanner.d.ts +141 -0
  16. package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
  17. package/lib/cjs/ChangesetScanner.js +187 -0
  18. package/lib/cjs/ChangesetScanner.js.map +1 -0
  19. package/lib/cjs/ECReferenceTypesCache.d.ts +0 -4
  20. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
  21. package/lib/cjs/ECReferenceTypesCache.js +61 -111
  22. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  23. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -1
  24. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
  25. package/lib/cjs/ElementAspectCleanup.d.ts +16 -0
  26. package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
  27. package/lib/cjs/ElementAspectCleanup.js +87 -0
  28. package/lib/cjs/ElementAspectCleanup.js.map +1 -0
  29. package/lib/cjs/ElementAspectExportCoordinator.d.ts +44 -0
  30. package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
  31. package/lib/cjs/ElementAspectExportCoordinator.js +148 -0
  32. package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
  33. package/lib/cjs/ElementAspectExportProcessor.d.ts +44 -0
  34. package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
  35. package/lib/cjs/ElementAspectExportProcessor.js +152 -0
  36. package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
  37. package/lib/cjs/ElementBulkDelete.d.ts +30 -0
  38. package/lib/cjs/ElementBulkDelete.d.ts.map +1 -0
  39. package/lib/cjs/ElementBulkDelete.js +358 -0
  40. package/lib/cjs/ElementBulkDelete.js.map +1 -0
  41. package/lib/cjs/EntityExistenceCache.d.ts +43 -0
  42. package/lib/cjs/EntityExistenceCache.d.ts.map +1 -0
  43. package/lib/cjs/EntityExistenceCache.js +91 -0
  44. package/lib/cjs/EntityExistenceCache.js.map +1 -0
  45. package/lib/cjs/EntityUnifier.d.ts +7 -7
  46. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  47. package/lib/cjs/EntityUnifier.js +55 -24
  48. package/lib/cjs/EntityUnifier.js.map +1 -1
  49. package/lib/cjs/IModelCloneContext.d.ts +9 -3
  50. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  51. package/lib/cjs/IModelCloneContext.js +56 -22
  52. package/lib/cjs/IModelCloneContext.js.map +1 -1
  53. package/lib/cjs/IModelExporter.d.ts +171 -43
  54. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  55. package/lib/cjs/IModelExporter.js +946 -247
  56. package/lib/cjs/IModelExporter.js.map +1 -1
  57. package/lib/cjs/IModelImporter.d.ts +75 -30
  58. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  59. package/lib/cjs/IModelImporter.js +292 -132
  60. package/lib/cjs/IModelImporter.js.map +1 -1
  61. package/lib/cjs/IModelTransformContext.d.ts +54 -0
  62. package/lib/cjs/IModelTransformContext.d.ts.map +1 -0
  63. package/lib/cjs/IModelTransformContext.js +3 -0
  64. package/lib/cjs/IModelTransformContext.js.map +1 -0
  65. package/lib/cjs/IModelTransformer.d.ts +180 -206
  66. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  67. package/lib/cjs/IModelTransformer.js +811 -1278
  68. package/lib/cjs/IModelTransformer.js.map +1 -1
  69. package/lib/cjs/IModelTransformerError.d.ts +84 -0
  70. package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
  71. package/lib/cjs/IModelTransformerError.js +93 -0
  72. package/lib/cjs/IModelTransformerError.js.map +1 -0
  73. package/lib/cjs/ProvenanceManager.d.ts +177 -0
  74. package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
  75. package/lib/cjs/ProvenanceManager.js +719 -0
  76. package/lib/cjs/ProvenanceManager.js.map +1 -0
  77. package/lib/cjs/SourceClassKinds.d.ts +22 -0
  78. package/lib/cjs/SourceClassKinds.d.ts.map +1 -0
  79. package/lib/cjs/SourceClassKinds.js +60 -0
  80. package/lib/cjs/SourceClassKinds.js.map +1 -0
  81. package/lib/cjs/SyncTypeResolver.d.ts +32 -0
  82. package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
  83. package/lib/cjs/SyncTypeResolver.js +92 -0
  84. package/lib/cjs/SyncTypeResolver.js.map +1 -0
  85. package/lib/cjs/TransformerLoggerCategory.d.ts +1 -1
  86. package/lib/cjs/TransformerLoggerCategory.js +1 -1
  87. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  88. package/lib/cjs/TransformerPackageMetadata.d.ts +8 -0
  89. package/lib/cjs/TransformerPackageMetadata.d.ts.map +1 -0
  90. package/lib/cjs/TransformerPackageMetadata.js +47 -0
  91. package/lib/cjs/TransformerPackageMetadata.js.map +1 -0
  92. package/lib/cjs/imodel-transformer.d.ts +28 -0
  93. package/lib/cjs/imodel-transformer.d.ts.map +1 -1
  94. package/lib/cjs/imodel-transformer.js +61 -34
  95. package/lib/cjs/imodel-transformer.js.map +1 -1
  96. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts +29 -0
  97. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.d.ts.map +1 -0
  98. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js +212 -0
  99. package/lib/cjs/schema-processing/DynamicSchemaUnionStrategy.js.map +1 -0
  100. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts +25 -0
  101. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.d.ts.map +1 -0
  102. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js +196 -0
  103. package/lib/cjs/schema-processing/SchemaProcessingCoordinator.js.map +1 -0
  104. package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts +59 -0
  105. package/lib/cjs/schema-processing/SchemaProcessingStrategy.d.ts.map +1 -0
  106. package/lib/cjs/schema-processing/SchemaProcessingStrategy.js +28 -0
  107. package/lib/cjs/schema-processing/SchemaProcessingStrategy.js.map +1 -0
  108. package/lib/cjs/schema-processing.d.ts +7 -0
  109. package/lib/cjs/schema-processing.d.ts.map +1 -0
  110. package/lib/cjs/schema-processing.js +27 -0
  111. package/lib/cjs/schema-processing.js.map +1 -0
  112. package/package.json +70 -57
  113. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
  114. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
  115. package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -106
  116. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
  117. package/lib/cjs/ElementCascadingDeleter.d.ts +0 -22
  118. package/lib/cjs/ElementCascadingDeleter.d.ts.map +0 -1
  119. package/lib/cjs/ElementCascadingDeleter.js +0 -57
  120. package/lib/cjs/ElementCascadingDeleter.js.map +0 -1
  121. package/lib/cjs/EntityMap.d.ts +0 -26
  122. package/lib/cjs/EntityMap.d.ts.map +0 -1
  123. package/lib/cjs/EntityMap.js +0 -54
  124. package/lib/cjs/EntityMap.js.map +0 -1
  125. package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
  126. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
  127. package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
  128. package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
  129. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
  130. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
  131. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -47
  132. 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":";AAAA;;;gGAGgG;;;AAEhG,sDAA6E;AAC7E,oDAAiD;AAuBjD;;;;;;;;;;;;;GAaG;AACI,KAAK,yBACV,QAAkB,EAClB,UAAmC;IAEnC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,cAAc,GAAG,IAAI,GAAG,EAA0B,CAAC;IACzD,SAAS,CAAC;QACR,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC/D,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC;QAElE,MAAM,UAAU,GAAG,MAAM,eAAe,CACtC,QAAQ,EACR,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAC5B,CAAC;QACF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAc,CAAC;QACzC,KAAK,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,IAAI,UAAU,CAAC,UAAU,EAAE,CAAC;YACpE,IAAI,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC;gBAAE,SAAS;YACtD,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC;gBACnC,cAAc,CAAC,GAAG,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;YACvB,OAAO;gBACL,MAAM,EAAE,MAAM,UAAU,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,CAAC;gBACrD,cAAc;aACf,CAAC;QAEJ,wGAAwG;QACxG,yEAAyE;QACzE,MAAM,cAAc,GAAG,YAAY,CAAC,IAAI,CAAC;QACzC,KAAK,MAAM,EAAE,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC;YAC5D,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1B,IAAA,qBAAM,EACJ,YAAY,CAAC,IAAI,GAAG,cAAc,EAClC,6DAA6D,CAC9D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,iGAAiG;AACjG,KAAK,UAAU,UAAU,CACvB,QAAkB,EAClB,KAAoB,EACpB,EAAE,UAAU,EAAE,qBAAqB,EAAkB;IAErD,oIAAoI;IACpI,MAAM,yBAAyB,GAAG,IAAI,GAAG,EAAc,CAAC;IACxD,qFAAqF;IACrF,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAc,CAAC;IACvD,MAAM,iBAAiB,GAAG,CAAC,YAAwB,EAAE,EAAE;QACrD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE;YAC/D,wBAAwB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC,CAAC;IACF,MAAM,kBAAkB,GAAoB,EAAE,CAAC;IAC/C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;YACzB,yBAAyB,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;YACvD,iBAAiB,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,iHAAiH;IACjH,6GAA6G;IAC7G,+GAA+G;IAC/G,wEAAwE;IACxE,MAAM,aAAa,GAAG,IAAI,GAAG,EAAc,CAAC;IAC5C,KAAK,MAAM,EAAE,IAAI,qBAAqB;QACpC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;YACrD,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAI,aAAa,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,uBAAuB,GAAG,CAAC,GAAG,KAAK,CAAC,cAAc,CAAC;aACtD,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;aACpE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACrB,IACE,CAAC,MAAM,sBAAsB,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC,CAAC,MAAM;YACxE,CAAC;YAED,KAAK,MAAM,EAAE,IAAI,MAAM,sBAAsB,CAC3C,QAAQ,EACR,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAC5B;gBACC,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtC,KAAK,MAAM,MAAM,IAAI,aAAa;YAAE,wBAAwB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,wBAAwB,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACnE,iEAAiE;IACjE,MAAM,0BAA0B,GAAG,CAAC,EAAc,EAAE,EAAE,CACpD,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CACnD,wBAAwB,CAAC,GAAG,CAAC,MAAM,CAAC,CACrC,CAAC;IACJ,2HAA2H;IAC3H,MAAM,aAAa,GAAG,CAAC,EAAc,EAAE,EAAE,CACvC,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CACvC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,wBAAwB,CAAC,GAAG,CAAC,MAAM,CAAC,CAClD,CAAC;IACJ,4GAA4G;IAC5G,wFAAwF;IACxF,MAAM,wBAAwB,GAC5B,CAAC,CAAC,GAAG,yBAAyB,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC;QAChE,CAAC,kBAAkB,CAAC,IAAI,CACtB,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,EAAE,EAAE,CAClC,0BAA0B,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,YAAY,CAAC,CAC3E,CAAC;IACJ,IAAI,wBAAwB,EAAE,CAAC;QAC7B,+GAA+G;QAC/G,0CAA0C;QAC1C,MAAM,UAAU,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CACxC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,wBAAwB,CAAC,GAAG,CAAC,EAAE,CAAC,CAC1C,CAAC;QACF,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC;YAC1B,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,wBAAwB,CAAC,CAAC;YAC7C,CAAC,CAAC,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,wGAAwG;IACxG,6GAA6G;IAC7G,wGAAwG;IACxG,MAAM,SAAS,GAAG,MAAM,kBAAkB,CACxC,QAAQ,EACR,yBAAyB,EACzB,IAAI,CACL,CAAC;IACF,MAAM,cAAc,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAC5C,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAC1C,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACtC,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,kBAAkB,CAC/B,QAAkB,EAClB,UAAmC,EACnC,6BAA6B,GAAG,KAAK;IAErC,MAAM,KAAK,GAAY,IAAI,GAAG,EAAc,CAAC;IAC7C,oHAAoH;IACpH,gGAAgG;IAChG,MAAM,cAAc,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC3D,kGAAkG;IAClG,iGAAiG;IACjG,qGAAqG;IACrG,4FAA4F;IAC5F,mGAAmG;IACnG,mEAAmE;IACnE,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;QAmBR,6BAA6B,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,uCAAuC;;;GAGjF,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,yBAAW,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACrE,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE;QAChE,cAAc,EAAE,IAAI;KACrB,CAAC,EAAE,CAAC;QACH,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtB,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChD,IAAI,YAAY;YAAE,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;;YAC3C,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,iBAAiB,CAC9B,QAAkB,EAClB,UAAmC;IAEnC,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2Bb,CAAC;IACF,MAAM,OAAO,GAAY,IAAI,GAAG,EAAc,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,yBAAW,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACrE,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE;QAChE,cAAc,EAAE,IAAI;KACrB,CAAC;QACA,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtB,OAAO,OAAO,CAAC;AACjB,CAAC;AAmBD;;;GAGG;AACH,KAAK,UAAU,eAAe,CAC5B,QAAkB,EAClB,UAAgC;IAEhC,MAAM,UAAU,GAAoB,EAAE,CAAC;IACvC,MAAM,qBAAqB,GAAiB,EAAE,CAAC;IAC/C,8GAA8G;IAC9G,sFAAsF;IACtF,MAAM,aAAa,GAAiB,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG;;;;;;;;GAQlB,CAAC;IACF,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC,iBAAiB,CAChD,UAAU,EACV,IAAI,yBAAW,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,EACrD,EAAE,cAAc,EAAE,IAAI,EAAE,CACzB,EAAE,CAAC;QACF,IAAI,GAAG,CAAC,YAAY,KAAK,CAAC;YAAE,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;;YAE/D,UAAU,CAAC,IAAI,CAAC;gBACd,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,YAAY,EAAE,GAAG,CAAC,YAAY;gBAC9B,UAAU,EAAE,KAAK;aAClB,CAAC,CAAC;IACP,CAAC;IACD,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,CAAC;IAE7E,0DAA0D;IAC1D,MAAM,iBAAiB,GAAG,MAAM,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IACrE,gHAAgH;IAChH,8GAA8G;IAC9G,2EAA2E;IAC3E,gFAAgF;IAChF,MAAM,eAAe,GAAG;;;;;;;;;;;;MAYpB,iBAAiB;SAChB,GAAG,CACF,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;;2CAEE,YAAY;eACxC,SAAS;4DACoC,YAAY,KAAK,CACtE;SACA,IAAI,CAAC,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;MAuBT,CAAC,cAAc,EAAE,gBAAgB,EAAE,SAAS,EAAE,WAAW,CAAC;SACzD,GAAG,CACF,CAAC,SAAS,EAAE,EAAE,CAAC;;;eAGR,SAAS;uEAC+C,CAChE;SACA,IAAI,CAAC,EAAE,CAAC;GACZ,CAAC;IACF,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC,iBAAiB,CAChD,eAAe,EACf,IAAI,yBAAW,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,aAAa,CAAC,EACxD,EAAE,cAAc,EAAE,IAAI,EAAE,CACzB,EAAE,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;YAAE,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;;YAE/D,UAAU,CAAC,IAAI,CAAC;gBACd,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,YAAY,EAAE,GAAG,CAAC,YAAY;gBAC9B,UAAU,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC;aAC3B,CAAC,CAAC;IACP,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,CAAC;AAC/C,CAAC;AAED,+FAA+F;AAC/F,KAAK,UAAU,0BAA0B,CACvC,QAAkB;IAElB,MAAM,KAAK,GAAG;;;;;;;;GAQb,CAAC;IACF,MAAM,UAAU,GAAkD,EAAE,CAAC;IACrE,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE;QACnE,cAAc,EAAE,IAAI;KACrB,CAAC;QACA,UAAU,CAAC,IAAI,CAAC;YACd,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,YAAY,EAAE,GAAG,CAAC,YAAY;SAC/B,CAAC,CAAC;IACL,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,2EAA2E;AAC3E,KAAK,UAAU,sBAAsB,CACnC,QAAkB,EAClB,UAAgC;IAEhC,2GAA2G;IAC3G,MAAM,KAAK,GAAG;;;;;;;;GAQb,CAAC;IACF,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,yBAAW,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACrE,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,QAAQ,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE;QAChE,cAAc,EAAE,IAAI;KACrB,CAAC;QACA,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnB,OAAO,GAAG,CAAC;AACb,CAAC","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, ConcreteEntityProps, IModelDb } from "@itwin/core-backend";
7
+ import { ConcreteEntity, IModelDb } from "@itwin/core-backend";
8
8
  /** @internal */
9
9
  export declare namespace EntityUnifier {
10
- export function getReadableType(entity: ConcreteEntity): "element" | "element aspect" | "relationship" | "unknown entity type";
11
- type EntityUpdater = (entityProps: ConcreteEntityProps) => void;
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
- export {};
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;AAGH,OAAO,EAEL,eAAe,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,mBAAmB,EAKnB,QAAQ,EAET,MAAM,qBAAqB,CAAC;AAG7B,gBAAgB;AAChB,yBAAiB,aAAa,CAAC;IAC7B,MAAM,UAAU,eAAe,CAAC,MAAM,EAAE,cAAc,yEAKrD;IAED,KAAK,aAAa,GAAG,CAAC,WAAW,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAEhE,2GAA2G;IAC3G,MAAM,UAAU,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,iBAc9D;IAED,gBAAsB,MAAM,CAC1B,EAAE,EAAE,QAAQ,EACZ,GAAG,EAAE;QAAE,MAAM,EAAE,cAAc,CAAA;KAAE,GAAG;QAAE,eAAe,EAAE,eAAe,CAAA;KAAE,oBAkBvE;;CACF"}
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"}