@itwin/core-backend 5.14.0-dev.1 → 5.14.0-dev.11

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 (120) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/lib/cjs/BriefcaseManager.d.ts +6 -0
  3. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  4. package/lib/cjs/BriefcaseManager.js +12 -6
  5. package/lib/cjs/BriefcaseManager.js.map +1 -1
  6. package/lib/cjs/ChangesetReader.d.ts.map +1 -1
  7. package/lib/cjs/ChangesetReader.js +1 -0
  8. package/lib/cjs/ChangesetReader.js.map +1 -1
  9. package/lib/cjs/ChangesetReaderTypes.d.ts +2 -1
  10. package/lib/cjs/ChangesetReaderTypes.d.ts.map +1 -1
  11. package/lib/cjs/ChangesetReaderTypes.js.map +1 -1
  12. package/lib/cjs/CloudSqlite.d.ts.map +1 -1
  13. package/lib/cjs/CloudSqlite.js +9 -2
  14. package/lib/cjs/CloudSqlite.js.map +1 -1
  15. package/lib/cjs/ECDb.d.ts.map +1 -1
  16. package/lib/cjs/ECDb.js +1 -0
  17. package/lib/cjs/ECDb.js.map +1 -1
  18. package/lib/cjs/Element.d.ts +1 -1
  19. package/lib/cjs/Element.js +1 -1
  20. package/lib/cjs/Element.js.map +1 -1
  21. package/lib/cjs/Entity.d.ts +3 -3
  22. package/lib/cjs/Entity.js +3 -3
  23. package/lib/cjs/Entity.js.map +1 -1
  24. package/lib/cjs/IModelDb.d.ts +83 -35
  25. package/lib/cjs/IModelDb.d.ts.map +1 -1
  26. package/lib/cjs/IModelDb.js +196 -89
  27. package/lib/cjs/IModelDb.js.map +1 -1
  28. package/lib/cjs/IpcHost.d.ts.map +1 -1
  29. package/lib/cjs/IpcHost.js +51 -17
  30. package/lib/cjs/IpcHost.js.map +1 -1
  31. package/lib/cjs/SchemaSync.d.ts +126 -18
  32. package/lib/cjs/SchemaSync.d.ts.map +1 -1
  33. package/lib/cjs/SchemaSync.js +303 -81
  34. package/lib/cjs/SchemaSync.js.map +1 -1
  35. package/lib/cjs/SqliteChangesetReader.d.ts +16 -8
  36. package/lib/cjs/SqliteChangesetReader.d.ts.map +1 -1
  37. package/lib/cjs/SqliteChangesetReader.js +11 -1
  38. package/lib/cjs/SqliteChangesetReader.js.map +1 -1
  39. package/lib/cjs/SynchronousChannel.d.ts +11 -1
  40. package/lib/cjs/SynchronousChannel.d.ts.map +1 -1
  41. package/lib/cjs/SynchronousChannel.js.map +1 -1
  42. package/lib/cjs/TxnManager.d.ts.map +1 -1
  43. package/lib/cjs/TxnManager.js +16 -13
  44. package/lib/cjs/TxnManager.js.map +1 -1
  45. package/lib/cjs/internal/IntegrityCheck.d.ts +57 -0
  46. package/lib/cjs/internal/IntegrityCheck.d.ts.map +1 -1
  47. package/lib/cjs/internal/IntegrityCheck.js.map +1 -1
  48. package/lib/cjs/internal/SchemaSyncReservations.d.ts.map +1 -1
  49. package/lib/cjs/internal/SchemaSyncReservations.js +13 -3
  50. package/lib/cjs/internal/SchemaSyncReservations.js.map +1 -1
  51. package/lib/esm/BriefcaseManager.d.ts +6 -0
  52. package/lib/esm/BriefcaseManager.d.ts.map +1 -1
  53. package/lib/esm/BriefcaseManager.js +12 -6
  54. package/lib/esm/BriefcaseManager.js.map +1 -1
  55. package/lib/esm/ChangesetReader.d.ts.map +1 -1
  56. package/lib/esm/ChangesetReader.js +1 -0
  57. package/lib/esm/ChangesetReader.js.map +1 -1
  58. package/lib/esm/ChangesetReaderTypes.d.ts +2 -1
  59. package/lib/esm/ChangesetReaderTypes.d.ts.map +1 -1
  60. package/lib/esm/ChangesetReaderTypes.js.map +1 -1
  61. package/lib/esm/CloudSqlite.d.ts.map +1 -1
  62. package/lib/esm/CloudSqlite.js +10 -3
  63. package/lib/esm/CloudSqlite.js.map +1 -1
  64. package/lib/esm/ECDb.d.ts.map +1 -1
  65. package/lib/esm/ECDb.js +1 -0
  66. package/lib/esm/ECDb.js.map +1 -1
  67. package/lib/esm/Element.d.ts +1 -1
  68. package/lib/esm/Element.js +1 -1
  69. package/lib/esm/Element.js.map +1 -1
  70. package/lib/esm/Entity.d.ts +3 -3
  71. package/lib/esm/Entity.js +3 -3
  72. package/lib/esm/Entity.js.map +1 -1
  73. package/lib/esm/IModelDb.d.ts +83 -35
  74. package/lib/esm/IModelDb.d.ts.map +1 -1
  75. package/lib/esm/IModelDb.js +196 -89
  76. package/lib/esm/IModelDb.js.map +1 -1
  77. package/lib/esm/IpcHost.d.ts.map +1 -1
  78. package/lib/esm/IpcHost.js +52 -18
  79. package/lib/esm/IpcHost.js.map +1 -1
  80. package/lib/esm/SchemaSync.d.ts +126 -18
  81. package/lib/esm/SchemaSync.d.ts.map +1 -1
  82. package/lib/esm/SchemaSync.js +304 -82
  83. package/lib/esm/SchemaSync.js.map +1 -1
  84. package/lib/esm/SqliteChangesetReader.d.ts +16 -8
  85. package/lib/esm/SqliteChangesetReader.d.ts.map +1 -1
  86. package/lib/esm/SqliteChangesetReader.js +11 -1
  87. package/lib/esm/SqliteChangesetReader.js.map +1 -1
  88. package/lib/esm/SynchronousChannel.d.ts +11 -1
  89. package/lib/esm/SynchronousChannel.d.ts.map +1 -1
  90. package/lib/esm/SynchronousChannel.js.map +1 -1
  91. package/lib/esm/TxnManager.d.ts.map +1 -1
  92. package/lib/esm/TxnManager.js +17 -14
  93. package/lib/esm/TxnManager.js.map +1 -1
  94. package/lib/esm/internal/IntegrityCheck.d.ts +57 -0
  95. package/lib/esm/internal/IntegrityCheck.d.ts.map +1 -1
  96. package/lib/esm/internal/IntegrityCheck.js.map +1 -1
  97. package/lib/esm/internal/SchemaSyncReservations.d.ts.map +1 -1
  98. package/lib/esm/internal/SchemaSyncReservations.js +13 -3
  99. package/lib/esm/internal/SchemaSyncReservations.js.map +1 -1
  100. package/lib/esm/test/annotations/FrameGeometry.test.js +1 -1
  101. package/lib/esm/test/annotations/FrameGeometry.test.js.map +1 -1
  102. package/lib/esm/test/hubaccess/BriefcaseManager.test.js +65 -1
  103. package/lib/esm/test/hubaccess/BriefcaseManager.test.js.map +1 -1
  104. package/lib/esm/test/hubaccess/Rebase.test.js +3 -1
  105. package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
  106. package/lib/esm/test/hubaccess/SemanticRebase.test.js +218 -28
  107. package/lib/esm/test/hubaccess/SemanticRebase.test.js.map +1 -1
  108. package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.d.ts +3 -0
  109. package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.d.ts.map +1 -0
  110. package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.js +15 -0
  111. package/lib/esm/test/hubaccess/SemanticRebaseTestUtils.js.map +1 -0
  112. package/lib/esm/test/standalone/ChangesetReader.test.js.map +1 -1
  113. package/lib/esm/test/standalone/SQLiteDb.test.d.ts.map +1 -1
  114. package/lib/esm/test/standalone/SQLiteDb.test.js +116 -0
  115. package/lib/esm/test/standalone/SQLiteDb.test.js.map +1 -1
  116. package/lib/esm/test/standalone/SchemaSyncDb.test.js +5 -25
  117. package/lib/esm/test/standalone/SchemaSyncDb.test.js.map +1 -1
  118. package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js +1 -0
  119. package/lib/esm/test/standalone/SchemaSyncElementReservation.test.js.map +1 -1
  120. package/package.json +14 -14
