@itwin/imodel-transformer 0.4.4-dev.0 → 0.4.18-dev.0

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 (65) hide show
  1. package/CHANGELOG.md +1 -9
  2. package/lib/cjs/Algo.d.ts +7 -0
  3. package/lib/cjs/Algo.d.ts.map +1 -0
  4. package/lib/cjs/Algo.js +65 -0
  5. package/lib/cjs/Algo.js.map +1 -0
  6. package/lib/cjs/BigMap.d.ts +1 -6
  7. package/lib/cjs/BigMap.d.ts.map +1 -1
  8. package/lib/cjs/BigMap.js +3 -29
  9. package/lib/cjs/BigMap.js.map +1 -1
  10. package/lib/cjs/BranchProvenanceInitializer.d.ts +10 -3
  11. package/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
  12. package/lib/cjs/BranchProvenanceInitializer.js +101 -10
  13. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  14. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +1 -1
  15. package/lib/cjs/DetachedExportElementAspectsStrategy.js +12 -5
  16. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
  17. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
  18. package/lib/cjs/ECReferenceTypesCache.js +32 -18
  19. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  20. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts +1 -1
  21. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.d.ts.map +1 -1
  22. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js +7 -5
  23. package/lib/cjs/ECSqlReaderAsyncIterableIteratorAdapter.js.map +1 -1
  24. package/lib/cjs/ElementCascadingDeleter.d.ts +3 -3
  25. package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
  26. package/lib/cjs/ElementCascadingDeleter.js +9 -7
  27. package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
  28. package/lib/cjs/EntityMap.d.ts.map +1 -1
  29. package/lib/cjs/EntityMap.js.map +1 -1
  30. package/lib/cjs/EntityUnifier.d.ts +5 -0
  31. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  32. package/lib/cjs/EntityUnifier.js +22 -35
  33. package/lib/cjs/EntityUnifier.js.map +1 -1
  34. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +1 -1
  35. package/lib/cjs/ExportElementAspectsStrategy.js +5 -4
  36. package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
  37. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +1 -1
  38. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +9 -5
  39. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
  40. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  41. package/lib/cjs/IModelCloneContext.js +23 -14
  42. package/lib/cjs/IModelCloneContext.js.map +1 -1
  43. package/lib/cjs/IModelExporter.d.ts +87 -21
  44. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  45. package/lib/cjs/IModelExporter.js +279 -114
  46. package/lib/cjs/IModelExporter.js.map +1 -1
  47. package/lib/cjs/IModelImporter.d.ts +29 -21
  48. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  49. package/lib/cjs/IModelImporter.js +115 -62
  50. package/lib/cjs/IModelImporter.js.map +1 -1
  51. package/lib/cjs/IModelTransformer.d.ts +285 -48
  52. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  53. package/lib/cjs/IModelTransformer.js +1273 -337
  54. package/lib/cjs/IModelTransformer.js.map +1 -1
  55. package/lib/cjs/PendingReferenceMap.d.ts.map +1 -1
  56. package/lib/cjs/PendingReferenceMap.js +12 -6
  57. package/lib/cjs/PendingReferenceMap.js.map +1 -1
  58. package/lib/cjs/TransformerLoggerCategory.d.ts +2 -2
  59. package/lib/cjs/TransformerLoggerCategory.d.ts.map +1 -1
  60. package/lib/cjs/TransformerLoggerCategory.js +5 -5
  61. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  62. package/lib/cjs/transformer.d.ts.map +1 -1
  63. package/lib/cjs/transformer.js +14 -10
  64. package/lib/cjs/transformer.js.map +1 -1
  65. package/package.json +20 -17
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
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
- *--------------------------------------------------------------------------------------------*/
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
6
  /** @packageDocumentation
7
7
  * @module iModels
8
8
  */
@@ -13,6 +13,7 @@ const core_bentley_1 = require("@itwin/core-bentley");
13
13
  const core_common_1 = require("@itwin/core-common");
14
14
  const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
15
15
  const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
16
+ const nodeAssert = require("assert");
16
17
  const ExportElementAspectsWithElementsStrategy_1 = require("./ExportElementAspectsWithElementsStrategy");
17
18
  const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelExporter;
18
19
  /** Handles the events generated by IModelExporter.
@@ -25,7 +26,9 @@ class IModelExportHandler {
25
26
  /** If `true` is returned, then the CodeSpec will be exported.
26
27
  * @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
27
28
  */
