@itwin/core-backend 5.12.0-dev.8 → 5.12.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 (96) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/lib/cjs/ClassRegistry.d.ts.map +1 -1
  3. package/lib/cjs/ClassRegistry.js +1 -2
  4. package/lib/cjs/ClassRegistry.js.map +1 -1
  5. package/lib/cjs/ECDb.d.ts.map +1 -1
  6. package/lib/cjs/ECDb.js +8 -3
  7. package/lib/cjs/ECDb.js.map +1 -1
  8. package/lib/cjs/ECSqlRowExecutor.d.ts +23 -10
  9. package/lib/cjs/ECSqlRowExecutor.d.ts.map +1 -1
  10. package/lib/cjs/ECSqlRowExecutor.js +77 -15
  11. package/lib/cjs/ECSqlRowExecutor.js.map +1 -1
  12. package/lib/cjs/IModelDb.d.ts +20 -8
  13. package/lib/cjs/IModelDb.d.ts.map +1 -1
  14. package/lib/cjs/IModelDb.js +83 -54
  15. package/lib/cjs/IModelDb.js.map +1 -1
  16. package/lib/cjs/IpcHost.d.ts +26 -0
  17. package/lib/cjs/IpcHost.d.ts.map +1 -1
  18. package/lib/cjs/IpcHost.js +68 -61
  19. package/lib/cjs/IpcHost.js.map +1 -1
  20. package/lib/cjs/Relationship.d.ts.map +1 -1
  21. package/lib/cjs/Relationship.js +13 -7
  22. package/lib/cjs/Relationship.js.map +1 -1
  23. package/lib/cjs/SQLiteDb.d.ts +43 -0
  24. package/lib/cjs/SQLiteDb.d.ts.map +1 -1
  25. package/lib/cjs/SQLiteDb.js +51 -0
  26. package/lib/cjs/SQLiteDb.js.map +1 -1
  27. package/lib/cjs/ViewStore.js +1 -1
  28. package/lib/cjs/ViewStore.js.map +1 -1
  29. package/lib/cjs/internal/ECSqlInstanceReshaper.d.ts +23 -0
  30. package/lib/cjs/internal/ECSqlInstanceReshaper.d.ts.map +1 -0
  31. package/lib/cjs/internal/ECSqlInstanceReshaper.js +125 -0
  32. package/lib/cjs/internal/ECSqlInstanceReshaper.js.map +1 -0
  33. package/lib/cjs/internal/annotations/fields.d.ts.map +1 -1
  34. package/lib/cjs/internal/annotations/fields.js +9 -4
  35. package/lib/cjs/internal/annotations/fields.js.map +1 -1
  36. package/lib/esm/ClassRegistry.d.ts.map +1 -1
  37. package/lib/esm/ClassRegistry.js +1 -2
  38. package/lib/esm/ClassRegistry.js.map +1 -1
  39. package/lib/esm/ECDb.d.ts.map +1 -1
  40. package/lib/esm/ECDb.js +9 -4
  41. package/lib/esm/ECDb.js.map +1 -1
  42. package/lib/esm/ECSqlRowExecutor.d.ts +23 -10
  43. package/lib/esm/ECSqlRowExecutor.d.ts.map +1 -1
  44. package/lib/esm/ECSqlRowExecutor.js +77 -16
  45. package/lib/esm/ECSqlRowExecutor.js.map +1 -1
  46. package/lib/esm/IModelDb.d.ts +20 -8
  47. package/lib/esm/IModelDb.d.ts.map +1 -1
  48. package/lib/esm/IModelDb.js +85 -56
  49. package/lib/esm/IModelDb.js.map +1 -1
  50. package/lib/esm/IpcHost.d.ts +26 -0
  51. package/lib/esm/IpcHost.d.ts.map +1 -1
  52. package/lib/esm/IpcHost.js +70 -63
  53. package/lib/esm/IpcHost.js.map +1 -1
  54. package/lib/esm/Relationship.d.ts.map +1 -1
  55. package/lib/esm/Relationship.js +13 -7
  56. package/lib/esm/Relationship.js.map +1 -1
  57. package/lib/esm/SQLiteDb.d.ts +43 -0
  58. package/lib/esm/SQLiteDb.d.ts.map +1 -1
  59. package/lib/esm/SQLiteDb.js +51 -0
  60. package/lib/esm/SQLiteDb.js.map +1 -1
  61. package/lib/esm/ViewStore.js +1 -1
  62. package/lib/esm/ViewStore.js.map +1 -1
  63. package/lib/esm/internal/ECSqlInstanceReshaper.d.ts +23 -0
  64. package/lib/esm/internal/ECSqlInstanceReshaper.d.ts.map +1 -0
  65. package/lib/esm/internal/ECSqlInstanceReshaper.js +120 -0
  66. package/lib/esm/internal/ECSqlInstanceReshaper.js.map +1 -0
  67. package/lib/esm/internal/annotations/fields.d.ts.map +1 -1
  68. package/lib/esm/internal/annotations/fields.js +9 -4
  69. package/lib/esm/internal/annotations/fields.js.map +1 -1
  70. package/lib/esm/test/IpcHost.test.js +232 -1
  71. package/lib/esm/test/IpcHost.test.js.map +1 -1
  72. package/lib/esm/test/ecdb/ECSqlSyncReader.test.js +139 -1
  73. package/lib/esm/test/ecdb/ECSqlSyncReader.test.js.map +1 -1
  74. package/lib/esm/test/imodel/IModel.test.js +20 -0
  75. package/lib/esm/test/imodel/IModel.test.js.map +1 -1
  76. package/lib/esm/test/internal/ECSqlInstanceReshaper.test.d.ts +2 -0
  77. package/lib/esm/test/internal/ECSqlInstanceReshaper.test.d.ts.map +1 -0
  78. package/lib/esm/test/internal/ECSqlInstanceReshaper.test.js +289 -0
  79. package/lib/esm/test/internal/ECSqlInstanceReshaper.test.js.map +1 -0
  80. package/lib/esm/test/schema/ClassRegistry.test.js +2 -2
  81. package/lib/esm/test/schema/ClassRegistry.test.js.map +1 -1
  82. package/lib/esm/test/schema/SchemaViewFragmentLoading.test.d.ts +2 -0
  83. package/lib/esm/test/schema/SchemaViewFragmentLoading.test.d.ts.map +1 -0
  84. package/lib/esm/test/schema/SchemaViewFragmentLoading.test.js +546 -0
  85. package/lib/esm/test/schema/SchemaViewFragmentLoading.test.js.map +1 -0
  86. package/lib/esm/test/schema/SchemaViewLifecycle.test.js +2 -2
  87. package/lib/esm/test/schema/SchemaViewLifecycle.test.js.map +1 -1
  88. package/lib/esm/test/standalone/MergeConflict.test.js +1 -1
  89. package/lib/esm/test/standalone/MergeConflict.test.js.map +1 -1
  90. package/lib/esm/test/standalone/SQLiteDb.test.d.ts +1 -1
  91. package/lib/esm/test/standalone/SQLiteDb.test.d.ts.map +1 -1
  92. package/lib/esm/test/standalone/SQLiteDb.test.js +93 -1
  93. package/lib/esm/test/standalone/SQLiteDb.test.js.map +1 -1
  94. package/lib/esm/test/standalone/ViewStoreDb.test.js +30 -0
  95. package/lib/esm/test/standalone/ViewStoreDb.test.js.map +1 -1
  96. package/package.json +14 -15
