@itwin/imodel-transformer 2.0.0-dev.3 → 2.0.0-dev.31

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/lib/cjs/BranchProvenanceInitializer.d.ts.map +1 -1
  2. package/lib/cjs/BranchProvenanceInitializer.js +24 -18
  3. package/lib/cjs/BranchProvenanceInitializer.js.map +1 -1
  4. package/lib/cjs/ChangesetScanner.d.ts +60 -0
  5. package/lib/cjs/ChangesetScanner.d.ts.map +1 -0
  6. package/lib/cjs/ChangesetScanner.js +101 -0
  7. package/lib/cjs/ChangesetScanner.js.map +1 -0
  8. package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +1 -1
  9. package/lib/cjs/DetachedExportElementAspectsStrategy.js +8 -6
  10. package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +1 -1
  11. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -1
  12. package/lib/cjs/ECReferenceTypesCache.js +60 -38
  13. package/lib/cjs/ECReferenceTypesCache.js.map +1 -1
  14. package/lib/cjs/ElementCascadingDeleter.d.ts +3 -3
  15. package/lib/cjs/ElementCascadingDeleter.d.ts.map +1 -1
  16. package/lib/cjs/ElementCascadingDeleter.js +12 -12
  17. package/lib/cjs/ElementCascadingDeleter.js.map +1 -1
  18. package/lib/cjs/EntityUnifier.d.ts +4 -8
  19. package/lib/cjs/EntityUnifier.d.ts.map +1 -1
  20. package/lib/cjs/EntityUnifier.js +6 -24
  21. package/lib/cjs/EntityUnifier.js.map +1 -1
  22. package/lib/cjs/ExportElementAspectsStrategy.d.ts +4 -4
  23. package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +1 -1
  24. package/lib/cjs/ExportElementAspectsStrategy.js +1 -1
  25. package/lib/cjs/ExportElementAspectsStrategy.js.map +1 -1
  26. package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +1 -1
  27. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +17 -16
  28. package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +1 -1
  29. package/lib/cjs/IModelCloneContext.d.ts +3 -3
  30. package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
  31. package/lib/cjs/IModelCloneContext.js +70 -54
  32. package/lib/cjs/IModelCloneContext.js.map +1 -1
  33. package/lib/cjs/IModelExporter.d.ts +33 -23
  34. package/lib/cjs/IModelExporter.d.ts.map +1 -1
  35. package/lib/cjs/IModelExporter.js +200 -152
  36. package/lib/cjs/IModelExporter.js.map +1 -1
  37. package/lib/cjs/IModelImporter.d.ts +32 -24
  38. package/lib/cjs/IModelImporter.d.ts.map +1 -1
  39. package/lib/cjs/IModelImporter.js +199 -127
  40. package/lib/cjs/IModelImporter.js.map +1 -1
  41. package/lib/cjs/IModelTransformer.d.ts +94 -186
  42. package/lib/cjs/IModelTransformer.d.ts.map +1 -1
  43. package/lib/cjs/IModelTransformer.js +502 -1075
  44. package/lib/cjs/IModelTransformer.js.map +1 -1
  45. package/lib/cjs/IModelTransformerError.d.ts +74 -0
  46. package/lib/cjs/IModelTransformerError.d.ts.map +1 -0
  47. package/lib/cjs/IModelTransformerError.js +83 -0
  48. package/lib/cjs/IModelTransformerError.js.map +1 -0
  49. package/lib/cjs/ProvenanceManager.d.ts +165 -0
  50. package/lib/cjs/ProvenanceManager.d.ts.map +1 -0
  51. package/lib/cjs/ProvenanceManager.js +735 -0
  52. package/lib/cjs/ProvenanceManager.js.map +1 -0
  53. package/lib/cjs/SyncTypeResolver.d.ts +34 -0
  54. package/lib/cjs/SyncTypeResolver.d.ts.map +1 -0
  55. package/lib/cjs/SyncTypeResolver.js +96 -0
  56. package/lib/cjs/SyncTypeResolver.js.map +1 -0
  57. package/lib/cjs/TransformerLoggerCategory.d.ts +2 -1
  58. package/lib/cjs/TransformerLoggerCategory.d.ts.map +1 -1
  59. package/lib/cjs/TransformerLoggerCategory.js +1 -0
  60. package/lib/cjs/TransformerLoggerCategory.js.map +1 -1
  61. package/lib/cjs/imodel-transformer.d.ts +5 -0
  62. package/lib/cjs/imodel-transformer.d.ts.map +1 -1
  63. package/lib/cjs/imodel-transformer.js +26 -7
  64. package/lib/cjs/imodel-transformer.js.map +1 -1
  65. package/package.json +22 -17
@@ -15,6 +15,8 @@ const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
15
15
  const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
16
16
  const nodeAssert = require("assert");
17
17
  const ExportElementAspectsWithElementsStrategy_1 = require("./ExportElementAspectsWithElementsStrategy");