28
- shouldExportCodeSpec(_codeSpec) { return true; }
29
+ shouldExportCodeSpec(_codeSpec) {
30
+ return true;
31
+ }
29
32
  /** Called when a CodeSpec should be exported.
30
33
  * @param codeSpec The CodeSpec to export
31
34
  * @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
@@ -49,7 +52,9 @@ class IModelExportHandler {
49
52
  /** If `true` is returned, then the element will be exported.
50
53
  * @note This method can optionally be overridden to exclude an individual Element (and its children and ElementAspects) from the export. The base implementation always returns `true`.
51
54
  */
52
- shouldExportElement(_element) { return true; }
55
+ shouldExportElement(_element) {
56
+ return true;
57
+ }
53
58
  /** Called when element is skipped instead of exported. */
54
59
  onSkipElement(_elementId) { }
55
60
  /** Called when an element should be exported.
@@ -70,7 +75,9 @@ class IModelExportHandler {
70
75
  /** If `true` is returned, then the ElementAspect will be exported.
71
76
  * @note This method can optionally be overridden to exclude an individual ElementAspect from the export. The base implementation always returns `true`.
72
77
  */
73
- shouldExportElementAspect(_aspect) { return true; }
78
+ shouldExportElementAspect(_aspect) {
79
+ return true;
80
+ }
74
81
  /** Called when an ElementUniqueAspect should be exported.
75
82
  * @param aspect The ElementUniqueAspect to export
76
83
  * @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
@@ -84,7 +91,9 @@ class IModelExportHandler {
84
91
  /** If `true` is returned, then the relationship will be exported.
85
92
  * @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
86
93
  */
87
- shouldExportRelationship(_relationship) { return true; }
94
+ shouldExportRelationship(_relationship) {
95
+ return true;
96
+ }
88
97
  /** Called when a Relationship should be exported.
89
98
  * @param relationship The Relationship to export
90
99
  * @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
@@ -96,7 +105,9 @@ class IModelExportHandler {
96
105
  /** If `true` is returned, then the schema will be exported.
97
106
  * @note This method can optionally be overridden to exclude an individual schema from the export. The base implementation always returns `true`.
98
107
  */
99
- shouldExportSchema(_schemaKey) { return true; }
108
+ shouldExportSchema(_schemaKey) {
109
+ return true;
110
+ }
100
111
  /** Called when a schema should be exported.
101
112
  * @param schema The schema to export
102
113
  * @note This should be overridden to actually do the export.
@@ -120,7 +131,7 @@ class IModelExporter {
120
131
  /**
121
132
  * Retrieve the cached entity change information.
122
133
  * @note This will only be initialized after [IModelExporter.exportChanges] is invoked.
123
- */
134
+ */
124
135
  get sourceDbChanges() {
125
136
  return this._sourceDbChanges;
126
137
  }