@@ -19,6 +19,7 @@ const BriefcaseManager_1 = require("./BriefcaseManager");
19
19
  const ChannelControl_1 = require("./ChannelControl");
20
20
  const ChannelAdmin_1 = require("./internal/ChannelAdmin");
21
21
  const CheckpointManager_1 = require("./CheckpointManager");
22
+ const ECSqlInstanceReshaper_1 = require("./internal/ECSqlInstanceReshaper");
22
23
  const ClassRegistry_1 = require("./ClassRegistry");
23
24
  const CloudSqlite_1 = require("./CloudSqlite");
24
25
  const CodeService_1 = require("./CodeService");
@@ -165,7 +166,9 @@ class IModelDb extends core_common_1.IModel {
165
166
  _jsClassMap;
166
167
  _schemaMap;
167
168
  _schemaContext;
168
- _schemasPromise;
169
+ // Created lazily on the first getSchemaView call. Owns the SchemaView's lifetime and does all its
170
+ // data access through the SchemaViewDataProvider implemented below.
171
+ _schemaViewManager;
169
172
  /** @deprecated in 5.0.0 - might be removed in next major version. Use [[fonts]]. */
170
173
  _fontMap; // eslint-disable-line @typescript-eslint/no-deprecated
171
174
  _fonts = (0, IModelDbFontsImpl_1.createIModelDbFonts)(this);
@@ -613,16 +616,22 @@ class IModelDb extends core_common_1.IModel {
613
616
  * @param config Allow to specify certain flags which control how query is executed.
614
617
  * @returns the value returned by `callback`.
615
618
  * @throws IModelError if db is not open.
616
- * Should be used when we want true step by step behaviour from the reader without any intermediate caching involved.
619
+ * Use this method for true step-by-step row consumption without intermediate result or page caching.
620
+ * The prepared ECSQL statement may be reused from the statement cache between completed calls.
617
621
  * @beta
618
622
  * */
619
623
  withQueryReader(ecsql, callback, params, config) {
620
624
  if (!this[Symbols_1._nativeDb].isOpen())
621
625
  throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR_NOTOPEN, "db not open");
622
- const executor = new ECSqlRowExecutor_1.ECSqlRowExecutor(this);
623
- const reader = new ECSqlSyncReader_1.ECSqlSyncReader(executor, ecsql, params, config);
624
- const release = () => executor[Symbol.dispose]();
626
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
627
+ const stmt = this._statementCache.findAndRemove(ecsql) ?? new ECSqlStatement_1.ECSqlStatement();
628
+ const executor = new ECSqlRowExecutor_1.ECSqlRowExecutor(this, stmt, loggerCategory);
629
+ const release = () => {
630
+ executor[Symbol.dispose]();
631
+ (0, ECSqlRowExecutor_1.releaseECSqlStatement)(stmt, this._statementCache, loggerCategory, executor.canCacheStatement);
632
+ };
625
633
  try {
634
+ const reader = new ECSqlSyncReader_1.ECSqlSyncReader(executor, ecsql, params, config);
626
635
  const val = callback(reader);
627
636
  if (val instanceof Promise) {
628
637
  val.then(release, release);
@@ -722,8 +731,7 @@ class IModelDb extends core_common_1.IModel {
722
731
  const where = [...parentCategories].join(",");
723
732
  const query = `SELECT ECInstanceId as id, Parent.Id as parentId, Properties as appearance FROM BisCore.SubCategory WHERE Parent.Id IN (${where})`;
724
733
  try {
725
- // eslint-disable-next-line @typescript-eslint/no-deprecated
726
- for await (const row of this.createQueryReader(query, undefined, { rowFormat: core_common_1.QueryRowFormat.UseJsPropertyNames })) {
734
+ for await (const row of this.createQueryReader(query, undefined, { rowFormat: core_common_1.QueryRowFormat.UseECSqlPropertyNames })) {
727
735
  result.push(row.toRow());
728
736
  }
729
737
  }
@@ -743,8 +751,7 @@ class IModelDb extends core_common_1.IModel {
743
751
  const where = [...categoryIds].join(",");
744
752
  const query = `SELECT ECInstanceId as id, Parent.Id as parentId, Properties as appearance FROM BisCore.SubCategory WHERE Parent.Id IN (${where})`;
745
753
  try {
746
- // eslint-disable-next-line @typescript-eslint/no-deprecated
747
- for await (const row of this.createQueryReader(query, undefined, { rowFormat: core_common_1.QueryRowFormat.UseJsPropertyNames })) {
754
+ for await (const row of this.createQueryReader(query, undefined, { rowFormat: core_common_1.QueryRowFormat.UseECSqlPropertyNames })) {
748
755
  result.push(row.toRow());
749
756
  }
750
757
  }
@@ -787,7 +794,7 @@ class IModelDb extends core_common_1.IModel {
787
794
  }
788
795
  }
789
796
  }
790
- }, core_common_1.QueryBinder.from(params.bindings));
797
+ }, core_common_1.QueryBinder.fromSkippingNullish(params.bindings));
791
798
  return ids;
792
799
  }
793
800
  clearCaches(params) {
@@ -798,11 +805,7 @@ class IModelDb extends core_common_1.IModel {
798
805
  this._jsClassMap = undefined;
799
806
  this._schemaMap = undefined;
800
807
  this._schemaContext = undefined;
801
- if (this._schemasPromise) {
802
- const old = this._schemasPromise;
803
- this._schemasPromise = undefined;
804
- old.then((view) => view.markOutdated()).catch(() => { });
805
- }
808
+ this._schemaViewManager?.reset();
806
809
  this[Symbols_1._nativeDb].clearECDbCache();
807
810
  }
808
811
  this.elements[Symbols_1._cache].clear();
@@ -1298,53 +1301,74 @@ class IModelDb extends core_common_1.IModel {
1298
1301
  return this._schemaContext;
1299
1302
  }
1300
1303
  /** Get the schema view for this iModel. The view is built lazily on
1301
- * first call by fetching compact binary schema data via `PRAGMA schema_view` through
1302
- * the ConcurrentQuery thread pool. Subsequent calls return the cached view. Multiple
1303
- * concurrent callers share a single in-flight build.
1304
+ * first call by fetching compact binary schema data through
1305
+ * the ConcurrentQuery thread pool.
1304
1306
  *
1305
1307
  * The returned `SchemaView` is a lightweight, read-only, synchronous API for
1306
1308
  * navigating schema metadata - classes, properties, relationships, enumerations, etc.
1307
1309
  * It is the recommended default for runtime read-only metadata access and is significantly
1308
1310
  * faster and lower-memory than [[schemaContext]]. Use [[schemaContext]] for schema authoring,
1309
1311
  * custom-attribute deserialization, or anywhere you need the full ecschema-metadata object graph.
1312
+ *
1313
+ * Every call shares one accumulating view instance and concurrent calls are serialized, so a
1314
+ * caller never observes a partially loaded view. The instance is discarded by [[clearCaches]],
1315
+ * for example after a schema import; the next call builds a new one. See
1316
+ * [GetSchemaViewArgs]($ecschema-metadata) for the arguments.
1310
1317
  * @beta
1311
1318
  */
1312
- async getSchemaView() {
1313
- if (this._schemasPromise) {
1314
- const ctx = await this._schemasPromise;
1315
- if (!ctx.isOutdated)
1316
- return ctx;
1317
- }
1318
- // Capture the in-flight promise locally so the rejection handler only clears
1319
- // `_schemasPromise` if it still points at this build. A concurrent invalidation +
1320
- // re-fetch could otherwise replace the field before our hydrate fails, and a naive
1321
- // `_schemasPromise = undefined` would clobber that newer reference.
1322
- const inflight = this._hydrateSchemas();
1323
- this._schemasPromise = inflight;
1324
- inflight.catch(() => {
1325
- if (this._schemasPromise === inflight)
1326
- this._schemasPromise = undefined;
1327
- });
1328
- return inflight;
1329
- }
1330
- async _hydrateSchemas() {
1331
- // PRAGMA returns exactly one row with format, formatVersion, data (binary), schemaToken.
1332
- // Important: only call reader.next() once - do NOT use `for await` on PRAGMA results.
1333
- // ConcurrentQuery wraps regular ECSQL in LIMIT/OFFSET for pagination but skips this for
1334
- // PRAGMAs. If the serialized result exceeds the memory threshold, the response is marked
1335
- // "Partial", and a `for await` loop would re-issue the same PRAGMA forever since PRAGMAs
1336
- // don't support OFFSET-based pagination.
1337
- // This implementation uses the non-pinned version of the pragma other than frontend - because backend
1338
- // is always strictly coupled with the native code.
1339
- const reader = this.createQueryReader("PRAGMA schema_view");
1319
+ async getSchemaView(args) {
1320
+ this._schemaViewManager ??= new ecschema_metadata_1.SchemaViewManager(this._createSchemaViewDataProvider());
1321
+ return this._schemaViewManager.getSchemaView(args);
1322
+ }
1323
+ /** The [SchemaViewDataProvider]($ecschema-metadata) backing this iModel's [[getSchemaView]]: the
1324
+ * transport-specific half of schema-view loading. The backend always uses the latest blob version
1325
+ * since it is strictly coupled with native code.
1326
+ */
1327
+ _createSchemaViewDataProvider() {
1328
+ return {
1329
+ fetchFullBlob: async () => this._fetchSchemaBlob("PRAGMA schema_view"),
1330
+ // Names are ECNames, so a comma can never occur in one. Native re-validates each token as an
1331
+ // ECName and fails the pragma on an unknown name.
1332
+ fetchFragmentBlob: async (schemaNames) => this._fetchSchemaBlob(`PRAGMA schema_view_fragment('${schemaNames.join(",")}')`),
1333
+ fetchManifest: async () => {
1334
+ const schemaRows = [];
1335
+ const schemaSql = "SELECT ECInstanceId, Name, VersionMajor, VersionWrite, VersionMinor FROM meta.ECSchemaDef";
1336
+ for await (const row of this.createQueryReader(schemaSql)) {
1337
+ // ECInstanceId arrives as a hex Id64String. `ec_` metadata rowids carry no briefcase
1338
+ // prefix, so the local id is the full value.
1339
+ schemaRows.push({ ecInstanceId: core_bentley_1.Id64.getLocalId(row[0]), name: row[1], versionMajor: row[2], versionWrite: row[3], versionMinor: row[4] });
1340
+ }
1341
+ const referenceRows = [];
1342
+ const referenceSql = "SELECT SourceECInstanceId, TargetECInstanceId FROM meta.SchemaHasSchemaReferences";
1343
+ for await (const row of this.createQueryReader(referenceSql))
1344
+ referenceRows.push({ sourceECInstanceId: core_bentley_1.Id64.getLocalId(row[0]), targetECInstanceId: core_bentley_1.Id64.getLocalId(row[1]) });
1345
+ return ecschema_metadata_1.SchemaManifest.fromRows(schemaRows, referenceRows);
1346
+ },
1347
+ fetchSchemaToken: async () => {
1348
+ const reader = this.createQueryReader("PRAGMA checksum(schema_token)");
1349
+ const result = await reader.next();
1350
+ if (result.done)
1351
+ throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "PRAGMA checksum(schema_token) returned no rows");
1352
+ return result.value.sha3_256;
1353
+ },
1354
+ };
1355
+ }
1356
+ /** Fetch one schema-view blob (full or fragment). Both `PRAGMA schema_view` and
1357
+ * `PRAGMA schema_view_fragment` return a single row with the same columns. */
1358
+ async _fetchSchemaBlob(pragma) {
1359
+ // Only call reader.next() once - do NOT use `for await` on PRAGMA results. ConcurrentQuery wraps
1360
+ // regular ECSQL in LIMIT/OFFSET for pagination but skips this for PRAGMAs; if the serialized result
1361
+ // exceeds the memory threshold the response is marked "Partial", and a `for await` loop would
1362
+ // re-issue the same PRAGMA forever since PRAGMAs don't support OFFSET-based pagination.
1363
+ const reader = this.createQueryReader(pragma);
1340
1364
  const result = await reader.next();
1341
1365
  if (result.done)
1342
- throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "PRAGMA schema_view returned no rows");
1366
+ throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, `${pragma} returned no rows`);
1343
1367
  const data = result.value.data;
1344
1368
  const token = result.value.schemaToken;
1345
1369
  if (data === undefined || data === null)
1346
- throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "PRAGMA schema_view returned null data column");
1347
- return ecschema_metadata_1.SchemaView.fromBinary(data, token ?? "");
1370
+ throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, `${pragma} returned null data column`);
1371
+ return { data, schemaToken: token ?? "" };
1348
1372
  }