@@ -320,7 +320,9 @@ class IModelDb extends core_common_1.IModel {
320
320
  db[Symbols_1._nativeDb].abandonChanges();
321
321
  db.close();
322
322
  }
323
- catch { }
323
+ catch {
324
+ return;
325
+ }
324
326
  });
325
327
  });
326
328
  }
@@ -386,12 +388,20 @@ class IModelDb extends core_common_1.IModel {
386
388
  this._codeService = undefined;
387
389
  this[Symbols_1._nativeDb].closeFile();
388
390
  }
391
+ /**
392
+ * Save any unsaved schema changes in this iModel.
393
+ * @internal
394
+ */
389
395
  saveSchemaChanges(args) {
390
396
  if (!this[Symbols_1._nativeDb].hasUnsavedChanges())
391
397
  return;
392
398
  const saveArgs = typeof args === "string" ? { description: args } : args;
393
399
  saveArgs === undefined ? this[Symbols_1._nativeDb].saveChanges() : this[Symbols_1._nativeDb].saveChanges(JSON.stringify(saveArgs));
394
400
  }
401
+ /**
402
+ * Abandon any unsaved schema changes in this iModel.
403
+ * @internal
404
+ */
395
405
  abandonSchemaChanges() {
396
406
  if (!this[Symbols_1._nativeDb].hasUnsavedChanges())
397
407
  return;
@@ -434,24 +444,30 @@ class IModelDb extends core_common_1.IModel {
434
444
  }
435
445
  /**
436
446
  * Performs integrity checks on this iModel.
437
- * Types of integrity checks that can be performed are:
438
447
  *
439
- * Default Check:
440
- * - Quick Check: Runs all integrity checks below and returns whether each check passed or failed, without detailed results.
448
+ * The quick check runs the ten checks below other than Missing Child Rows. It reports pass/fail for each check
449
+ * based on the first problem found, without returning problem details. The specific checks return all problem rows.
450
+ * Selecting both quickCheck and specific checks repeats the selected work in both modes.
451
+ *
452
+ * The checks are:
453
+ * - Data Columns Check: Checks for missing nonvirtual physical columns in mapped data tables. It does not compare column types.
454
+ * - EC Profile Check: Checks expected EC profile tables, indexes, and iModel triggers for missing or mismatching SQL definitions. It does not check be_* tables.
455
+ * - Navigation Class Ids Check: Checks that each non-null persisted RelECClassId is the declared relationship class or one of its derived classes.
456
+ * - Navigation Ids Check: Checks that a navigation property's non-null .Id (the referenced instance's ECInstanceId) resolves to a row in the first relationship constraint class in the navigation direction, including derived classes.
457
+ * - Linktable Foreign Key Class Ids Check: Checks that SourceECClassId and TargetECClassId have matching meta.ECClassDef entries. This checks class existence only, not endpoint-class validity.
458
+ * - Linktable Foreign Key Ids Check: Checks SourceECInstanceId and TargetECInstanceId for null or an unresolved row in the first endpoint constraint class, including derived classes.
459
+ * - Class Ids Check: Checks persisted ECClassId values for a corresponding class definition. This checks class existence, not whether the class belongs in that table.
460
+ * - Data Schema Check: Checks for missing mapped data tables and indexes. Results report object names and types, not DDL differences.
461
+ * - Schema Load Check: Checks whether the schema manager can load each schema recorded in the database. A problem reports the schema name without a reason.
462
+ * - Missing Child Rows Check: Checks whether each existing bis_Element row has all required mapped child-table rows.
463
+ * - Diverged Property Maps Check: Checks whether an inherited property maps to different columns for a base and derived class under the same physical table root.
441
464
  *
442
- * Specific Checks:
443
- * - Data Columns Check: Checks if all the required columns exist in data tables. Issues are returned as a list of those tables/columns.
444
- * - EC Profile Check: Checks if the profile table, indexes, and triggers are present. Does not check be_* tables. Issues are returned as a list of tables/indexes/triggers which were not found or have different DDL.
445
- * - Navigation Class Ids Check: Checks if RelClassId of a Navigation property is a valid ECClassId. It does not check the value to match the relationship class.
446
- * - Navigation Ids Check: Checks if Id of a Navigation property matches a valid row primary class.
447
- * - Linktable Foreign Key Class Ids Check: Checks if SourceECClassId or TargetECClassId of a link table matches a valid ECClassId.
448
- * - Linktable Foreign Key Ids Check: Checks if SourceECInstanceId or TargetECInstanceId of a link table matches a valid row in primary class.
449
- * - Class Ids Check: Checks persisted ECClassId in all data tables and makes sure they are valid.
450
- * - Data Schema Check: Checks if all the required data tables and indexes exist for mapped classes. Issues are returned as a list of tables/columns which were not found or have different DDL.
451
- * - Schema Load Check: Checks if all schemas can be loaded into memory.
452
- * - Missing Child Rows Check: Checks if all child rows have a corresponding parent row.
465
+ * If no options are provided, or if quickCheck is false and no specific check is enabled, a quick check is performed by default.
466
+ * The checks are read-only and use the current primary connection. They have no callbacks, and the native operation
467
+ * blocks the backend while each check runs.
453
468
  *
454
- * @param options Options specifying which integrity checks to perform. If no options are provided or all options are false, a quick check will be performed by default.
469
+ * @see [ECSQL integrity checks]($docs/learning/ECSqlReference/Pragmas.md#pragma-integrity_check-experimental)
470
+ * @param options Options specifying which integrity checks to perform.
455
471
  * @returns An array of integrity check results.
456
472
  * @throws [[IModelError]] if the iModel is not open.
457
473
  * @beta
@@ -938,7 +954,7 @@ class IModelDb extends core_common_1.IModel {
938
954
  async dropSchemas(schemaNames) {
939
955
  if (schemaNames.length === 0)
940
956
  return;
941
- if (this[Symbols_1._nativeDb].schemaSyncEnabled())
957
+ if (SchemaSync_1.SchemaSync.isEnabled(this))
942
958
  throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot drop schemas when schema sync is enabled");
943
959
  if (this[Symbols_1._nativeDb].hasUnsavedChanges())
944
960
  throw new core_common_1.IModelError(core_bentley_1.ChangeSetStatus.HasUncommittedChanges, "Cannot drop schemas with unsaved changes");
@@ -1024,26 +1040,34 @@ class IModelDb extends core_common_1.IModel {
1024
1040
  this.cleanupSnapshot(context.resources);
1025
1041
  }
1026
1042
  }
1027
- /** Shared implementation for importing schemas from file or string. */
1028
- async importSchemasInternal(schemas, options, nativeImportOp) {
1029
- // BriefcaseDb-specific validation checks
1030
- if (this.isBriefcaseDb()) {
1031
- if (this.txns.rebaser.isRebasing) {
1032
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while rebasing");
1033
- }
1034
- if (this.txns.isIndirectChanges) {
1035
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while in an indirect change scope");
1036
- }
1037
- // Additional checks when semantic rebase is enabled
1038
- if (IModelHost_1.IModelHost.useSemanticRebase) {
1039
- if (this[Symbols_1._nativeDb].hasUnsavedChanges()) {
1040
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas with unsaved changes when useSemanticRebase flag is on");
1041
- }
1042
- if (this[Symbols_1._nativeDb].schemaSyncEnabled()) {
1043
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas when schema sync is enabled and also useSemanticRebase flag is on");
1044
- }
1043
+ /** Refuse a schema import this briefcase is in no state to run. Shared by [[importSchemas]] and
1044
+ * [[BriefcaseDb.upgradeSchemas]], which reach the native importer by different routes.
1045
+ * @internal
1046
+ */
1047
+ assertCanImportSchemas() {
1048
+ if (!this.isBriefcaseDb())
1049
+ return;
1050
+ if (this.txns.rebaser.isRebasing) {
1051
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while rebasing");
1052
+ }
1053
+ if (this.txns.isIndirectChanges) {
1054
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while in an indirect change scope");
1055
+ }
1056
+ // Additional checks when semantic rebase is enabled
1057
+ if (IModelHost_1.IModelHost.useSemanticRebase) {
1058
+ if (this[Symbols_1._nativeDb].hasUnsavedChanges()) {
1059
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas with unsaved changes when useSemanticRebase flag is on");
1060
+ }
1061
+ if (SchemaSync_1.SchemaSync.isEnabled(this)) {
1062
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas when schema sync is enabled and also useSemanticRebase flag is on");
1045
1063
  }
1046
1064
  }
1065
+ }
1066
+ /** Shared implementation for importing schemas from file or string.
1067
+ * @internal
1068
+ */
1069
+ async importSchemasInternal(schemas, options, nativeImportOp) {
1070
+ this.assertCanImportSchemas();
1047
1071
  if (options?.channelUpgrade) {
1048
1072
  const channelUpgrade = options.channelUpgrade;
1049
1073
  try {
@@ -1061,30 +1085,52 @@ class IModelDb extends core_common_1.IModel {
1061
1085
  if (options?.schemaImportCallbacks?.preSchemaImportCallback)
1062
1086
  preSchemaImportCallbackResult = await this.preSchemaImportCallback(options.schemaImportCallbacks, { iModel: this, data: options.data, schemaData: schemas });
1063
1087
  const maybeCustomNativeContext = options?.ecSchemaXmlContext?.nativeContext;
1064
- if (this[Symbols_1._nativeDb].schemaSyncEnabled()) {
1065
- await SchemaSync_1.SchemaSync.withLockedAccess(this, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schema sync" }, async (syncAccess) => {
1066
- const schemaSyncDbUri = syncAccess.getUri();
1067
- this.saveSchemaChanges();
1068
- try {
1069
- nativeImportOp(schemas, { schemaLockHeld: false, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
1070
- }
1071
- catch (outerErr) {
1072
- if (core_bentley_1.DbResult.BE_SQLITE_ERROR_DataTransformRequired === outerErr.errorNumber) {
1073
- this.abandonSchemaChanges();
1074
- if (this[Symbols_1._nativeDb].getITwinId() !== core_bentley_1.Guid.empty)
1075
- await this.acquireSchemaLock();
1076
- try {
1077
- nativeImportOp(schemas, { schemaLockHeld: true, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
1078
- }
1079
- catch (innerErr) {
1080
- throw new core_common_1.IModelError(innerErr.errorNumber, innerErr.message);
1081
- }
1088
+ if (SchemaSync_1.SchemaSync.isEnabled(this)) {
1089
+ // The shared lock lets concurrent updates through while blocking anyone taking the exclusive lock for an upgrade.
1090
+ if (this[Symbols_1._nativeDb].getITwinId() !== core_bentley_1.Guid.empty)
1091
+ await this.locks.acquireLocks({ shared: core_common_1.IModel.repositoryModelId });
1092
+ // The native import adopts rows from the attached SchemaSyncDb. Attach and detach commit
1093
+ // implicitly, so abandoning changes cannot undo the adoption. A later failure must roll the
1094
+ // briefcase back before those ec_ rows can be pushed.
1095
+ let txnBeforeAdopt;
1096
+ // Set once the caller's own work is committed. Getting a token or the container write lock happens
1097
+ // before that, and abandonSchemaChanges discards every unsaved change rather than only this import's,
1098
+ // so abandoning on a failure from that window would throw away work the import never touched.
1099
+ let callerChangesSaved = false;
1100
+ try {
1101
+ await SchemaSync_1.SchemaSync.withLockedAccess(this, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schema sync" }, async (syncAccess) => {
1102
+ const schemaSyncDbUri = syncAccess.getUri();
1103
+ this.saveSchemaChanges();
1104
+ callerChangesSaved = true;
1105
+ // After saveSchemaChanges, so rolling back cannot reach the caller's own work.
1106
+ if (this.isBriefcaseDb())
1107
+ txnBeforeAdopt = this.txns.getCurrentTxnId();
1108
+ try {
1109
+ nativeImportOp(schemas, { schemaLockHeld: false, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
1082
1110
  }
1083
- else {
1084
- throw new core_common_1.IModelError(outerErr.errorNumber, outerErr.message);
1111
+ catch (err) {
1112
+ if (SchemaSync_1.SchemaSync.requiresUpgrade(err))
1113
+ throw new core_common_1.IModelError(err.errorNumber, `${err.message} - this schema change needs the upgrade path. Use BriefcaseDb.upgradeSchemas, which takes the exclusive schema lock and pushes the result.`);
1114
+ throw new core_common_1.IModelError(err.errorNumber, err.message);
1085
1115
  }
1116
+ });
1117
+ }
1118
+ catch (err) {
1119
+ if (callerChangesSaved)
1120
+ this.abandonSchemaChanges();
1121
+ if (this.isBriefcaseDb() && undefined !== txnBeforeAdopt && this.txns.getCurrentTxnId() !== txnBeforeAdopt) {
1122
+ // Saving the adopt's txn started a new undo session, because schema changes are kept out of the
1123
+ // user's undo stack, so this reverses across that boundary. The changes themselves reverse like
1124
+ // any other - a rebase does it to every local txn on every pull. It does not reverse DDL: a table
1125
+ // or column the adopt created stays behind with no ec_ row describing it, which is the state a
1126
+ // briefcase already tolerates, and the next successful import finds the table up to date and
1127
+ // carries on. Cancel rather than reverse, so the rows cannot come back through undo either.
1128
+ const status = this[Symbols_1._nativeDb].cancelTo(txnBeforeAdopt, true);
1129
+ if (core_bentley_1.IModelStatus.Success !== status)
1130
+ core_bentley_1.Logger.logError(loggerCategory, `Failed to roll back the adopted schema rows after a failed import: ${core_bentley_1.IModelStatus[status] ?? status}`);
1086
1131
  }
1087
- });
1132
+ throw err;
1133
+ }
1088
1134
  }
1089
1135
  else {
1090
1136
  const nativeImportOptions = {
@@ -2850,7 +2896,9 @@ function processSchemaWriteStatus(status) {
2850
2896
  try {
2851
2897
  props.push(imodel.elements.getElementProps(id));
2852
2898
  }
2853
- catch { }
2899
+ catch {
2900
+ return;
2901
+ }
2854
2902
  });
2855
2903
  return props;
2856
2904
  }
@@ -2873,7 +2921,9 @@ function processSchemaWriteStatus(status) {
2873
2921
  break;
2874
2922
  }
2875
2923
  }
2876
- catch { }
2924
+ catch {
2925
+ continue;
2926
+ }
2877
2927
  }
2878
2928
  return finished;
2879
2929
  }
@@ -3083,9 +3133,6 @@ class BriefcaseDb extends IModelDb {
3083
3133
  get isBriefcase() { return true; }
3084
3134
  /* the BriefcaseId of the briefcase opened with this BriefcaseDb */
3085
3135
  briefcaseId;
3086
- _skipSyncSchemasOnPullAndPush;
3087
- /** @internal */
3088
- get skipSyncSchemasOnPullAndPush() { return this._skipSyncSchemasOnPullAndPush ?? false; }
3089
3136
  /**
3090
3137
  * Event raised just before a BriefcaseDb is opened. Supplies the arguments that will be used to open the BriefcaseDb.
3091
3138
  * Throw an exception to stop the open.
@@ -3168,6 +3215,8 @@ class BriefcaseDb extends IModelDb {
3168
3215
  }
3169
3216
  constructor(args) {
3170
3217
  super({ ...args, changeset: args.nativeDb.getCurrentChangeset() });
3218
+ // Native invokes this private callback dynamically through the object registered by IModelDb.
3219
+ void this.onChangesetConflict;
3171
3220
  this._openMode = args.openMode;
3172
3221
  this.briefcaseId = args.briefcaseId;
3173
3222
  this.makeLockControl();
@@ -3180,26 +3229,33 @@ class BriefcaseDb extends IModelDb {
3180
3229
  wasChanges = nativeDb.hasPendingTxns();
3181
3230
  nativeDb.closeFile();
3182
3231
  };
3183
- const isSchemaSyncEnabled = await withBriefcaseDb(briefcase, async (db) => {
3184
- await SchemaSync_1.SchemaSync.pull(db);
3185
- return db[Symbols_1._nativeDb].schemaSyncEnabled();
3186
- });
3187
- if (isSchemaSyncEnabled) {
3232
+ const push = async () => {
3233
+ if (wasChanges)
3234
+ await withBriefcaseDb(briefcase, async (db) => db.pushChanges({ ...briefcase, description, retainLocks: true }));
3235
+ };
3236
+ // Asked of the closed file, so an iModel without schema sync never gets opened for this.
3237
+ if (SchemaSync_1.SchemaSync.isEnabled(briefcase)) {
3238
+ await withBriefcaseDb(briefcase, async (db) => SchemaSync_1.SchemaSync.updateDbSchema(db));
3188
3239
  await SchemaSync_1.SchemaSync.withLockedAccess(briefcase, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schema sync" }, async (syncAccess) => {
3189
3240
  const schemaSyncDbUri = syncAccess.getUri();
3190
3241
  executeUpgrade();
3242
+ // Overwriting advances the SchemaSync data version, so a no-op upgrade must not call it.
3243
+ if (!wasChanges)
3244
+ return;
3191
3245
  await withBriefcaseDb(briefcase, async (db) => {
3192
- db[Symbols_1._nativeDb].schemaSyncPush(schemaSyncDbUri);
3246
+ db[Symbols_1._nativeDb].schemaSyncOverwrite(schemaSyncDbUri);
3193
3247
  db[Symbols_1._nativeDb].saveChanges();
3194
3248
  });
3195
- syncAccess.synchronizeWithCloud();
3249
+ // Publish the sync db before the changeset while retaining both locks.
3250
+ syncAccess.closeDb();
3251
+ await syncAccess.container.uploadChanges();
3252
+ await push();
3196
3253
  });
3197
3254
  }
3198
3255
  else {
3199
3256
  executeUpgrade();
3257
+ await push();
3200
3258
  }
3201
- if (wasChanges)
3202
- await withBriefcaseDb(briefcase, async (db) => db.pushChanges({ ...briefcase, description, retainLocks: true }));
3203
3259
  }
3204
3260
  /** Upgrades the schemas in the iModel based on the current version of the software. Follows a sequence of operations -
3205
3261
  * * Acquires a schema lock to prevent other users from making any other changes while upgrade is happening
@@ -3219,6 +3275,22 @@ class BriefcaseDb extends IModelDb {
3219
3275
  // - push changes
3220
3276
  // - release schema lock
3221
3277
  // good thing computers are fast. Fortunately upgrading should be rare (and the push time will dominate anyway.) Don't try to optimize any of this away.
3278
+ // schemaSyncOverwrite can discard unpushed imports, so hold the exclusive lock across both upgrades.
3279
+ if (SchemaSync_1.SchemaSync.isEnabled(briefcase)) {
3280
+ try {
3281
+ await withBriefcaseDb(briefcase, async (db) => {
3282
+ if (!db.locks.isServerBased)
3283
+ throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot upgrade schemas with SchemaSync without server-based locking");
3284
+ await db.acquireSchemaLock();
3285
+ });
3286
+ await this.doUpgrade(briefcase, { profile: core_common_1.ProfileOptions.Upgrade, schemaLockHeld: true }, "Upgraded profile");
3287
+ await this.doUpgrade(briefcase, { domain: core_common_1.DomainOptions.Upgrade, schemaLockHeld: true }, "Upgraded domain schemas");
3288
+ }
3289
+ finally {
3290
+ await withBriefcaseDb(briefcase, async (db) => db.locks[Symbols_1._releaseAllLocks]());
3291
+ }
3292
+ return;
3293
+ }
3222
3294
  try {
3223
3295
  await this.doUpgrade(briefcase, { profile: core_common_1.ProfileOptions.Upgrade }, "Upgraded profile");
3224
3296
  }
@@ -3519,8 +3591,7 @@ class BriefcaseDb extends IModelDb {
3519
3591
  async pullChanges(arg) {
3520
3592
  await this.executeWritable(async () => {
3521
3593
  await BriefcaseManager_1.BriefcaseManager.pullAndApplyChangesets(this, arg ?? {});
3522
- if (!this.skipSyncSchemasOnPullAndPush)
3523
- await SchemaSync_1.SchemaSync.pull(this);
3594
+ SchemaSync_1.SchemaSync.updateDbSchema(this);
3524
3595
  this.initializeIModelDb("pullMerge");
3525
3596
  });
3526
3597
  // If this pull enabled or disabled SchemaSync for this briefcase, its reservations must now be re-initialized
@@ -3605,24 +3676,10 @@ class BriefcaseDb extends IModelDb {
3605
3676
  if (nativeDb.hasPendingTxns()) {
3606
3677
  throw new core_common_1.IModelError(core_bentley_1.ChangeSetStatus.HasLocalChanges, "Cannot revert with pending txns");
3607
3678
  }
3608
- const skipSchemaSyncPull = async (func) => {
3609
- if (nativeDb.schemaSyncEnabled()) {
3610
- this._skipSyncSchemasOnPullAndPush = true;
3611
- try {
3612
- return await func();
3613
- }
3614
- finally {
3615
- this._skipSyncSchemasOnPullAndPush = undefined;
3616
- }
3617
- }
3618
- else {
3619
- return func();
3620
- }
3621
- };
3622
3679
  this.clearCaches();
3623
- await skipSchemaSyncPull(async () => this.pullChanges({ ...arg, toIndex: undefined }));
3680
+ await this.pullChanges({ ...arg, toIndex: undefined });
3624
3681
  await this.acquireSchemaLock();
3625
- if (nativeDb.schemaSyncEnabled()) {
3682
+ if (SchemaSync_1.SchemaSync.isEnabled(this)) {
3626
3683
  arg.skipSchemaChanges = true;
3627
3684
  }
3628
3685
  // The native side enables file-based txns during revert. Restore the original setting afterward.
@@ -3643,7 +3700,7 @@ class BriefcaseDb extends IModelDb {
3643
3700
  pushRetryDelay: arg.pushRetryDelay,
3644
3701
  retainLocks: arg.retainLocks,
3645
3702
  };
3646
- await skipSchemaSyncPull(async () => this.pushChanges(pushArgs));
3703
+ await this.pushChanges(pushArgs);
3647
3704
  this.clearCaches();
3648
3705
  }
3649
3706
  catch (err) {
@@ -3720,6 +3777,56 @@ class BriefcaseDb extends IModelDb {
3720
3777
  this.txns._onChangesPushed(this.changeset);
3721
3778
  BriefcaseManager_1.BriefcaseManager.deleteRebaseFolders(this);
3722
3779
  }
3780
+ /** Import schemas while allowing changes that move or delete data.
3781
+ *
3782
+ * Takes the exclusive schema lock, requires the briefcase to be at the timeline tip, imports, updates the sync db, and pushes the result.
3783
+ * @note The briefcase must have no local changes.
3784
+ * @see [[BriefcaseDb.upgradeSchemas]] (static) for upgrading the software's profile and domain schemas.
3785
+ * @alpha
3786
+ */
3787
+ async upgradeSchemas(schemaFileNames, arg) {
3788
+ return this.upgradeSchemasInternal(schemaFileNames, arg, (schemas, importOptions) => this[Symbols_1._nativeDb].importSchemas(schemas, importOptions));
3789
+ }
3790
+ /** The [[importSchemaStrings]] counterpart of [[upgradeSchemas]].
3791
+ * @alpha
3792
+ */
3793
+ async upgradeSchemaStrings(serializedXmlSchemas, arg) {
3794
+ return this.upgradeSchemasInternal(serializedXmlSchemas, arg, (schemas, importOptions) => this[Symbols_1._nativeDb].importXmlSchemas(schemas, importOptions));
3795
+ }
3796
+ async upgradeSchemasInternal(schemas, arg, nativeImportOp) {
3797
+ if (schemas.length === 0)
3798
+ return;
3799
+ this.assertCanImportSchemas();
3800
+ if (this[Symbols_1._nativeDb].hasUnsavedChanges() || this.txns.hasLocalChanges)
3801
+ throw new core_common_1.IModelError(core_bentley_1.ChangeSetStatus.HasLocalChanges, "Cannot upgrade schemas while there are local changes");
3802
+ if (SchemaSync_1.SchemaSync.isEnabled(this) && !this.locks.isServerBased)
3803
+ throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "Cannot upgrade schemas with SchemaSync without server-based locking");
3804
+ await this.acquireSchemaLock();
3805
+ await this.pullChanges({ accessToken: arg.accessToken });
3806
+ if (!SchemaSync_1.SchemaSync.isEnabled(this)) {
3807
+ await this.importSchemasInternal(schemas, { ecSchemaXmlContext: arg.ecSchemaXmlContext }, nativeImportOp);
3808
+ await this.pushChanges(arg);
3809
+ return;
3810
+ }
3811
+ await SchemaSync_1.SchemaSync.withLockedAccess(this, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schema upgrade" }, async (syncAccess) => {
3812
+ this.saveSchemaChanges();
3813
+ try {
3814
+ nativeImportOp(schemas, {
3815
+ schemaLockHeld: true,
3816
+ ecSchemaXmlContext: arg.ecSchemaXmlContext?.nativeContext,
3817
+ schemaSyncDbUri: syncAccess.getUri(),
3818
+ });
3819
+ }
3820
+ catch (err) {
3821
+ this.abandonSchemaChanges();
3822
+ throw new core_common_1.IModelError(err.errorNumber, err.message);
3823
+ }
3824
+ this.clearCaches();
3825
+ syncAccess.closeDb();
3826
+ await syncAccess.container.uploadChanges();
3827
+ await this.pushChanges(arg);
3828
+ });
3829
+ }
3723
3830
  close(options) {
3724
3831
  if (this.isBriefcase && this.isOpen && !this.isReadonly && this.txns.rebaser.inProgress()) {
3725
3832
  this[Symbols_1._nativeDb].abandonChanges();