@@ -175,6 +186,7 @@ class IModelExporter {
175
186
  this._excludedElementClasses = new Set();
176
187
  /** The set of classes of Relationships that will be excluded (polymorphically) from transformation to the target iModel. */
177
188
  this._excludedRelationshipClasses = new Set();
189
+ this._resetChangeDataOnExport = true;
178
190
  this._yieldManager = new core_bentley_1.YieldManager();
179
191
  this.sourceDb = sourceDb;
180
192
  this._exportElementAspectsStrategy = new elementAspectsStrategy(this.sourceDb, {
@@ -184,6 +196,24 @@ class IModelExporter {
184
196
  trackProgress: async () => this.trackProgress(),
185
197
  });
186
198
  }
199
+ /**
200
+ * Initialize prerequisites of exporting. This is implicitly done by any `export*` calls that need initialization
201
+ * which is currently just `exportChanges`.
202
+ * Prefer to not call this explicitly (e.g. just call [[IModelExporter.exportChanges]])
203
+ * @note that if you do call this explicitly, you must do so with the same options that
204
+ * you pass to [[IModelExporter.exportChanges]]
205
+ */
206
+ async initialize(options) {
207
+ if (!this.sourceDb.isBriefcaseDb() || this._sourceDbChanges)
208
+ return;
209
+ this._sourceDbChanges = await ChangedInstanceIds.initialize({
210
+ iModel: this.sourceDb,
211
+ ...options,
212
+ });
213
+ if (this._sourceDbChanges === undefined)
214
+ return;
215
+ this._exportElementAspectsStrategy.setAspectChanges(this._sourceDbChanges.aspect);
216
+ }
187
217
  /** Register the handler that will be called by IModelExporter. */
188
218
  registerHandler(handler) {
189
219
  this._handler = handler;
@@ -216,40 +246,38 @@ class IModelExporter {
216
246
  * @note [[exportSchemas]] must be called separately.
217
247
  */
218
248
  async exportAll() {
249
+ await this.initialize({});
219
250
  await this.exportCodeSpecs();
220
251
  await this.exportFonts();
221
252
  await this.exportModel(core_common_1.IModel.repositoryModelId);
222
253
  await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
223
254
  }
224
- async exportChanges(accessTokenOrArgs, startChangesetId) {
255
+ async exportChanges(accessTokenOrOpts, startChangesetId) {
225
256
  if (!this.sourceDb.isBriefcaseDb())
226
257
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Must be a briefcase to export changes");
227
258
  if ("" === this.sourceDb.changeset.id) {
228
259
  await this.exportAll(); // no changesets, so revert to exportAll
229
260
  return;
230
261
  }
231
- const { accessToken, startChangeset } = typeof accessTokenOrArgs === "object"
232
- ? accessTokenOrArgs
262
+ const initOpts = typeof accessTokenOrOpts === "object"
263
+ ? accessTokenOrOpts
233
264
  : {
234
- accessToken: accessTokenOrArgs,
265
+ accessToken: accessTokenOrOpts,
235
266
  startChangeset: { id: startChangesetId },
236
267
  };
237
- this._sourceDbChanges =
238
- (typeof accessTokenOrArgs === "object"
239
- ? accessTokenOrArgs.changedInstanceIds
240
- : undefined)
241
- ?? await ChangedInstanceIds.initialize({
242
- accessToken,
243
- startChangeset: startChangeset?.id !== undefined || startChangeset?.index !== undefined
244
- ? startChangeset
245
- : this.sourceDb.changeset,
246
- iModel: this.sourceDb,
247
- });
248
- this._exportElementAspectsStrategy.setAspectChanges(this._sourceDbChanges?.aspect);
268
+ await this.initialize(initOpts);
269
+ // _sourceDbChanges are initialized in this.initialize
270
+ nodeAssert(this._sourceDbChanges !== undefined, "sourceDbChanges must be initialized.");
249
271
  await this.exportCodeSpecs();
250
272
  await this.exportFonts();
251
- await this.exportModelContents(core_common_1.IModel.repositoryModelId);
252
- await this.exportSubModels(core_common_1.IModel.repositoryModelId);
273
+ if (initOpts.skipPropagateChangesToRootElements) {
274
+ await this.exportModelContents(core_common_1.IModel.repositoryModelId);
275
+ await this.exportSubModels(core_common_1.IModel.repositoryModelId);
276
+ }
277
+ else {
278
+ await this.exportModel(core_common_1.IModel.repositoryModelId);
279
+ }
280
+ await this.exportAllAspects();
253
281
  await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
254
282
  // handle deletes
255
283
  if (this.visitElements) {
@@ -268,17 +296,25 @@ class IModelExporter {
268
296
  this.handler.onDeleteElement(elementId);
269
297
  }
270
298
  catch (err) {
271
- const isMissingErr = err instanceof core_common_1.IModelError && err.errorNumber === core_bentley_1.IModelStatus.NotFound;
299
+ const isMissingErr = err instanceof core_common_1.IModelError &&
300
+ err.errorNumber === core_bentley_1.IModelStatus.NotFound;
272
301
  if (!isMissingErr)
273
302
  throw err;
274
303
  }
275
304
  }
276
305
  }
277
306
  if (this.visitRelationships) {
278
- for (const relInstanceId of this._sourceDbChanges.relationship.deleteIds) {
307
+ for (const relInstanceId of this._sourceDbChanges.relationship
308
+ .deleteIds) {
279
309
  this.handler.onDeleteRelationship(relInstanceId);
280
310
  }
281
311
  }
312
+ // Enable consecutive exportChanges runs without the need to re-instantiate the exporter.
313
+ // You can counteract the obvious impact of losing this expensive data by always calling
314
+ // exportChanges with the [[ExportChangesOptions.changedInstanceIds]] option set to
315
+ // whatever you want
316
+ if (this._resetChangeDataOnExport)
317
+ this._sourceDbChanges = undefined;
282
318
  }
283
319
  /** Export schemas from the source iModel.
284
320
  * @note This must be called separately from [[exportAll]] or [[exportChanges]].
@@ -288,7 +324,9 @@ class IModelExporter {
288
324
  const sql = `
289
325
  SELECT s.Name, s.VersionMajor, s.VersionWrite, s.VersionMinor
290
326
  FROM ECDbMeta.ECSchemaDef s
291
- ${this.wantSystemSchemas ? "" : `
327
+ ${this.wantSystemSchemas
328
+ ? ""
329
+ : `
292
330
  WHERE ECInstanceId >= (SELECT ECInstanceId FROM ECDbMeta.ECSchemaDef WHERE Name='BisCore')
293
331
  `}
294
332
  ORDER BY ECInstanceId
@@ -324,8 +362,8 @@ class IModelExporter {
324
362
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
325
363
  */
326
364
  async exportCodeSpecs() {
327
- core_bentley_1.Logger.logTrace(loggerCategory, `exportCodeSpecs()`);
328
- const sql = `SELECT Name FROM BisCore:CodeSpec ORDER BY ECInstanceId`;
365
+ core_bentley_1.Logger.logTrace(loggerCategory, "exportCodeSpecs()");
366
+ const sql = "SELECT Name FROM BisCore:CodeSpec ORDER BY ECInstanceId";
329
367
  await this.sourceDb.withPreparedStatement(sql, async (statement) => {
330
368
  while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
331
369
  const codeSpecName = statement.getValue(0).getString();
@@ -339,7 +377,8 @@ class IModelExporter {
339
377
  async exportCodeSpecByName(codeSpecName) {
340
378
  const codeSpec = this.sourceDb.codeSpecs.getByName(codeSpecName);
341
379
  let isUpdate;
342
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
380
+ if (undefined !== this._sourceDbChanges) {
381
+ // is changeset information available?
343
382
  if (this._sourceDbChanges.codeSpec.insertIds.has(codeSpec.id)) {
344
383
  isUpdate = false;
345
384
  }
@@ -373,7 +412,7 @@ class IModelExporter {
373
412
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
374
413
  */
375
414
  async exportFonts() {
376
- core_bentley_1.Logger.logTrace(loggerCategory, `exportFonts()`);
415
+ core_bentley_1.Logger.logTrace(loggerCategory, "exportFonts()");
377
416
  for (const font of this.sourceDb.fontMap.fonts.values()) {
378
417
  await this.exportFontByNumber(font.id);
379
418
  }
@@ -392,19 +431,12 @@ class IModelExporter {
392
431
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
393
432
  */
394
433
  async exportFontByNumber(fontNumber) {
395
- let isUpdate;
396
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
397
- const fontId = core_bentley_1.Id64.fromUint32Pair(fontNumber, 0); // changeset information uses Id64String, not number
398
- if (this._sourceDbChanges.font.insertIds.has(fontId)) {
399
- isUpdate = false;
400
- }
401
- else if (this._sourceDbChanges.font.updateIds.has(fontId)) {
402
- isUpdate = true;
403
- }
404
- else {
405
- return; // not in changeset, don't export
406
- }
407
- }
434
+ /** sourceDbChanges now works by using TS ChangesetECAdaptor which doesn't pick up changes to fonts since fonts is not an ec table.
435
+ * So lets always export fonts for the time being by always setting isUpdate = true.
436
+ * It is very rare and even problematic for the font table to reach a large size, so it is not a bottleneck in transforming changes.
437
+ * See https://github.com/iTwin/imodel-transformer/pull/135 for removed code.
438
+ */
439
+ const isUpdate = true;
408
440
  core_bentley_1.Logger.logTrace(loggerCategory, `exportFontById(${fontNumber})`);
409
441
  const font = this.sourceDb.fontMap.getFont(fontNumber);
410
442
  if (undefined !== font) {
@@ -420,7 +452,11 @@ class IModelExporter {
420
452
  if (model.isTemplate && !this.wantTemplateModels) {
421
453
  return;
422
454
  }
423
- const modeledElement = this.sourceDb.elements.getElement({ id: modeledElementId, wantGeometry: this.wantGeometry, wantBRepData: this.wantGeometry });
455
+ const modeledElement = this.sourceDb.elements.getElement({
456
+ id: modeledElementId,
457
+ wantGeometry: this.wantGeometry,
458
+ wantBRepData: this.wantGeometry,
459
+ });
424
460
  core_bentley_1.Logger.logTrace(loggerCategory, `exportModel(${modeledElementId})`);
425
461
  if (this.shouldExportElement(modeledElement)) {
426
462
  await this.exportModelContainer(model);
@@ -433,7 +469,8 @@ class IModelExporter {
433
469
  /** Export the model (the container only) from the source iModel. */
434
470
  async exportModelContainer(model) {
435
471
  let isUpdate;
436
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
472
+ if (undefined !== this._sourceDbChanges) {
473
+ // is changeset information available?
437
474
  if (this._sourceDbChanges.model.insertIds.has(model.id)) {
438
475
  isUpdate = false;
439
476
  }
@@ -463,8 +500,10 @@ class IModelExporter {
463
500
  core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportModelContents(${modelId})`);
464
501
  return;
465
502
  }
466
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
467
- if (!this._sourceDbChanges.model.insertIds.has(modelId) && !this._sourceDbChanges.model.updateIds.has(modelId)) {
503
+ if (undefined !== this._sourceDbChanges) {
504
+ // is changeset information available?
505
+ if (!this._sourceDbChanges.model.insertIds.has(modelId) &&
506
+ !this._sourceDbChanges.model.updateIds.has(modelId)) {
468
507
  return; // this optimization assumes that the Model changes (LastMod) any time an Element in the Model changes
469
508
  }
470
509
  }
@@ -531,7 +570,8 @@ class IModelExporter {
531
570
  return false;
532
571
  }
533
572
  }
534
- if (!this.wantTemplateModels && (element instanceof core_backend_1.RecipeDefinitionElement)) {
573
+ if (!this.wantTemplateModels &&
574
+ element instanceof core_backend_1.RecipeDefinitionElement) {
535
575
  core_bentley_1.Logger.logInfo(loggerCategory, `Excluded RecipeDefinitionElement ${element.id} because wantTemplate=false`);
536
576
  return false;
537
577
  }
@@ -552,21 +592,17 @@ class IModelExporter {
552
592
  core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportElement(${elementId})`);
553
593
  return;
554
594
  }
555
- let isUpdate;
556
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
557
- if (this._sourceDbChanges.element.insertIds.has(elementId)) {
558
- isUpdate = false;
559
- }
560
- else if (this._sourceDbChanges.element.updateIds.has(elementId)) {
561
- isUpdate = true;
562
- }
563
- else {
564
- // NOTE: This optimization assumes that the Element will change (LastMod) if an owned ElementAspect changes
565
- // NOTE: However, child elements may have changed without the parent changing
566
- return this.exportChildElements(elementId);
567
- }
568
- }
569
- const element = this.sourceDb.elements.getElement({ id: elementId, wantGeometry: this.wantGeometry, wantBRepData: this.wantGeometry });
595
+ // are we processing changes?
596
+ const isUpdate = this._sourceDbChanges?.element.insertIds.has(elementId)
597
+ ? false
598
+ : this._sourceDbChanges?.element.updateIds.has(elementId)
599
+ ? true
600
+ : undefined;
601
+ const element = this.sourceDb.elements.getElement({
602
+ id: elementId,
603
+ wantGeometry: this.wantGeometry,
604
+ wantBRepData: this.wantGeometry,
605
+ });
570
606
  core_bentley_1.Logger.logTrace(loggerCategory, `exportElement(${element.id}, "${element.getDisplayLabel()}")${this.getChangeOpSuffix(isUpdate)}`);
571
607
  // the order and `await`ing of calls beyond here is depended upon by the IModelTransformer for a current bug workaround
572
608
  if (this.shouldExportElement(element)) {
@@ -606,7 +642,7 @@ class IModelExporter {
606
642
  */
607
643
  async exportRelationships(baseRelClassFullName) {
608
644
  if (!this.visitRelationships) {
609
- core_bentley_1.Logger.logTrace(loggerCategory, `visitRelationships=false, skipping exportRelationships()`);
645
+ core_bentley_1.Logger.logTrace(loggerCategory, "visitRelationships=false, skipping exportRelationships()");
610
646
  return;
611
647
  }
612
648
  core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationships(${baseRelClassFullName})`);
@@ -617,7 +653,9 @@ class IModelExporter {
617
653
  await this.sourceDb.withPreparedStatement(sql, async (statement) => {
618
654
  while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
619
655
  const relationshipId = statement.getValue(0).getId();
620
- const relationshipClass = statement.getValue(1).getClassNameForClassId();
656
+ const relationshipClass = statement
657
+ .getValue(1)
658
+ .getClassNameForClassId();
621
659
  await this.exportRelationship(relationshipClass, relationshipId); // must call exportRelationship using the actual classFullName, not baseRelClassFullName
622
660
  await this._yieldManager.allowYield();
623
661
  }
@@ -630,7 +668,8 @@ class IModelExporter {
630
668
  return;
631
669
  }
632
670
  let isUpdate;
633
- if (undefined !== this._sourceDbChanges) { // is changeset information available?
671
+ if (undefined !== this._sourceDbChanges) {
672
+ // is changeset information available?
634
673
  if (this._sourceDbChanges.relationship.insertIds.has(relInstanceId)) {
635
674
  isUpdate = false;
636
675
  }
@@ -659,7 +698,7 @@ class IModelExporter {
659
698
  /** Tracks incremental progress */
660
699
  async trackProgress() {
661
700
  this._progressCounter++;
662
- if (0 === (this._progressCounter % this.progressInterval)) {
701
+ if (0 === this._progressCounter % this.progressInterval) {
663
702
  return this.handler.onProgress();
664
703
  }
665
704
  }
@@ -690,9 +729,9 @@ class IModelExporter {
690
729
  this.visitElements = state.visitElements;
691
730
  this.visitRelationships = state.visitRelationships;
692
731
  this._excludedCodeSpecNames = new Set(state.excludedCodeSpecNames);
693
- this._excludedElementIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementIds),
694
- this._excludedElementCategoryIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementCategoryIds),
695
- this._excludedElementClasses = new Set(state.excludedElementClassNames.map((c) => this.sourceDb.getJsClass(c)));
732
+ (this._excludedElementIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementIds)),
733
+ (this._excludedElementCategoryIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementCategoryIds)),
734
+ (this._excludedElementClasses = new Set(state.excludedElementClassNames.map((c) => this.sourceDb.getJsClass(c))));
696
735
  this._exportElementAspectsStrategy.loadExcludedElementAspectClasses(state.excludedElementAspectClassFullNames);
697
736
  this._excludedRelationshipClasses = new Set(state.excludedRelationshipClassNames.map((c) => this.sourceDb.getJsClass(c)));
698
737
  this.loadAdditionalStateJson(state.additionalState);
@@ -715,7 +754,10 @@ class IModelExporter {
715
754
  excludedElementIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementIds),
716
755
  excludedElementCategoryIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementCategoryIds),
717
756
  excludedElementClassNames: Array.from(this._excludedElementClasses, (cls) => cls.classFullName),
718
- excludedElementAspectClassFullNames: [...this._exportElementAspectsStrategy.excludedElementAspectClassFullNames],
757
+ excludedElementAspectClassFullNames: [
758
+ ...this._exportElementAspectsStrategy
759
+ .excludedElementAspectClassFullNames,
760
+ ],
719
761
  excludedRelationshipClassNames: Array.from(this._excludedRelationshipClasses, (cls) => cls.classFullName),
720
762
  additionalState: this.getAdditionalStateJson(),
721
763
  };
@@ -724,7 +766,7 @@ class IModelExporter {
724
766
  exports.IModelExporter = IModelExporter;
725
767
  /** Class for holding change information.
726
768
  * @beta
727
- */
769
+ */
728
770
  class ChangedInstanceOps {
729
771
  constructor() {
730
772
  this.insertIds = new Set();
@@ -734,11 +776,11 @@ class ChangedInstanceOps {
734
776
  /** Initializes the object from IModelJsNative.ChangedInstanceOpsProps. */
735
777
  addFromJson(val) {
736
778
  if (undefined !== val) {
737
- if ((undefined !== val.insert) && (Array.isArray(val.insert)))
779
+ if (undefined !== val.insert && Array.isArray(val.insert))
738
780
  val.insert.forEach((id) => this.insertIds.add(id));
739
- if ((undefined !== val.update) && (Array.isArray(val.update)))
781
+ if (undefined !== val.update && Array.isArray(val.update))
740
782
  val.update.forEach((id) => this.updateIds.add(id));
741
- if ((undefined !== val.delete) && (Array.isArray(val.delete)))
783
+ if (undefined !== val.delete && Array.isArray(val.delete))
742
784
  val.delete.forEach((id) => this.deleteIds.add(id));
743
785
  }
744
786
  }
@@ -749,51 +791,174 @@ exports.ChangedInstanceOps = ChangedInstanceOps;
749
791
  * @beta
750
792
  */
751
793
  class ChangedInstanceIds {
752
- constructor() {
794
+ constructor(db) {
753
795
  this.codeSpec = new ChangedInstanceOps();
754
796
  this.model = new ChangedInstanceOps();
755
797
  this.element = new ChangedInstanceOps();
756
798
  this.aspect = new ChangedInstanceOps();
757
799
  this.relationship = new ChangedInstanceOps();
758
800
  this.font = new ChangedInstanceOps();
801
+ this._db = db;
802
+ }
803
+ async setupECClassIds() {
804
+ this._codeSpecSubclassIds = new Set();
805
+ this._modelSubclassIds = new Set();
806
+ this._elementSubclassIds = new Set();
807
+ this._aspectSubclassIds = new Set();
808
+ this._relationshipSubclassIds = new Set();
809
+ const addECClassIdsToSet = async (setToModify, baseClass) => {
810
+ for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})`)) {
811
+ setToModify.add(row.ECInstanceId);
812
+ }
813
+ };
814
+ const promises = [
815
+ addECClassIdsToSet(this._codeSpecSubclassIds, "BisCore.CodeSpec"),
816
+ addECClassIdsToSet(this._modelSubclassIds, "BisCore.Model"),
817
+ addECClassIdsToSet(this._elementSubclassIds, "BisCore.Element"),
818
+ addECClassIdsToSet(this._aspectSubclassIds, "BisCore.ElementUniqueAspect"),
819
+ addECClassIdsToSet(this._aspectSubclassIds, "BisCore.ElementMultiAspect"),
820
+ addECClassIdsToSet(this._relationshipSubclassIds, "BisCore.ElementRefersToElements"),
821
+ ];
822
+ await Promise.all(promises);
823
+ }
824
+ get _ecClassIdsInitialized() {
825
+ return (this._codeSpecSubclassIds &&
826
+ this._modelSubclassIds &&
827
+ this._elementSubclassIds &&
828
+ this._aspectSubclassIds &&
829
+ this._relationshipSubclassIds);
830
+ }
831
+ isRelationship(ecClassId) {
832
+ return this._relationshipSubclassIds?.has(ecClassId);
833
+ }
834
+ isCodeSpec(ecClassId) {
835
+ return this._codeSpecSubclassIds?.has(ecClassId);
836
+ }
837
+ isAspect(ecClassId) {
838
+ return this._aspectSubclassIds?.has(ecClassId);
839
+ }
840
+ isModel(ecClassId) {
841
+ return this._modelSubclassIds?.has(ecClassId);
842
+ }
843
+ isElement(ecClassId) {
844
+ return this._elementSubclassIds?.has(ecClassId);
845
+ }
846
+ /**
847
+ * Adds the provided [[ChangedECInstance]] to the appropriate set of changes by class type (codeSpec, model, element, aspect, or relationship) maintained by this instance of ChangedInstanceIds.
848
+ * If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
849
+ * from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
850
+ * @param change ChangedECInstance which has the ECInstanceId, changeType (insert, update, delete) and ECClassId of the changed entity
851
+ */
852
+ async addChange(change) {
853
+ if (!this._ecClassIdsInitialized)
854
+ await this.setupECClassIds();
855
+ const ecClassId = change.ECClassId ?? change.$meta?.fallbackClassId;
856
+ if (ecClassId === undefined)
857
+ throw new Error(`ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change?.$meta?.tables}`);
858
+ const changeType = change.$meta?.op;
859
+ if (changeType === undefined)
860
+ throw new Error(`ChangeType was undefined for id: ${change.ECInstanceId}.`);
861
+ if (this.isRelationship(ecClassId))
862
+ this.handleChange(this.relationship, changeType, change.ECInstanceId);
863
+ else if (this.isCodeSpec(ecClassId))
864
+ this.handleChange(this.codeSpec, changeType, change.ECInstanceId);
865
+ else if (this.isAspect(ecClassId))
866
+ this.handleChange(this.aspect, changeType, change.ECInstanceId);
867
+ else if (this.isModel(ecClassId))
868
+ this.handleChange(this.model, changeType, change.ECInstanceId);
869
+ else if (this.isElement(ecClassId))
870
+ this.handleChange(this.element, changeType, change.ECInstanceId);
871
+ }
872
+ handleChange(changedInstanceOps, changeType, id) {
873
+ // if changeType is a delete and we already have the id in the inserts then we can remove the id from the inserts.
874
+ // if changeType is a delete and we already have the id in the updates then we can remove the id from the updates AND add it to the deletes.
875
+ // if changeType is an insert and we already have the id in the deletes then we can remove the id from the deletes AND add it to the inserts.
876
+ if (changeType === "Inserted") {
877
+ changedInstanceOps.insertIds.add(id);
878
+ changedInstanceOps.deleteIds.delete(id);
879
+ }
880
+ else if (changeType === "Updated") {
881
+ if (!changedInstanceOps.insertIds.has(id))
882
+ changedInstanceOps.updateIds.add(id);
883
+ }
884
+ else if (changeType === "Deleted") {
885
+ // If we've inserted the entity at some point already and now we're seeing a delete. We can simply remove the entity from our inserted ids without adding it to deletedIds.
886
+ if (changedInstanceOps.insertIds.has(id))
887
+ changedInstanceOps.insertIds.delete(id);
888
+ else {
889
+ changedInstanceOps.updateIds.delete(id);
890
+ changedInstanceOps.deleteIds.add(id);
891
+ }
892
+ }
759
893
  }
760
894
  /**
761
895
  * Initializes a new ChangedInstanceIds object with information taken from a range of changesets.
762
- * @deprecated in 0.1.x. Pass a [[ChangedInstanceIdsInitOptions]] object instead of a changeset id
763
- * @param accessToken Access token.
764
- * @param iModel IModel briefcase whose changesets will be queried.
765
- * @param firstChangesetId Changeset id.
766
- * @note Modified element information will be taken from a range of changesets. First changeset in a range will be the 'firstChangesetId', the last will be whichever changeset the 'iModel' briefcase is currently opened on.
767
- */
768
- static async initialize(accessTokenOrOpts, iModel, startChangesetId) {
769
- const opts = typeof accessTokenOrOpts === "object"
770
- ? accessTokenOrOpts
771
- : {
772
- accessToken: accessTokenOrOpts,
773
- iModel: iModel,
774
- startChangeset: { id: startChangesetId },
775
- };
776
- const accessToken = opts.accessToken;
896
+ */
897
+ static async initialize(opts) {
898
+ if ("changedInstanceIds" in opts)
899
+ return opts.changedInstanceIds;
777
900
  const iModelId = opts.iModel.iModelId;
778
- const first = opts.startChangeset?.index
779
- ?? (await core_backend_1.IModelHost.hubAccess.queryChangeset({ iModelId, changeset: { id: opts.startChangeset.id }, accessToken })).index;
780
- const end = opts.iModel.changeset.index
781
- ?? (await core_backend_1.IModelHost.hubAccess.queryChangeset({ iModelId, changeset: { id: opts.iModel.changeset.id }, accessToken })).index;
782
- const changesets = await core_backend_1.IModelHost.hubAccess.downloadChangesets({ accessToken, iModelId, range: { first, end }, targetDir: core_backend_1.BriefcaseManager.getChangeSetsPath(iModelId) });
783
- const changedInstanceIds = new ChangedInstanceIds();
784
- const changesetFiles = changesets.map((c) => c.pathname);
785
- const statusOrResult = opts.iModel.nativeDb.extractChangedInstanceIdsFromChangeSets(changesetFiles);
786
- if (statusOrResult.error) {
787
- throw new core_common_1.IModelError(statusOrResult.error.status, "Error processing changeset");
788
- }
789
- const result = statusOrResult.result;
790
- (0, core_bentley_1.assert)(result !== undefined);
791
- changedInstanceIds.codeSpec.addFromJson(result.codeSpec);
792
- changedInstanceIds.model.addFromJson(result.model);
793
- changedInstanceIds.element.addFromJson(result.element);
794
- changedInstanceIds.aspect.addFromJson(result.aspect);
795
- changedInstanceIds.relationship.addFromJson(result.relationship);
796
- changedInstanceIds.font.addFromJson(result.font);
901
+ const accessToken = opts.accessToken;
902
+ const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
903
+ const changesetRanges = startChangeset !== undefined
904
+ ? [
905
+ [
906
+ startChangeset.index ??
907
+ (await core_backend_1.IModelHost.hubAccess.queryChangeset({
908
+ iModelId,
909
+ changeset: {
910
+ id: startChangeset.id ?? opts.iModel.changeset.id,
911
+ },
912
+ accessToken,
913
+ })).index,
914
+ opts.iModel.changeset.index ??
915
+ (await core_backend_1.IModelHost.hubAccess.queryChangeset({
916
+ iModelId,
917
+ changeset: { id: opts.iModel.changeset.id },
918
+ accessToken,
919
+ })).index,
920
+ ],
921
+ ]
922
+ : "changesetRanges" in opts
923
+ ? opts.changesetRanges
924
+ : undefined;
925
+ const csFileProps = changesetRanges !== undefined
926
+ ? (await Promise.all(changesetRanges.map(async ([first, end]) => core_backend_1.IModelHost.hubAccess.downloadChangesets({
927
+ accessToken,
928
+ iModelId,
929
+ range: { first, end },
930
+ targetDir: core_backend_1.BriefcaseManager.getChangeSetsPath(iModelId),
931
+ })))).flat()
932
+ : "csFileProps" in opts
933
+ ? opts.csFileProps
934
+ : undefined;
935
+ if (csFileProps === undefined)
936
+ return undefined;
937
+ const changedInstanceIds = new ChangedInstanceIds(opts.iModel);
938
+ const relationshipECClassIdsToSkip = new Set();
939
+ for await (const row of opts.iModel.createQueryReader("SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (BisCore.ElementDrivesElement)")) {
940
+ relationshipECClassIdsToSkip.add(row.ECInstanceId);
941
+ }
942
+ for (const csFile of csFileProps) {
943
+ const csReader = core_backend_1.SqliteChangesetReader.openFile({
944
+ fileName: csFile.pathname,
945
+ db: opts.iModel,
946
+ disableSchemaCheck: true,
947
+ });
948
+ const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
949
+ const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier();
950
+ while (csAdaptor.step()) {
951
+ ecChangeUnifier.appendFrom(csAdaptor);
952
+ }
953
+ const changes = [...ecChangeUnifier.instances];
954
+ for (const change of changes) {
955
+ if (change.ECClassId !== undefined &&
956
+ relationshipECClassIdsToSkip.has(change.ECClassId))
957
+ continue;
958
+ await changedInstanceIds.addChange(change);
959
+ }
960
+ csReader.close();
961
+ }
797
962
  return changedInstanceIds;
798
963
  }
799
964
  }