18
+ const ChangesetScanner_1 = require("./ChangesetScanner");
19
+ const IModelTransformerError_1 = require("./IModelTransformerError");
18
20
  const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelExporter;
19
21
  /** Handles the events generated by IModelExporter.
20
22
  * @note Change information is available when `IModelExportHandler` methods are invoked via [IModelExporter.exportChanges]($transformer), but not available when invoked via [IModelExporter.exportAll]($transformer).
@@ -26,7 +28,7 @@ class IModelExportHandler {
26
28
  /** If `true` is returned, then the CodeSpec will be exported.
27
29
  * @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
28
30
  */
29
- shouldExportCodeSpec(_codeSpec) {
31
+ async shouldExportCodeSpec(_codeSpec) {
30
32
  return true;
31
33
  }
32
34
  /** Called when a CodeSpec should be exported.
@@ -34,37 +36,37 @@ class IModelExportHandler {
34
36
  * @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.
35
37
  * @note This should be overridden to actually do the export.
36
38
  */
37
- onExportCodeSpec(_codeSpec, _isUpdate) { }
39
+ async onExportCodeSpec(_codeSpec, _isUpdate) { }
38
40
  /** Called when a font should be exported.
39
41
  * @param font The font to export
40
42
  * @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.
41
43
  * @note This should be overridden to actually do the export.
42
44
  */
43
- onExportFont(_font, _isUpdate) { }
45
+ async onExportFont(_font, _isUpdate) { }
44
46
  /** Called when a model should be exported.
45
47
  * @param model The model to export
46
48
  * @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.
47
49
  * @note This should be overridden to actually do the export.
48
50
  */
49
- onExportModel(_model, _isUpdate) { }
51
+ async onExportModel(_model, _isUpdate) { }
50
52
  /** Called when a model should be deleted. */
51
- onDeleteModel(_modelId) { }
53
+ async onDeleteModel(_modelId) { }
52
54
  /** If `true` is returned, then the element will be exported.
53
55
  * @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`.
54
56
  */
55
- shouldExportElement(_element) {
57
+ async shouldExportElement(_element) {
56
58
  return true;
57
59
  }
58
60
  /** Called when element is skipped instead of exported.
59
61
  * @note When an element is skipped, exporter will not export any of its child elements. Because of this, [[onSkipElement]] will not be invoked for any children of a "skipped" element.
60
62
  */
61
- onSkipElement(_elementId) { }
63
+ async onSkipElement(_elementId) { }
62
64
  /** Called when an element should be exported.
63
65
  * @param element The element to export
64
66
  * @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.
65
67
  * @note This should be overridden to actually do the export.
66
68
  */
67
- onExportElement(_element, _isUpdate) { }
69
+ async onExportElement(_element, _isUpdate) { }
68
70
  /**
69
71
  * Do any asynchronous actions before exporting an element
70
72
  * @note Do not implement this handler manually, it is internal, it will be removed.
@@ -73,11 +75,11 @@ class IModelExportHandler {
73
75
  */
74
76
  async preExportElement(_element) { }
75
77
  /** Called when an element should be deleted. */
76
- onDeleteElement(_elementId) { }
78
+ async onDeleteElement(_elementId) { }
77
79
  /** If `true` is returned, then the ElementAspect will be exported.
78
80
  * @note This method can optionally be overridden to exclude an individual ElementAspect from the export. The base implementation always returns `true`.
79
81
  */
80
- shouldExportElementAspect(_aspect) {
82
+ async shouldExportElementAspect(_aspect) {
81
83
  return true;
82
84
  }
83
85
  /** Called when an ElementUniqueAspect should be exported.
@@ -85,15 +87,15 @@ class IModelExportHandler {
85
87
  * @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.
86
88
  * @note This should be overridden to actually do the export.
87
89
  */
88
- onExportElementUniqueAspect(_aspect, _isUpdate) { }
90
+ async onExportElementUniqueAspect(_aspect, _isUpdate) { }
89
91
  /** Called when ElementMultiAspects should be exported.
90
92
  * @note This should be overridden to actually do the export.
91
93
  */
92
- onExportElementMultiAspects(_aspects) { }
94
+ async onExportElementMultiAspects(_aspects) { }
93
95
  /** If `true` is returned, then the relationship will be exported.
94
96
  * @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
95
97
  */
96
- shouldExportRelationship(_relationship) {
98
+ async shouldExportRelationship(_relationship) {
97
99
  return true;
98
100
  }
99
101
  /** Called when a Relationship should be exported.
@@ -101,13 +103,13 @@ class IModelExportHandler {
101
103
  * @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.
102
104
  * @note This should be overridden to actually do the export.
103
105
  */
104
- onExportRelationship(_relationship, _isUpdate) { }
106
+ async onExportRelationship(_relationship, _isUpdate) { }
105
107
  /** Called when a relationship should be deleted. */
106
- onDeleteRelationship(_relInstanceId) { }
108
+ async onDeleteRelationship(_relInstanceId) { }
107
109
  /** If `true` is returned, then the schema will be exported.
108
110
  * @note This method can optionally be overridden to exclude an individual schema from the export. The base implementation always returns `true`.
109
111
  */
110
- shouldExportSchema(_schemaKey) {
112
+ async shouldExportSchema(_schemaKey) {
111
113
  return true;
112
114
  }
113
115
  /** Called when a schema should be exported.
@@ -175,7 +177,13 @@ class IModelExporter {
175
177
  /** The handler called by this IModelExporter. */
176
178
  get handler() {
177
179
  if (undefined === this._handler) {
178
- throw new Error("IModelExportHandler not registered");
180
+ core_bentley_1.ITwinError.throwError({
181
+ iTwinErrorId: {
182
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
183
+ key: IModelTransformerError_1.IModelTransformerError.ExportHandlerNotRegistered,
184
+ },
185
+ message: "IModelExportHandler not registered",
186
+ });
179
187
  }
180
188
  return this._handler;
181
189
  }
@@ -198,9 +206,9 @@ class IModelExporter {
198
206
  constructor(sourceDb, elementAspectsStrategy = ExportElementAspectsWithElementsStrategy_1.ExportElementAspectsWithElementsStrategy) {
199
207
  this.sourceDb = sourceDb;
200
208
  this._exportElementAspectsStrategy = new elementAspectsStrategy(this.sourceDb, {
201
- onExportElementMultiAspects: (aspects) => this.handler.onExportElementMultiAspects(aspects),
202
- onExportElementUniqueAspect: (aspect, isUpdate) => this.handler.onExportElementUniqueAspect(aspect, isUpdate),
203
- shouldExportElementAspect: (aspect) => this.handler.shouldExportElementAspect(aspect),
209
+ onExportElementMultiAspects: async (aspects) => this.handler.onExportElementMultiAspects(aspects),
210
+ onExportElementUniqueAspect: async (aspect, isUpdate) => this.handler.onExportElementUniqueAspect(aspect, isUpdate),
211
+ shouldExportElementAspect: async (aspect) => this.handler.shouldExportElementAspect(aspect),
204
212
  trackProgress: async () => this.trackProgress(),
205
213
  });
206
214
  }
@@ -262,6 +270,7 @@ class IModelExporter {
262
270
  }
263
271
  /** Export changes from the source iModel.
264
272
  * Inserts, updates, and deletes are determined by inspecting the changeset(s).
273
+ * @throws [[ITwinError]] identified by [[IModelTransformerError.NoChangesets]] if the source iModel has no changesets and no custom changes. Call [[exportAll]] to export all content.
265
274
  * @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired
266
275
  * range and open the source iModel as of the end (inclusive) of the desired range.
267
276
  * @note the changedInstanceIds are just for this call to exportChanges, so you must continue to pass it in
@@ -269,23 +278,45 @@ class IModelExporter {
269
278
  */
270
279
  async exportChanges(args) {
271
280
  if (!this.sourceDb.isBriefcaseDb())
272
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Must be a briefcase to export changes");
273
- if ("" === this.sourceDb.changeset.id &&
274
- !this.sourceDbChanges?.hasChanges) {
275
- await this.exportAll(); // no changesets or custom changes, so revert to exportAll
276
- return;
281
+ core_bentley_1.ITwinError.throwError({
282
+ iTwinErrorId: {
283
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
284
+ key: IModelTransformerError_1.IModelTransformerError.ExportChangesRequiresBriefcase,
285
+ },
286
+ message: "Must be a briefcase to export changes",
287
+ });
288
+ let initOpts = args ?? {};
289
+ const hasExplicitChangeSource = "csFileProps" in initOpts ||
290
+ "changedInstanceIds" in initOpts ||
291
+ "changesetRanges" in initOpts ||
292
+ "startChangeset" in initOpts;
293
+ const currentChangesetId = this.sourceDb.changeset.id;
294
+ if (!hasExplicitChangeSource && currentChangesetId !== "") {
295
+ initOpts = {
296
+ ...initOpts,
297
+ startChangeset: { id: currentChangesetId },
298
+ };
277
299
  }
278
- const startChangeset = args && "startChangeset" in args ? args.startChangeset : undefined;
279
- const initOpts = {
280
- startChangeset: { id: startChangeset?.id },
281
- };
282
300
  await this.initialize(initOpts);
301
+ if (currentChangesetId === "" && !this.sourceDbChanges?.hasChanges) {
302
+ core_bentley_1.ITwinError.throwError({
303
+ iTwinErrorId: {
304
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
305
+ key: IModelTransformerError_1.IModelTransformerError.NoChangesets,
306
+ },
307
+ message: "Cannot export changes because the source iModel has no changesets or custom changes. Call exportAll() to export all content.",
308
+ });
309
+ }
283
310
  // _sourceDbChanges are initialized in this.initialize
284
311
  nodeAssert(this._sourceDbChanges !== undefined, "sourceDbChanges must be initialized.");
285
312
  await this.exportCodeSpecs();
286
313
  await this.exportFonts();
287
314
  if (initOpts.skipPropagateChangesToRootElements) {
288
- await this.exportModelContents(core_common_1.IModel.repositoryModelId);
315
+ // The root Subject is in the RepositoryModel. Traverse its children
316
+ // separately, then export other top-level repository elements while
317
+ // excluding the root so no element is visited twice.
318
+ await this.exportChildElements(core_common_1.IModel.rootSubjectId);
319
+ await this.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true);
289
320
  await this.exportSubModels(core_common_1.IModel.repositoryModelId);
290
321
  }
291
322
  else {
@@ -297,7 +328,7 @@ class IModelExporter {
297
328
  if (this.visitElements) {
298
329
  // must delete models first since they have a constraint on the submodeling element which may also be deleted
299
330
  for (const modelId of this._sourceDbChanges.model.deleteIds) {
300
- this.handler.onDeleteModel(modelId);
331
+ await this.handler.onDeleteModel(modelId);
301
332
  }
302
333
  for (const elementId of this._sourceDbChanges.element.deleteIds) {
303
334
  // We don't know how the handler wants to handle deletions, and we don't have enough information
@@ -307,7 +338,7 @@ class IModelExporter {
307
338
  // In the future, the handler may be responsible for doing the work of finding out which elements were cascade deleted,
308
339
  // and returning them for the exporter to use to avoid double-deleting with error ignoring
309
340
  try {
310
- this.handler.onDeleteElement(elementId);
341
+ await this.handler.onDeleteElement(elementId);
311
342
  }
312
343
  catch (err) {
313
344
  const isMissingErr = err instanceof core_common_1.IModelError &&
@@ -320,7 +351,7 @@ class IModelExporter {
320
351
  if (this.visitRelationships) {
321
352
  for (const relInstanceId of this._sourceDbChanges.relationship
322
353
  .deleteIds) {
323
- this.handler.onDeleteRelationship(relInstanceId);
354
+ await this.handler.onDeleteRelationship(relInstanceId);
324
355
  }
325
356
  }
326
357
  // Enable consecutive exportChanges runs without the need to re-instantiate the exporter.
@@ -345,26 +376,33 @@ class IModelExporter {
345
376
  `}
346
377
  ORDER BY ECInstanceId
347
378
  `;
348
- const schemaNamesToExport = [];
349
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
350
- this.sourceDb.withPreparedStatement(sql, (statement) => {
351
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
352
- const schemaName = statement.getValue(0).getString();
353
- const versionMajor = statement.getValue(1).getInteger();
354
- const versionWrite = statement.getValue(2).getInteger();
355
- const versionMinor = statement.getValue(3).getInteger();
356
- const schemaKey = new ecschema_metadata_1.SchemaKey(schemaName, new ecschema_metadata_1.ECVersion(versionMajor, versionWrite, versionMinor));
357
- if (this.handler.shouldExportSchema(schemaKey)) {
358
- schemaNamesToExport.push(schemaName);
359
- }
379
+ const schemaKeysToExport = [];
380
+ for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
381
+ usePrimaryConn: true,
382
+ })) {
383
+ const schemaName = row[0];
384
+ const versionMajor = row[1];
385
+ const versionWrite = row[2];
386
+ const versionMinor = row[3];
387
+ const schemaKey = new ecschema_metadata_1.SchemaKey(schemaName, new ecschema_metadata_1.ECVersion(versionMajor, versionWrite, versionMinor));
388
+ if (await this.handler.shouldExportSchema(schemaKey)) {
389
+ schemaKeysToExport.push(schemaKey);
360
390
  }
361
- });
362
- if (schemaNamesToExport.length === 0)
391
+ }
392
+ if (schemaKeysToExport.length === 0)
363
393
  return;
364
- const schemaLoader = new ecschema_metadata_1.SchemaLoader((name) => this.sourceDb.getSchemaProps(name));
365
- await Promise.all(schemaNamesToExport.map(async (schemaName) => {
366
- const schema = schemaLoader.getSchema(schemaName);
367
- core_bentley_1.Logger.logTrace(loggerCategory, `exportSchema(${schemaName})`);
394
+ await Promise.all(schemaKeysToExport.map(async (schemaKey) => {
395
+ const schema = await this.sourceDb.schemaContext.getSchema(schemaKey);
396
+ if (!schema) {
397
+ core_bentley_1.ITwinError.throwError({
398
+ iTwinErrorId: {
399
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
400
+ key: IModelTransformerError_1.IModelTransformerError.SchemaLoadFailed,
401
+ },
402
+ message: `Failed to load schema: ${schemaKey.name}`,
403
+ });
404
+ }
405
+ core_bentley_1.Logger.logTrace(loggerCategory, `exportSchema(${schemaKey.name})`);
368
406
  return this.handler.onExportSchema(schema);
369
407
  }));
370
408
  }
@@ -378,15 +416,11 @@ class IModelExporter {
378
416
  async exportCodeSpecs() {
379
417
  core_bentley_1.Logger.logTrace(loggerCategory, "exportCodeSpecs()");
380
418
  const sql = "SELECT Name FROM BisCore:CodeSpec ORDER BY ECInstanceId";
381
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
382
- await this.sourceDb.withPreparedStatement(sql,
383
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
384
- async (statement) => {
385
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
386
- const codeSpecName = statement.getValue(0).getString();
387
- await this.exportCodeSpecByName(codeSpecName);
388
- }
389
- });
419
+ for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
420
+ usePrimaryConn: true,
421
+ })) {
422
+ await this.exportCodeSpecByName(row[0]);
423
+ }
390
424
  }
391
425
  /** Export a single CodeSpec from the source iModel.
392
426
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
@@ -412,9 +446,9 @@ class IModelExporter {
412
446
  return;
413
447
  }
414
448
  // CodeSpec has passed standard exclusion rules, now give handler a chance to accept/reject export
415
- if (this.handler.shouldExportCodeSpec(codeSpec)) {
449
+ if (await this.handler.shouldExportCodeSpec(codeSpec)) {
416
450
  core_bentley_1.Logger.logTrace(loggerCategory, `exportCodeSpec(${codeSpecName})${this.getChangeOpSuffix(isUpdate)}`);
417
- this.handler.onExportCodeSpec(codeSpec, isUpdate);
451
+ await this.handler.onExportCodeSpec(codeSpec, isUpdate);
418
452
  return this.trackProgress();
419
453
  }
420
454
  }
@@ -430,35 +464,54 @@ class IModelExporter {
430
464
  */
431
465
  async exportFonts() {
432
466
  core_bentley_1.Logger.logTrace(loggerCategory, "exportFonts()");
433
- for (const font of this.sourceDb.fontMap.fonts.values()) {
434
- await this.exportFontByNumber(font.id);
467
+ for (const font of this.sourceDb.fonts.queryMappedFamilies()) {
468
+ await this.exportFontByFontProps(font);
435
469
  }
436
470
  }
437
471
  /** Export a single font from the source iModel.
438
- * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
472
+ * @note multiple fonts can have the same font name, if a font with a specific type is needed use exportFontByFontFamilyDescriptor.
473
+ * If not this will only export the first font with this type in the db.
439
474
  */
440
475
  async exportFontByName(fontName) {
441
476
  core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByName(${fontName})`);
442
- const font = this.sourceDb.fontMap.getFont(fontName);
443
- if (undefined !== font) {
444
- await this.exportFontByNumber(font.id);
477
+ const fontId = this.sourceDb.fonts.findId({
478
+ name: fontName,
479
+ });
480
+ if (undefined !== fontId) {
481
+ await this.exportFontByNumber(fontId);
482
+ }
483
+ }
484
+ /** Export a single font from the source iModel.
485
+ * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
486
+ */
487
+ async exportFontByFontProps(fontProps) {
488
+ core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByFamily(${fontProps.name}, ${fontProps.type})`);
489
+ await this.handler.onExportFont(fontProps, true);
490
+ return this.trackProgress();
491
+ }
492
+ /** Export a single font from the source iModel.
493
+ * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
494
+ */
495
+ async exportFontByFontFamilyDescriptor(fontFamily) {
496
+ core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByFamilyDescriptor(${fontFamily.name}, ${fontFamily.type})`);
497
+ const fontId = await this.sourceDb.fonts.acquireId(fontFamily);
498
+ if (undefined !== fontId) {
499
+ await this.exportFontByFontProps({ ...fontFamily, id: fontId });
445
500
  }
446
501
  }
447
502
  /** Export a single font from the source iModel.
448
503
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
449
504
  */
450
505
  async exportFontByNumber(fontNumber) {
451
- /** sourceDbChanges now works by using TS ChangesetECAdaptor which doesn't pick up changes to fonts since fonts is not an ec table.
452
- * So lets always export fonts for the time being by always setting isUpdate = true.
506
+ /** ChangedInstanceIds contains EC-mapped changes, but the font table is not EC-mapped.
507
+ * Always export fonts because their changes are therefore absent from sourceDbChanges.
453
508
  * 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.
454
509
  * See https://github.com/iTwin/imodel-transformer/pull/135 for removed code.
455
510
  */
456
- const isUpdate = true;
457
511
  core_bentley_1.Logger.logTrace(loggerCategory, `exportFontById(${fontNumber})`);
458
- const font = this.sourceDb.fontMap.getFont(fontNumber);
512
+ const font = this.sourceDb.fonts.findDescriptor(fontNumber);
459
513
  if (undefined !== font) {
460
- this.handler.onExportFont(font, isUpdate);
461
- return this.trackProgress();
514
+ await this.exportFontByFontFamilyDescriptor(font);
462
515
  }
463
516
  }
464
517
  /** Export the model container, contents, and sub-models from the source iModel.
@@ -475,7 +528,7 @@ class IModelExporter {
475
528
  wantBRepData: this.wantGeometry,
476
529
  });
477
530
  core_bentley_1.Logger.logTrace(loggerCategory, `exportModel(${modeledElementId})`);
478
- if (this.shouldExportElement(modeledElement)) {
531
+ if (await this.shouldExportElement(modeledElement)) {
479
532
  await this.exportModelContainer(model);
480
533
  if (this.visitElements) {
481
534
  await this.exportModelContents(modeledElementId);
@@ -498,7 +551,7 @@ class IModelExporter {
498
551
  return; // not in changeset, don't export
499
552
  }
500
553
  }
501
- this.handler.onExportModel(model, isUpdate);
554
+ await this.handler.onExportModel(model, isUpdate);
502
555
  return this.trackProgress();
503
556
  }
504
557
  _yieldManager = new core_bentley_1.YieldManager();
@@ -533,19 +586,16 @@ class IModelExporter {
533
586
  else {
534
587
  sql = `SELECT ECInstanceId FROM ${elementClassFullName} WHERE Parent.Id IS NULL AND Model.Id=:modelId ORDER BY ECInstanceId`;
535
588
  }
536
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
537
- await this.sourceDb.withPreparedStatement(sql,
538
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
539
- async (statement) => {
540
- statement.bindId("modelId", modelId);
541
- if (skipRootSubject) {
542
- statement.bindId("rootSubjectId", core_common_1.IModel.rootSubjectId);
543
- }
544
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
545
- await this.exportElement(statement.getValue(0).getId());
546
- await this._yieldManager.allowYield();
547
- }
548
- });
589
+ const params = new core_common_1.QueryBinder().bindId("modelId", modelId);
590
+ if (skipRootSubject) {
591
+ params.bindId("rootSubjectId", core_common_1.IModel.rootSubjectId);
592
+ }
593
+ for await (const row of this.sourceDb.createQueryReader(sql, params, {
594
+ usePrimaryConn: true,
595
+ })) {
596
+ await this.exportElement(row.id);
597
+ await this._yieldManager.allowYield();
598
+ }
549
599
  }
550
600
  /** Export the sub-models directly below the specified model.
551
601
  * @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
@@ -555,22 +605,19 @@ class IModelExporter {
555
605
  const definitionModelIds = [];
556
606
  const otherModelIds = [];
557
607
  const sql = `SELECT ECInstanceId FROM ${core_backend_1.Model.classFullName} WHERE ParentModel.Id=:parentModelId ORDER BY ECInstanceId`;
558
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
559
- this.sourceDb.withPreparedStatement(sql,
560
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
561
- (statement) => {
562
- statement.bindId("parentModelId", parentModelId);
563
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
564
- const modelId = statement.getValue(0).getId();
565
- const model = this.sourceDb.models.getModel(modelId);
566
- if (model instanceof core_backend_1.DefinitionModel) {
567
- definitionModelIds.push(modelId);
568
- }
569
- else {
570
- otherModelIds.push(modelId);
571
- }
608
+ const params = new core_common_1.QueryBinder().bindId("parentModelId", parentModelId);
609
+ for await (const row of this.sourceDb.createQueryReader(sql, params, {
610
+ usePrimaryConn: true,
611
+ })) {
612
+ const modelId = row.id;
613
+ const model = this.sourceDb.models.getModel(modelId);
614
+ if (model instanceof core_backend_1.DefinitionModel) {
615
+ definitionModelIds.push(modelId);
572
616
  }
573
- });
617
+ else {
618
+ otherModelIds.push(modelId);
619
+ }
620
+ }
574
621
  // export DefinitionModels before other types of Models
575
622
  for (const definitionModelId of definitionModelIds) {
576
623
  await this.exportModel(definitionModelId);
@@ -583,7 +630,7 @@ class IModelExporter {
583
630
  * This considers the standard IModelExporter exclusion rules plus calls [IModelExportHandler.shouldExportElement]($transformer) for any custom exclusion rules.
584
631
  * @note This method is called from within [[exportChanges]] and [[exportAll]], so usually does not need to be called directly.
585
632
  */
586
- shouldExportElement(element) {
633
+ async shouldExportElement(element) {
587
634
  if (this._excludedElementIds.has(element.id)) {
588
635
  core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Id`);
589
636
  return false;
@@ -619,7 +666,7 @@ class IModelExporter {
619
666
  // Return early if the elementId is already in the excludedElementIds, that way we don't need to load the element from the db.
620
667
  if (this._excludedElementIds.has(elementId)) {
621
668
  core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementId} by Id`);
622
- this.handler.onSkipElement(elementId);
669
+ await this.handler.onSkipElement(elementId);
623
670
  return;
624
671
  }
625
672
  // are we processing changes?
@@ -628,6 +675,10 @@ class IModelExporter {
628
675
  : this._sourceDbChanges?.element.updateIds.has(elementId)
629
676
  ? true
630
677
  : undefined;
678
+ // Short-circuit: element is not in the changeset, skip its own export but still visit children
679
+ if (undefined !== this._sourceDbChanges && undefined === isUpdate) {
680
+ return this.exportChildElements(elementId);
681
+ }
631
682
  const element = this.sourceDb.elements.getElement({
632
683
  id: elementId,
633
684
  wantGeometry: this.wantGeometry,
@@ -635,15 +686,15 @@ class IModelExporter {
635
686
  });
636
687
  core_bentley_1.Logger.logTrace(loggerCategory, `exportElement(${element.id}, "${element.getDisplayLabel()}")${this.getChangeOpSuffix(isUpdate)}`);
637
688
  // the order and `await`ing of calls beyond here is depended upon by the IModelTransformer for a current bug workaround
638
- if (this.shouldExportElement(element)) {
689
+ if (await this.shouldExportElement(element)) {
639
690
  await this.handler.preExportElement(element);
640
- this.handler.onExportElement(element, isUpdate);
691
+ await this.handler.onExportElement(element, isUpdate);
641
692
  await this.trackProgress();
642
693
  await this._exportElementAspectsStrategy.exportElementAspectsForElement(elementId);
643
694
  return this.exportChildElements(elementId);
644
695
  }
645
696
  else {
646
- this.handler.onSkipElement(element.id);
697
+ await this.handler.onSkipElement(element.id);
647
698
  }
648
699
  }
649
700
  /** Export the child elements of the specified element from the source iModel.
@@ -676,23 +727,18 @@ class IModelExporter {
676
727
  return;
677
728
  }
678
729
  core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationships(${baseRelClassFullName})`);
679
- const sql = `SELECT r.ECInstanceId, r.ECClassId FROM ${baseRelClassFullName} r
730
+ const sql = `SELECT r.ECInstanceId, ec_className(r.ECClassId, 's.c') as className FROM ${baseRelClassFullName} r
680
731
  JOIN bis.Element s ON s.ECInstanceId = r.SourceECInstanceId
681
732
  JOIN bis.Element t ON t.ECInstanceId = r.TargetECInstanceId
682
733
  WHERE s.ECInstanceId IS NOT NULL AND t.ECInstanceId IS NOT NULL`;
683
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
684
- await this.sourceDb.withPreparedStatement(sql,
685
- // eslint-disable-next-line @itwin/no-internal, @typescript-eslint/no-deprecated
686
- async (statement) => {
687
- while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
688
- const relationshipId = statement.getValue(0).getId();
689
- const relationshipClass = statement
690
- .getValue(1)
691
- .getClassNameForClassId();
692
- await this.exportRelationship(relationshipClass, relationshipId); // must call exportRelationship using the actual classFullName, not baseRelClassFullName
693
- await this._yieldManager.allowYield();
694
- }
695
- });
734
+ for await (const row of this.sourceDb.createQueryReader(sql, undefined, {
735
+ usePrimaryConn: true,
736
+ })) {
737
+ const relationshipId = row.id;
738
+ const relationshipClass = row.className;
739
+ await this.exportRelationship(relationshipClass, relationshipId); // must call exportRelationship using the actual classFullName, not baseRelClassFullName
740
+ await this._yieldManager.allowYield();
741
+ }
696
742
  }
697
743
  /** Export a relationship from the source iModel. */
698
744
  async exportRelationship(relClassFullName, relInstanceId) {
@@ -723,8 +769,8 @@ class IModelExporter {
723
769
  }
724
770
  }
725
771
  // relationship has passed standard exclusion rules, now give handler a chance to accept/reject export
726
- if (this.handler.shouldExportRelationship(relationship)) {
727
- this.handler.onExportRelationship(relationship, isUpdate);
772
+ if (await this.handler.shouldExportRelationship(relationship)) {
773
+ await this.handler.onExportRelationship(relationship, isUpdate);
728
774
  await this.trackProgress();
729
775
  }
730
776
  }
@@ -795,7 +841,7 @@ class ChangedInstanceIds {
795
841
  this._relationshipSubclassIds = new Set();
796
842
  this._relationshipSubclassIdsToSkip = new Set();
797
843
  const addECClassIdsToSet = async (setToModify, baseClass) => {
798
- for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})`)) {
844
+ for await (const row of this._db.createQueryReader(`SELECT ECInstanceId FROM ECDbMeta.ECClassDef where ECInstanceId IS (${baseClass})`, undefined, { usePrimaryConn: true })) {
799
845
  setToModify.add(row.ECInstanceId);
800
846
  }
801
847
  };
@@ -845,20 +891,32 @@ class ChangedInstanceIds {
845
891
  !this.font.isEmpty);
846
892
  }
847
893
  /**
848
- * 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.
894
+ * Adds the provided [[ChangeInstance]] to the appropriate set of changes by class type (codeSpec, model, element, aspect, or relationship) maintained by this instance of ChangedInstanceIds.
849
895
  * 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'
850
896
  * from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
851
- * @param change ChangedECInstance which has the ECInstanceId, changeType (insert, update, delete) and ECClassId of the changed entity
897
+ * @param change Changed EC instance with the ID, operation, and EC class ID of the changed entity.
852
898
  */
853
899
  async addChange(change) {
854
900
  if (!this._ecClassIdsInitialized)
855
901
  await this.setupECClassIds();
856
- const ecClassId = change.ECClassId ?? change.$meta?.fallbackClassId;
902
+ const ecClassId = change.ECClassId;
857
903
  if (ecClassId === undefined)
858
- throw new Error(`ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change?.$meta?.tables}`);
859
- const changeType = change.$meta?.op;
904
+ core_bentley_1.ITwinError.throwError({
905
+ iTwinErrorId: {
906
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
907
+ key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
908
+ },
909
+ message: `ECClassId was not found for id: ${change.ECInstanceId}! Table is : ${change.$meta.tables}`,
910
+ });
911
+ const changeType = change.$meta.op;
860
912
  if (changeType === undefined)
861
- throw new Error(`ChangeType was undefined for id: ${change.ECInstanceId}.`);
913
+ core_bentley_1.ITwinError.throwError({
914
+ iTwinErrorId: {
915
+ scope: IModelTransformerError_1.IModelTransformerErrorScope,
916
+ key: IModelTransformerError_1.IModelTransformerError.ChangedInstanceMetadataMissing,
917
+ },
918
+ message: `ChangeType was undefined for id: ${change.ECInstanceId}.`,
919
+ });
862
920
  if (this._relationshipSubclassIdsToSkip?.has(ecClassId))
863
921
  return;
864
922
  if (this.isRelationship(ecClassId))
@@ -958,7 +1016,9 @@ class ChangedInstanceIds {
958
1016
  SELECT parentId FROM hierarchy where parentId is not null
959
1017
  `;
960
1018
  const parentModelIds = new Set();
961
- for await (const row of this._db.createQueryReader(ecQuery, params)) {
1019
+ for await (const row of this._db.createQueryReader(ecQuery, params, {
1020
+ usePrimaryConn: true,
1021
+ })) {
962
1022
  // Transformer handles update as insert when element does not exist in target.
963
1023
  // Which means that in scenario where child and parent model are filtered out from target,
964
1024
  // and child element is inserted trough custom change, its parent model will be marked as updated.
@@ -976,7 +1036,9 @@ class ChangedInstanceIds {
976
1036
  WHERE InVirtualSet(:elementIds, TargetECInstanceId)
977
1037
  OR InVirtualSet(:elementIds, SourceECInstanceId)`;
978
1038
  const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
979
- const queryReader = this._db.createQueryReader(ecQuery, queryBinder);
1039
+ const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
1040
+ usePrimaryConn: true,
1041
+ });
980
1042
  for await (const row of queryReader) {
981
1043
  this.handleChange(this.relationship, "Inserted", row.ECInstanceId);
982
1044
  }
@@ -988,7 +1050,9 @@ class ChangedInstanceIds {
988
1050
  ]) {
989
1051
  const ecQuery = `Select ECInstanceId from ${aspectClassName} where InVirtualSet(:elementIds, Element.Id)`;
990
1052
  const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
991
- const queryReader = this._db.createQueryReader(ecQuery, queryBinder);
1053
+ const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
1054
+ usePrimaryConn: true,
1055
+ });
992
1056
  for await (const row of queryReader) {
993
1057
  this.addCustomAspectChange("Inserted", row.toArray()[0]);
994
1058
  }
@@ -1057,23 +1121,7 @@ class ChangedInstanceIds {
1057
1121
  if (csFileProps === undefined)
1058
1122
  return undefined;
1059
1123
  const changedInstanceIds = new ChangedInstanceIds(opts.iModel);
1060
- for (const csFile of csFileProps) {
1061
- const csReader = core_backend_1.SqliteChangesetReader.openFile({
1062
- fileName: csFile.pathname,
1063
- db: opts.iModel,
1064
- disableSchemaCheck: true,
1065
- });
1066
- const csAdaptor = new core_backend_1.ChangesetECAdaptor(csReader);
1067
- const ecChangeUnifier = new core_backend_1.PartialECChangeUnifier(opts.iModel);
1068
- while (csAdaptor.step()) {
1069
- ecChangeUnifier.appendFrom(csAdaptor);
1070
- }
1071
- const changes = [...ecChangeUnifier.instances];
1072
- for (const change of changes) {
1073
- await changedInstanceIds.addChange(change);
1074
- }
1075
- csReader.close();
1076
- }
1124
+ await ChangesetScanner_1.ChangesetScanner.scan(opts.iModel, csFileProps, changedInstanceIds);
1077
1125
  return changedInstanceIds;
1078
1126
  }
1079
1127
  }