1349
1373
  /** Get the linkTableRelationships for this IModel */
1350
1374
  get relationships() {
@@ -2520,16 +2544,21 @@ function processSchemaWriteStatus(status) {
2520
2544
  * @throws [[IModelError]]
2521
2545
  */
2522
2546
  _queryAspect(aspectInstanceId, aspectClassName) {
2523
- const sql = `SELECT *, ec_classname(ECClassId, 's:c') classFullName FROM ${aspectClassName} WHERE ECInstanceId=:aspectInstanceId`;
2547
+ // `SELECT *` targets a caller/runtime-determined ElementAspect subclass, so its shape can't be decomposed
2548
+ // ahead of time. Query using the non-deprecated UseECSqlPropertyNames format and reshape the row into the
2549
+ // legacy UseJsPropertyNames shape using ECSchema metadata (see ECSqlInstanceReshaper for why a naive,
2550
+ // non-schema-aware rename isn't safe here).
2551
+ const ecClass = (0, ECSqlInstanceReshaper_1.getRuntimeClass)(this._iModel, aspectClassName);
2552
+ const sql = `SELECT * FROM ${aspectClassName} WHERE ECInstanceId=:aspectInstanceId`;
2524
2553
  const aspect = this._iModel.withQueryReader(sql, (reader) => {
2525
2554
  if (reader.step()) {
2526
- const aspectProps = { ...reader.current.toRow() }; // start with everything that SELECT * returned; classFullName is supplied by the ec_classname alias
2527
- aspectProps.className = undefined; // clear the raw ECClassId-derived property from SELECT * that we don't want in the final instance
2555
+ const aspectProps = (0, ECSqlInstanceReshaper_1.reshapeInstanceRow)(reader.current.toRow(), ecClass, this._iModel);
2556
+ aspectProps.classFullName = aspectProps.className.replace(".", ":"); // add in property required by EntityProps
2557
+ aspectProps.className = undefined; // clear property from SELECT * that we don't want in the final instance
2528
2558
  return aspectProps;
2529
2559
  }
2530
2560
  return undefined;
2531
- // eslint-disable-next-line @typescript-eslint/no-deprecated
2532
- }, new core_common_1.QueryBinder().bindId("aspectInstanceId", aspectInstanceId), { rowFormat: core_common_1.QueryRowFormat.UseJsPropertyNames });
2561
+ }, new core_common_1.QueryBinder().bindId("aspectInstanceId", aspectInstanceId), { rowFormat: core_common_1.QueryRowFormat.UseECSqlPropertyNames });
2533
2562
  if (undefined === aspect) {
2534
2563
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.NotFound, `ElementAspect not found ${aspectInstanceId}, ${aspectClassName}`);
2535
2564
  }