@itwin/core-backend 5.15.1 → 5.16.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 (90) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/lib/cjs/ConcurrentQuery.d.ts +4 -0
  3. package/lib/cjs/ConcurrentQuery.d.ts.map +1 -1
  4. package/lib/cjs/ConcurrentQuery.js +28 -1
  5. package/lib/cjs/ConcurrentQuery.js.map +1 -1
  6. package/lib/cjs/ECDb.d.ts.map +1 -1
  7. package/lib/cjs/ECDb.js +5 -10
  8. package/lib/cjs/ECDb.js.map +1 -1
  9. package/lib/cjs/ECSqlRowExecutor.d.ts +39 -55
  10. package/lib/cjs/ECSqlRowExecutor.d.ts.map +1 -1
  11. package/lib/cjs/ECSqlRowExecutor.js +118 -140
  12. package/lib/cjs/ECSqlRowExecutor.js.map +1 -1
  13. package/lib/cjs/ECSqlSyncReader.d.ts +21 -8
  14. package/lib/cjs/ECSqlSyncReader.d.ts.map +1 -1
  15. package/lib/cjs/ECSqlSyncReader.js +67 -25
  16. package/lib/cjs/ECSqlSyncReader.js.map +1 -1
  17. package/lib/cjs/IModelDb.d.ts.map +1 -1
  18. package/lib/cjs/IModelDb.js +5 -10
  19. package/lib/cjs/IModelDb.js.map +1 -1
  20. package/lib/cjs/IModelHost.d.ts +7 -0
  21. package/lib/cjs/IModelHost.d.ts.map +1 -1
  22. package/lib/cjs/IModelHost.js +1 -0
  23. package/lib/cjs/IModelHost.js.map +1 -1
  24. package/lib/cjs/TxnManager.d.ts.map +1 -1
  25. package/lib/cjs/TxnManager.js +11 -6
  26. package/lib/cjs/TxnManager.js.map +1 -1
  27. package/lib/cjs/internal/ChangesetConflictArgs.d.ts +4 -0
  28. package/lib/cjs/internal/ChangesetConflictArgs.d.ts.map +1 -1
  29. package/lib/cjs/internal/ChangesetConflictArgs.js +1 -0
  30. package/lib/cjs/internal/ChangesetConflictArgs.js.map +1 -1
  31. package/lib/cjs/internal/QueryMetadataCache.d.ts +8 -0
  32. package/lib/cjs/internal/QueryMetadataCache.d.ts.map +1 -1
  33. package/lib/cjs/internal/QueryMetadataCache.js +23 -0
  34. package/lib/cjs/internal/QueryMetadataCache.js.map +1 -1
  35. package/lib/esm/ConcurrentQuery.d.ts +4 -0
  36. package/lib/esm/ConcurrentQuery.d.ts.map +1 -1
  37. package/lib/esm/ConcurrentQuery.js +29 -2
  38. package/lib/esm/ConcurrentQuery.js.map +1 -1
  39. package/lib/esm/ECDb.d.ts.map +1 -1
  40. package/lib/esm/ECDb.js +6 -11
  41. package/lib/esm/ECDb.js.map +1 -1
  42. package/lib/esm/ECSqlRowExecutor.d.ts +39 -55
  43. package/lib/esm/ECSqlRowExecutor.d.ts.map +1 -1
  44. package/lib/esm/ECSqlRowExecutor.js +119 -140
  45. package/lib/esm/ECSqlRowExecutor.js.map +1 -1
  46. package/lib/esm/ECSqlSyncReader.d.ts +21 -8
  47. package/lib/esm/ECSqlSyncReader.d.ts.map +1 -1
  48. package/lib/esm/ECSqlSyncReader.js +68 -26
  49. package/lib/esm/ECSqlSyncReader.js.map +1 -1
  50. package/lib/esm/IModelDb.d.ts.map +1 -1
  51. package/lib/esm/IModelDb.js +6 -11
  52. package/lib/esm/IModelDb.js.map +1 -1
  53. package/lib/esm/IModelHost.d.ts +7 -0
  54. package/lib/esm/IModelHost.d.ts.map +1 -1
  55. package/lib/esm/IModelHost.js +1 -0
  56. package/lib/esm/IModelHost.js.map +1 -1
  57. package/lib/esm/TxnManager.d.ts.map +1 -1
  58. package/lib/esm/TxnManager.js +12 -7
  59. package/lib/esm/TxnManager.js.map +1 -1
  60. package/lib/esm/internal/ChangesetConflictArgs.d.ts +4 -0
  61. package/lib/esm/internal/ChangesetConflictArgs.d.ts.map +1 -1
  62. package/lib/esm/internal/ChangesetConflictArgs.js +1 -0
  63. package/lib/esm/internal/ChangesetConflictArgs.js.map +1 -1
  64. package/lib/esm/internal/QueryMetadataCache.d.ts +8 -0
  65. package/lib/esm/internal/QueryMetadataCache.d.ts.map +1 -1
  66. package/lib/esm/internal/QueryMetadataCache.js +23 -0
  67. package/lib/esm/internal/QueryMetadataCache.js.map +1 -1
  68. package/lib/esm/test/IModelHost.test.js +103 -2
  69. package/lib/esm/test/IModelHost.test.js.map +1 -1
  70. package/lib/esm/test/ecdb/CTE.test.js +21 -0
  71. package/lib/esm/test/ecdb/CTE.test.js.map +1 -1
  72. package/lib/esm/test/ecdb/ConcurrentQuery.test.js +72 -0
  73. package/lib/esm/test/ecdb/ConcurrentQuery.test.js.map +1 -1
  74. package/lib/esm/test/ecdb/ConcurrentQueryLoad.test.js +51 -9
  75. package/lib/esm/test/ecdb/ConcurrentQueryLoad.test.js.map +1 -1
  76. package/lib/esm/test/ecdb/ECSqlQuery.test.js +2 -1
  77. package/lib/esm/test/ecdb/ECSqlQuery.test.js.map +1 -1
  78. package/lib/esm/test/ecdb/ECSqlSyncReader.test.js +338 -84
  79. package/lib/esm/test/ecdb/ECSqlSyncReader.test.js.map +1 -1
  80. package/lib/esm/test/ecdb/QueryMetadataCache.test.js +40 -4
  81. package/lib/esm/test/ecdb/QueryMetadataCache.test.js.map +1 -1
  82. package/lib/esm/test/ecdb/QueryReaders.test.js +14 -7
  83. package/lib/esm/test/ecdb/QueryReaders.test.js.map +1 -1
  84. package/lib/esm/test/standalone/ChangeMerge.test.js +71 -1
  85. package/lib/esm/test/standalone/ChangeMerge.test.js.map +1 -1
  86. package/lib/esm/test/standalone/IModelWrite.test.js +6 -2
  87. package/lib/esm/test/standalone/IModelWrite.test.js.map +1 -1
  88. package/lib/esm/test/standalone/MergeConflict.test.js +15 -0
  89. package/lib/esm/test/standalone/MergeConflict.test.js.map +1 -1
  90. package/package.json +16 -16
@@ -1,79 +1,63 @@
1
1
  import { PropertyMetaDataMap } from "@itwin/core-common";
2
2
  import type { IModelDb } from "./IModelDb";
3
- import { ECSqlStatement } from "./ECSqlStatement";
4
- import { IModelJsNative } from "@bentley/imodeljs-native";
5
3
  import type { ECDb } from "./ECDb";
6
- import type { StatementCache } from "./SqliteStatement";
7
- /** Returns a statement to its cache, falling back to direct disposal without throwing.
4
+ /** Row rendering flags passed to `prepareECSqlBatchReader`.
8
5
  * @internal
9
6
  */
10
- export declare function releaseECSqlStatement(stmt: ECSqlStatement, cache: StatementCache<ECSqlStatement>, loggerCategory: string, canCache: boolean): void;
7
+ export declare enum SyncReaderRowFlags {
8
+ None = 0,
9
+ AbbreviateBlobs = 1,
10
+ ClassIdsToClassNames = 2,
11
+ UseJsName = 4,
12
+ DoNotConvertClassIdsToClassNamesWhenAliased = 8
13
+ }
11
14
  /**
12
- * Executes ECSql queries one row at a time against an IModelDb, maintaining statement state between
13
- * successive calls so the caller can page through results via offset-based requests.
15
+ * Executes an ECSql query on the db's primary connection and hands out rows one at a time. On a read-only db, rows are
16
+ * read from native code in growing batches to reduce JS/native transitions; on a writable db native code returns one row
17
+ * per call so rows are never read ahead of writes made while iterating. The prepared statement is leased from the native
18
+ * concurrent query statement cache and returned to it when the executor is disposed.
14
19
  * @internal
15
20
  */
16
21
  export declare class ECSqlRowExecutor implements Disposable {
17
22
  private readonly _db;
18
- private readonly _stmt;
19
- private readonly _loggerCategory;
20
23
  private _removeListener;
21
- private _isDisposed;
22
- private _canCacheStatement;
23
- /** Whether the statement completed preparation and can be returned to its cache.
24
- * @internal
25
- */
26
- get canCacheStatement(): boolean;
27
- constructor(_db: IModelDb | ECDb, _stmt: ECSqlStatement, _loggerCategory: string);
28
- /** Disposes the currently held statement without returning it to the cache.
29
- * Invoked when the db signals it is closing (the statement cache is cleared on close, so the
30
- * checked-out statement must be disposed directly to avoid a use-after-free or double dispose).
31
- * @internal
32
- */
33
- private cleanup;
34
- /** Removes the database-close listener owned by this row executor.
24
+ private _reader?;
25
+ /** Key of this statement's metadata in `metadataCache`, or undefined when its metadata must not be cached. */
26
+ private _metadataKey;
27
+ private _rows;
28
+ private _rowIndex;
29
+ private _batchSize;
30
+ /** Status of the last native batch. Anything other than BE_SQLITE_ROW means no further batch is fetched. */
31
+ private _batchStatus;
32
+ constructor(_db: IModelDb | ECDb);
33
+ /** Returns the leased statement to the native cache. Safe to call more than once. */
34
+ private releaseReader;
35
+ private resetBatch;
36
+ /** Returns the leased statement and removes the database-close listener owned by this row executor.
35
37
  * @internal
36
38
  */
37
39
  [Symbol.dispose](): void;
38
- /** Prepare the statement and bind parameters in one step.
39
- * Call once during reader initialization — avoids the per-row `ensureStatementReady` check.
40
+ /** Prepare the statement and bind parameters in one step. On a read-only db the first batch of rows is read by the
41
+ * same native call.
40
42
  * @param query - The ECSql text to prepare.
41
- * @param args - Optional bind parameters.
43
+ * @param args - Optional bind parameters as flat `[key, type, value, ...]` triples.
44
+ * @param rowFlags - [[SyncReaderRowFlags]] used for every row and for metadata.
42
45
  * @throws IModelError on preparation or binding failure.
43
46
  * @internal
44
47
  */
45
- prepareAndBind(query: string, args?: object): void;
46
- /** Fast-path: step the cursor once and return row data directly.
47
- *
48
- * Returns the row data array if a row is available.
49
- * Returns `undefined` if the result set is exhausted (DONE).
50
- *
51
- * This avoids all intermediate object allocations (StepResult, RowDataResult,
52
- * DbRuntimeStats, DbQueryResponse) that the general `execute()` path creates per row.
53
- *
54
- * @param options - Native row-adaptor options (should be cached and reused across rows).
55
- * @throws IModelError on step failure or row extraction failure.
56
- * @internal
57
- */
58
- stepNextRow(options: IModelJsNative.ECSqlRowAdaptorOptions): any;
59
- /** Get column metadata directly from the prepared statement.
60
- * Call once after `prepareAndBind` — the metadata does not change between rows.
61
- * @param options - Native row-adaptor options that influence property naming.
62
- * @returns Column metadata owned by the caller.
63
- * @internal
64
- */
65
- fetchMetadata(options: IModelJsNative.ECSqlRowAdaptorOptions): PropertyMetaDataMap;
66
- /** Prepares the ECSql statement when the caller did not supply one from its cache.
67
- * @param ecsql - The ECSql text to prepare.
68
- * @returns An `OperationResult` indicating success or failure.
48
+ prepareAndBind(query: string, args: any[] | undefined, rowFlags: SyncReaderRowFlags): void;
49
+ /** Store a batch returned by native code: a JSON array string, an array of rows, or the final DbResult. */
50
+ private acceptBatch;
51
+ /** Return the next row data array, or `undefined` when the result set is exhausted. Rows are served from the
52
+ * read-ahead buffer; a new native batch is fetched only when the buffer is empty.
53
+ * @throws IModelError on step failure or when the statement is no longer available. A failure reported by a batch is
54
+ * thrown after the rows stepped before it have been returned.
69
55
  * @internal
70
56
  */
71
- private prepareStmt;
72
- /** Binds the supplied parameter values to the prepared statement.
73
- * @param args - The parameter object to bind, or `undefined` when no parameters are needed.
74
- * @returns An `OperationResult` indicating success or failure.
57
+ stepNextRow(): any;
58
+ /** Get shared internal column metadata. Readers must copy it before exposing mutable metadata to callers.
75
59
  * @internal
76
60
  */
77
- private bindValues;
61
+ fetchMetadata(): PropertyMetaDataMap;
78
62
  }
79
63
  //# sourceMappingURL=ECSqlRowExecutor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ECSqlRowExecutor.d.ts","sourceRoot":"","sources":["../../src/ECSqlRowExecutor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAYxD;;GAEG;AAEH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,CAAC,cAAc,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAoBlJ;AAkBD;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IAW9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAAmB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAkB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAVlI,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAQ;IAElC;;OAEG;IACH,IAAW,iBAAiB,IAAI,OAAO,CAAoC;gBAGvC,GAAG,EAAE,QAAQ,GAAG,IAAI,EAAmB,KAAK,EAAE,cAAc,EAAmB,eAAe,EAAE,MAAM;IAQ1I;;;;OAIG;IACH,OAAO,CAAC,OAAO;IASf;;OAEG;IACI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;IAS/B;;;;;;OAMG;IACI,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAazD;;;;;;;;;;;OAWG;IACI,WAAW,CAAC,OAAO,EAAE,cAAc,CAAC,sBAAsB,GAAG,GAAG;IAWvE;;;;;OAKG;IACI,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,sBAAsB,GAAG,mBAAmB;IAUzF;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAkBnB;;;;OAIG;IACH,OAAO,CAAC,UAAU;CAcnB"}
1
+ {"version":3,"file":"ECSqlRowExecutor.d.ts","sourceRoot":"","sources":["../../src/ECSqlRowExecutor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAqBnC;;GAEG;AACH,oBAAY,kBAAkB;IAC5B,IAAI,IAAI;IACR,eAAe,IAAI;IACnB,oBAAoB,IAAI;IACxB,SAAS,IAAI;IACb,2CAA2C,IAAI;CAChD;AAyBD;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IAW9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAVvC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,OAAO,CAAC,CAAkC;IAClD,8GAA8G;IAC9G,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,UAAU,CAAoB;IACtC,4GAA4G;IAC5G,OAAO,CAAC,YAAY,CAA0B;gBAEV,GAAG,EAAE,QAAQ,GAAG,IAAI;IAQxD,qFAAqF;IACrF,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;IAS/B;;;;;;;OAOG;IACI,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,SAAS,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAejG,2GAA2G;IAC3G,OAAO,CAAC,WAAW;IAWnB;;;;;OAKG;IACI,WAAW,IAAI,GAAG;IAmBzB;;OAEG;IACI,aAAa,IAAI,mBAAmB;CAO5C"}
@@ -2,191 +2,170 @@
2
2
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- import { IModelError } from "@itwin/core-common";
6
- import { BentleyError, DbResult, Logger } from "@itwin/core-bentley";
5
+ import { IModelError, PropertyMetaDataMap } from "@itwin/core-common";
6
+ import { BentleyError, DbResult } from "@itwin/core-bentley";
7
7
  import { _nativeDb } from "./internal/Symbols";
8
8
  const statementNotPreparedMessage = "Statement is not prepared. Likely cause: the db was closed before step is called or the ECSqlSyncReader is used outside the context of the callback passed to withQueryReader.";
9
- function logStatementCleanupError(loggerCategory, message, error) {
10
- try {
11
- Logger.logTrace(loggerCategory, message, () => ({ error: BentleyError.getErrorMessage(error) }));
12
- }
13
- catch {
14
- // Cleanup diagnostics must not replace the original query error.
15
- }
16
- }
17
- /** Returns a statement to its cache, falling back to direct disposal without throwing.
9
+ /** Column metadata keyed by native statement id and row options. Statement ids change whenever the native cache
10
+ * prepares the ECSql again, so entries never outlive the statement they describe.
11
+ */
12
+ const maxCachedMetadataEntries = 100;
13
+ /** Rows fetched by the first native batch. Small so single-row and early-exit queries do little extra work. */
14
+ const initialBatchSize = 4;
15
+ /** Each subsequent batch grows by this factor up to [[maxBatchSize]]. */
16
+ const batchGrowthFactor = 4;
17
+ /** Upper bound on rows rendered per native call; bounds memory held by the read-ahead buffer. */
18
+ const maxBatchSize = 128;
19
+ /** Number of distinct row-flag combinations; metadata keys pack the statement id and the row flags into one number. */
20
+ const rowFlagCombinations = 16;
21
+ const metadataCache = new WeakMap();
22
+ /** Receives the first batch and statement id from `prepareECSqlBatchReader`. Read immediately after each call. */
23
+ const prepareOut = [];
24
+ /** Row rendering flags passed to `prepareECSqlBatchReader`.
18
25
  * @internal
19
26
  */
20
- // eslint-disable-next-line @typescript-eslint/no-deprecated
21
- export function releaseECSqlStatement(stmt, cache, loggerCategory, canCache) {
22
- if (!canCache || !stmt.isPrepared) {
23
- try {
24
- stmt[Symbol.dispose]();
25
- }
26
- catch (error) {
27
- logStatementCleanupError(loggerCategory, "Failed to dispose an ECSQL statement that could not be cached.", error);
28
- }
29
- return;
27
+ export var SyncReaderRowFlags;
28
+ (function (SyncReaderRowFlags) {
29
+ SyncReaderRowFlags[SyncReaderRowFlags["None"] = 0] = "None";
30
+ SyncReaderRowFlags[SyncReaderRowFlags["AbbreviateBlobs"] = 1] = "AbbreviateBlobs";
31
+ SyncReaderRowFlags[SyncReaderRowFlags["ClassIdsToClassNames"] = 2] = "ClassIdsToClassNames";
32
+ SyncReaderRowFlags[SyncReaderRowFlags["UseJsName"] = 4] = "UseJsName";
33
+ SyncReaderRowFlags[SyncReaderRowFlags["DoNotConvertClassIdsToClassNamesWhenAliased"] = 8] = "DoNotConvertClassIdsToClassNamesWhenAliased";
34
+ })(SyncReaderRowFlags || (SyncReaderRowFlags = {}));
35
+ function getCachedMetadata(db, key, fetch) {
36
+ let entries = metadataCache.get(db);
37
+ if (!entries) {
38
+ entries = new Map();
39
+ metadataCache.set(db, entries);
30
40
  }
31
- try {
32
- cache.addOrDispose(stmt);
41
+ let properties = entries.get(key);
42
+ if (properties) {
43
+ entries.delete(key);
33
44
  }
34
- catch (error) {
35
- try {
36
- stmt[Symbol.dispose]();
37
- }
38
- catch (disposeError) {
39
- logStatementCleanupError(loggerCategory, "Failed to dispose an ECSQL statement after it could not be returned to the statement cache.", disposeError);
40
- }
41
- logStatementCleanupError(loggerCategory, "Failed to return an ECSQL statement to the statement cache; attempted direct disposal instead.", error);
45
+ else {
46
+ properties = fetch();
47
+ if (entries.size >= maxCachedMetadataEntries)
48
+ entries.delete(entries.keys().next().value);
42
49
  }
50
+ entries.set(key, properties);
51
+ return properties;
43
52
  }
44
53
  // --------------------------------------------------------------------------------------------
45
54
  // ECSqlRowExecutor
46
55
  // --------------------------------------------------------------------------------------------
47
56
  /**
48
- * Executes ECSql queries one row at a time against an IModelDb, maintaining statement state between
49
- * successive calls so the caller can page through results via offset-based requests.
57
+ * Executes an ECSql query on the db's primary connection and hands out rows one at a time. On a read-only db, rows are
58
+ * read from native code in growing batches to reduce JS/native transitions; on a writable db native code returns one row
59
+ * per call so rows are never read ahead of writes made while iterating. The prepared statement is leased from the native
60
+ * concurrent query statement cache and returned to it when the executor is disposed.
50
61
  * @internal
51
62
  */
52
63
  export class ECSqlRowExecutor {
53
64
  _db;
54
- _stmt;
55
- _loggerCategory;
56
65
  _removeListener;
57
- _isDisposed = false;
58
- _canCacheStatement = true;
59
- /** Whether the statement completed preparation and can be returned to its cache.
60
- * @internal
61
- */
62
- get canCacheStatement() { return this._canCacheStatement; }
63
- // eslint-disable-next-line @typescript-eslint/no-deprecated
64
- constructor(_db, _stmt, _loggerCategory) {
66
+ _reader;
67
+ /** Key of this statement's metadata in `metadataCache`, or undefined when its metadata must not be cached. */
68
+ _metadataKey;
69
+ _rows = [];
70
+ _rowIndex = 0;
71
+ _batchSize = initialBatchSize;
72
+ /** Status of the last native batch. Anything other than BE_SQLITE_ROW means no further batch is fetched. */
73
+ _batchStatus = DbResult.BE_SQLITE_ROW;
74
+ constructor(_db) {
65
75
  this._db = _db;
66
- this._stmt = _stmt;
67
- this._loggerCategory = _loggerCategory;
68
- this._removeListener = this._db.onBeforeClose.addListener(() => this.cleanup());
76
+ this._removeListener = this._db.onBeforeClose.addListener(() => this.releaseReader());
69
77
  }
70
78
  // --------------------------------------------------------------------------------------------
71
79
  // Lifecycle
72
80
  // --------------------------------------------------------------------------------------------
73
- /** Disposes the currently held statement without returning it to the cache.
74
- * Invoked when the db signals it is closing (the statement cache is cleared on close, so the
75
- * checked-out statement must be disposed directly to avoid a use-after-free or double dispose).
76
- * @internal
77
- */
78
- cleanup() {
79
- this._isDisposed = true;
80
- try {
81
- this._stmt[Symbol.dispose]();
82
- }
83
- catch (error) {
84
- logStatementCleanupError(this._loggerCategory, "Failed to dispose an ECSQL statement while closing its database.", error);
85
- }
81
+ /** Returns the leased statement to the native cache. Safe to call more than once. */
82
+ releaseReader() {
83
+ const reader = this._reader;
84
+ this._reader = undefined;
85
+ this.resetBatch();
86
+ reader?.dispose();
87
+ }
88
+ resetBatch() {
89
+ this._rows = [];
90
+ this._rowIndex = 0;
91
+ this._batchSize = initialBatchSize;
92
+ this._batchStatus = DbResult.BE_SQLITE_ROW;
86
93
  }
87
- /** Removes the database-close listener owned by this row executor.
94
+ /** Returns the leased statement and removes the database-close listener owned by this row executor.
88
95
  * @internal
89
96
  */
90
97
  [Symbol.dispose]() {
91
- this._isDisposed = true;
92
98
  this._removeListener();
99
+ this.releaseReader();
93
100
  }
94
101
  // --------------------------------------------------------------------------------------------
95
102
  // Core execution
96
103
  // --------------------------------------------------------------------------------------------
97
- /** Prepare the statement and bind parameters in one step.
98
- * Call once during reader initialization — avoids the per-row `ensureStatementReady` check.
104
+ /** Prepare the statement and bind parameters in one step. On a read-only db the first batch of rows is read by the
105
+ * same native call.
99
106
  * @param query - The ECSql text to prepare.
100
- * @param args - Optional bind parameters.
107
+ * @param args - Optional bind parameters as flat `[key, type, value, ...]` triples.
108
+ * @param rowFlags - [[SyncReaderRowFlags]] used for every row and for metadata.
101
109
  * @throws IModelError on preparation or binding failure.
102
110
  * @internal
103
111
  */
104
- prepareAndBind(query, args) {
105
- const prepResult = this.prepareStmt(query);
106
- if (!prepResult.isSuccessful)
107
- throw new IModelError(DbResult.BE_SQLITE_ERROR, prepResult.message ?? `Failed to prepare statement: ${query}`);
108
- if (args) {
109
- const bindResult = this.bindValues(args);
110
- if (!bindResult.isSuccessful)
111
- throw new IModelError(DbResult.BE_SQLITE_ERROR, bindResult.message ?? `Failed to bind values: ${query}`);
112
+ prepareAndBind(query, args, rowFlags) {
113
+ this.releaseReader();
114
+ try {
115
+ this._reader = this._db[_nativeDb].prepareECSqlBatchReader(query, args, rowFlags, this._batchSize, prepareOut);
112
116
  }
117
+ catch (error) {
118
+ throw new IModelError(DbResult.BE_SQLITE_ERROR, BentleyError.getErrorMessage(error));
119
+ }
120
+ const firstBatch = prepareOut[0];
121
+ // Metadata of a dynamic column (e.g. $->prop) changes as the statement is stepped, so it is never cached.
122
+ this._metadataKey = prepareOut[2] ? undefined : prepareOut[1] * rowFlagCombinations + rowFlags;
123
+ prepareOut.length = 0;
124
+ if (firstBatch !== undefined)
125
+ this.acceptBatch(firstBatch);
113
126
  }
114
- /** Fast-path: step the cursor once and return row data directly.
115
- *
116
- * Returns the row data array if a row is available.
117
- * Returns `undefined` if the result set is exhausted (DONE).
118
- *
119
- * This avoids all intermediate object allocations (StepResult, RowDataResult,
120
- * DbRuntimeStats, DbQueryResponse) that the general `execute()` path creates per row.
121
- *
122
- * @param options - Native row-adaptor options (should be cached and reused across rows).
123
- * @throws IModelError on step failure or row extraction failure.
124
- * @internal
125
- */
126
- stepNextRow(options) {
127
- if (this._isDisposed || !this._stmt.isPrepared)
128
- throw new IModelError(DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);
129
- const stepResult = this._stmt.step();
130
- if (stepResult === DbResult.BE_SQLITE_ROW)
131
- return this._stmt.toRow(options).data;
132
- if (stepResult === DbResult.BE_SQLITE_DONE)
133
- return undefined;
134
- throw new IModelError(stepResult, `Step failed with code ${stepResult}`);
127
+ /** Store a batch returned by native code: a JSON array string, an array of rows, or the final DbResult. */
128
+ acceptBatch(batch) {
129
+ this._batchSize = Math.min(this._batchSize * batchGrowthFactor, maxBatchSize);
130
+ this._rowIndex = 0;
131
+ if (typeof batch === "number") {
132
+ this._rows = [];
133
+ this._batchStatus = batch;
134
+ }
135
+ else {
136
+ this._rows = typeof batch === "string" ? JSON.parse(batch) : batch;
137
+ }
135
138
  }
136
- /** Get column metadata directly from the prepared statement.
137
- * Call once after `prepareAndBind` — the metadata does not change between rows.
138
- * @param options - Native row-adaptor options that influence property naming.
139
- * @returns Column metadata owned by the caller.
139
+ /** Return the next row data array, or `undefined` when the result set is exhausted. Rows are served from the
140
+ * read-ahead buffer; a new native batch is fetched only when the buffer is empty.
141
+ * @throws IModelError on step failure or when the statement is no longer available. A failure reported by a batch is
142
+ * thrown after the rows stepped before it have been returned.
140
143
  * @internal
141
144
  */
142
- fetchMetadata(options) {
143
- if (this._isDisposed || !this._stmt.isPrepared)
145
+ stepNextRow() {
146
+ if (this._rowIndex < this._rows.length)
147
+ return this._rows[this._rowIndex++];
148
+ const reader = this._reader;
149
+ if (!reader)
144
150
  throw new IModelError(DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);
145
- return this._stmt.getMetadata(options);
146
- }
147
- // --------------------------------------------------------------------------------------------
148
- // Execution phases
149
- // --------------------------------------------------------------------------------------------
150
- /** Prepares the ECSql statement when the caller did not supply one from its cache.
151
- * @param ecsql - The ECSql text to prepare.
152
- * @returns An `OperationResult` indicating success or failure.
153
- * @internal
154
- */
155
- prepareStmt(ecsql) {
156
- if (this._stmt.isPrepared)
157
- return { isSuccessful: true };
158
- try {
159
- this._stmt.prepare(this._db[_nativeDb], ecsql);
160
- return { isSuccessful: true };
161
- }
162
- catch (error) {
163
- this._canCacheStatement = false;
164
- try {
165
- this._stmt[Symbol.dispose]();
166
- }
167
- catch (disposeError) {
168
- logStatementCleanupError(this._loggerCategory, "Failed to dispose an ECSQL statement after preparation failed.", disposeError);
169
- }
170
- return { isSuccessful: false, message: error.message };
151
+ if (this._batchStatus === DbResult.BE_SQLITE_ROW) {
152
+ this.acceptBatch(reader.stepBatch(this._batchSize));
153
+ if (this._rows.length > 0)
154
+ return this._rows[this._rowIndex++];
171
155
  }
156
+ if (this._batchStatus === DbResult.BE_SQLITE_DONE)
157
+ return undefined;
158
+ throw new IModelError(this._batchStatus, `Step failed with code ${this._batchStatus}`);
172
159
  }
173
- /** Binds the supplied parameter values to the prepared statement.
174
- * @param args - The parameter object to bind, or `undefined` when no parameters are needed.
175
- * @returns An `OperationResult` indicating success or failure.
160
+ /** Get shared internal column metadata. Readers must copy it before exposing mutable metadata to callers.
176
161
  * @internal
177
162
  */
178
- bindValues(args) {
179
- try {
180
- if (args === undefined)
181
- return { isSuccessful: true };
182
- if (!this._stmt.isPrepared)
183
- return { isSuccessful: false, message: statementNotPreparedMessage };
184
- this._stmt.bindParams(args);
185
- return { isSuccessful: true };
186
- }
187
- catch (error) {
188
- return { isSuccessful: false, message: error.message };
189
- }
163
+ fetchMetadata() {
164
+ const reader = this._reader;
165
+ if (!reader)
166
+ throw new IModelError(DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);
167
+ const fetch = () => new PropertyMetaDataMap(reader.getMetadata());
168
+ return this._metadataKey === undefined ? fetch() : getCachedMetadata(this._db, this._metadataKey, fetch);
190
169
  }
191
170
  }
192
171
  //# sourceMappingURL=ECSqlRowExecutor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ECSqlRowExecutor.js","sourceRoot":"","sources":["../../src/ECSqlRowExecutor.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { IModelError, PropertyMetaDataMap } from \"@itwin/core-common\";\r\nimport type { IModelDb } from \"./IModelDb\";\r\nimport { ECSqlStatement } from \"./ECSqlStatement\";\r\nimport { BentleyError, DbResult, Logger } from \"@itwin/core-bentley\";\r\nimport { IModelJsNative } from \"@bentley/imodeljs-native\";\r\nimport { _nativeDb } from \"./internal/Symbols\";\r\nimport type { ECDb } from \"./ECDb\";\r\nimport type { StatementCache } from \"./SqliteStatement\";\r\n\r\nconst statementNotPreparedMessage = \"Statement is not prepared. Likely cause: the db was closed before step is called or the ECSqlSyncReader is used outside the context of the callback passed to withQueryReader.\";\r\n\r\nfunction logStatementCleanupError(loggerCategory: string, message: string, error: unknown): void {\r\n try {\r\n Logger.logTrace(loggerCategory, message, () => ({ error: BentleyError.getErrorMessage(error) }));\r\n } catch {\r\n // Cleanup diagnostics must not replace the original query error.\r\n }\r\n}\r\n\r\n/** Returns a statement to its cache, falling back to direct disposal without throwing.\r\n * @internal\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-deprecated\r\nexport function releaseECSqlStatement(stmt: ECSqlStatement, cache: StatementCache<ECSqlStatement>, loggerCategory: string, canCache: boolean): void {\r\n if (!canCache || !stmt.isPrepared) {\r\n try {\r\n stmt[Symbol.dispose]();\r\n } catch (error) {\r\n logStatementCleanupError(loggerCategory, \"Failed to dispose an ECSQL statement that could not be cached.\", error);\r\n }\r\n return;\r\n }\r\n\r\n try {\r\n cache.addOrDispose(stmt);\r\n } catch (error) {\r\n try {\r\n stmt[Symbol.dispose]();\r\n } catch (disposeError) {\r\n logStatementCleanupError(loggerCategory, \"Failed to dispose an ECSQL statement after it could not be returned to the statement cache.\", disposeError);\r\n }\r\n logStatementCleanupError(loggerCategory, \"Failed to return an ECSQL statement to the statement cache; attempted direct disposal instead.\", error);\r\n }\r\n}\r\n\r\n// --------------------------------------------------------------------------------------------\r\n// Internal result types\r\n// --------------------------------------------------------------------------------------------\r\n\r\n/** Result of an internal operation that may fail with a message.\r\n * @internal\r\n */\r\ninterface OperationResult {\r\n isSuccessful: boolean;\r\n message?: string;\r\n}\r\n\r\n// --------------------------------------------------------------------------------------------\r\n// ECSqlRowExecutor\r\n// --------------------------------------------------------------------------------------------\r\n\r\n/**\r\n * Executes ECSql queries one row at a time against an IModelDb, maintaining statement state between\r\n * successive calls so the caller can page through results via offset-based requests.\r\n * @internal\r\n */\r\nexport class ECSqlRowExecutor implements Disposable {\r\n private _removeListener: () => void;\r\n private _isDisposed = false;\r\n private _canCacheStatement = true;\r\n\r\n /** Whether the statement completed preparation and can be returned to its cache.\r\n * @internal\r\n */\r\n public get canCacheStatement(): boolean { return this._canCacheStatement; }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-deprecated\r\n public constructor(private readonly _db: IModelDb | ECDb, private readonly _stmt: ECSqlStatement, private readonly _loggerCategory: string) {\r\n this._removeListener = this._db.onBeforeClose.addListener(() => this.cleanup());\r\n }\r\n\r\n // --------------------------------------------------------------------------------------------\r\n // Lifecycle\r\n // --------------------------------------------------------------------------------------------\r\n\r\n /** Disposes the currently held statement without returning it to the cache.\r\n * Invoked when the db signals it is closing (the statement cache is cleared on close, so the\r\n * checked-out statement must be disposed directly to avoid a use-after-free or double dispose).\r\n * @internal\r\n */\r\n private cleanup(): void {\r\n this._isDisposed = true;\r\n try {\r\n this._stmt[Symbol.dispose]();\r\n } catch (error) {\r\n logStatementCleanupError(this._loggerCategory, \"Failed to dispose an ECSQL statement while closing its database.\", error);\r\n }\r\n }\r\n\r\n /** Removes the database-close listener owned by this row executor.\r\n * @internal\r\n */\r\n public [Symbol.dispose](): void {\r\n this._isDisposed = true;\r\n this._removeListener();\r\n }\r\n\r\n // --------------------------------------------------------------------------------------------\r\n // Core execution\r\n // --------------------------------------------------------------------------------------------\r\n\r\n /** Prepare the statement and bind parameters in one step.\r\n * Call once during reader initialization — avoids the per-row `ensureStatementReady` check.\r\n * @param query - The ECSql text to prepare.\r\n * @param args - Optional bind parameters.\r\n * @throws IModelError on preparation or binding failure.\r\n * @internal\r\n */\r\n public prepareAndBind(query: string, args?: object): void {\r\n\r\n const prepResult = this.prepareStmt(query);\r\n if (!prepResult.isSuccessful)\r\n throw new IModelError(DbResult.BE_SQLITE_ERROR, prepResult.message ?? `Failed to prepare statement: ${query}`);\r\n\r\n if (args) {\r\n const bindResult = this.bindValues(args);\r\n if (!bindResult.isSuccessful)\r\n throw new IModelError(DbResult.BE_SQLITE_ERROR, bindResult.message ?? `Failed to bind values: ${query}`);\r\n }\r\n }\r\n\r\n /** Fast-path: step the cursor once and return row data directly.\r\n *\r\n * Returns the row data array if a row is available.\r\n * Returns `undefined` if the result set is exhausted (DONE).\r\n *\r\n * This avoids all intermediate object allocations (StepResult, RowDataResult,\r\n * DbRuntimeStats, DbQueryResponse) that the general `execute()` path creates per row.\r\n *\r\n * @param options - Native row-adaptor options (should be cached and reused across rows).\r\n * @throws IModelError on step failure or row extraction failure.\r\n * @internal\r\n */\r\n public stepNextRow(options: IModelJsNative.ECSqlRowAdaptorOptions): any {\r\n if (this._isDisposed || !this._stmt.isPrepared)\r\n throw new IModelError(DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);\r\n const stepResult = this._stmt.step();\r\n if (stepResult === DbResult.BE_SQLITE_ROW)\r\n return this._stmt.toRow(options).data;\r\n if (stepResult === DbResult.BE_SQLITE_DONE)\r\n return undefined;\r\n throw new IModelError(stepResult, `Step failed with code ${stepResult}`);\r\n }\r\n\r\n /** Get column metadata directly from the prepared statement.\r\n * Call once after `prepareAndBind` — the metadata does not change between rows.\r\n * @param options - Native row-adaptor options that influence property naming.\r\n * @returns Column metadata owned by the caller.\r\n * @internal\r\n */\r\n public fetchMetadata(options: IModelJsNative.ECSqlRowAdaptorOptions): PropertyMetaDataMap {\r\n if (this._isDisposed || !this._stmt.isPrepared)\r\n throw new IModelError(DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);\r\n return this._stmt.getMetadata(options);\r\n }\r\n\r\n // --------------------------------------------------------------------------------------------\r\n // Execution phases\r\n // --------------------------------------------------------------------------------------------\r\n\r\n /** Prepares the ECSql statement when the caller did not supply one from its cache.\r\n * @param ecsql - The ECSql text to prepare.\r\n * @returns An `OperationResult` indicating success or failure.\r\n * @internal\r\n */\r\n private prepareStmt(ecsql: string): OperationResult {\r\n if (this._stmt.isPrepared)\r\n return { isSuccessful: true };\r\n\r\n try {\r\n this._stmt.prepare(this._db[_nativeDb], ecsql);\r\n return { isSuccessful: true };\r\n } catch (error: any) {\r\n this._canCacheStatement = false;\r\n try {\r\n this._stmt[Symbol.dispose]();\r\n } catch (disposeError) {\r\n logStatementCleanupError(this._loggerCategory, \"Failed to dispose an ECSQL statement after preparation failed.\", disposeError);\r\n }\r\n return { isSuccessful: false, message: error.message };\r\n }\r\n }\r\n\r\n /** Binds the supplied parameter values to the prepared statement.\r\n * @param args - The parameter object to bind, or `undefined` when no parameters are needed.\r\n * @returns An `OperationResult` indicating success or failure.\r\n * @internal\r\n */\r\n private bindValues(args: object | undefined): OperationResult {\r\n try {\r\n if (args === undefined)\r\n return { isSuccessful: true };\r\n\r\n if (!this._stmt.isPrepared)\r\n return { isSuccessful: false, message: statementNotPreparedMessage };\r\n\r\n this._stmt.bindParams(args);\r\n return { isSuccessful: true };\r\n } catch (error: any) {\r\n return { isSuccessful: false, message: error.message };\r\n }\r\n }\r\n}"]}
1
+ {"version":3,"file":"ECSqlRowExecutor.js","sourceRoot":"","sources":["../../src/ECSqlRowExecutor.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { IModelError, PropertyMetaDataMap } from \"@itwin/core-common\";\r\nimport type { IModelDb } from \"./IModelDb\";\r\nimport { BentleyError, DbResult } from \"@itwin/core-bentley\";\r\nimport { IModelJsNative } from \"@bentley/imodeljs-native\";\r\nimport { _nativeDb } from \"./internal/Symbols\";\r\nimport type { ECDb } from \"./ECDb\";\r\n\r\nconst statementNotPreparedMessage = \"Statement is not prepared. Likely cause: the db was closed before step is called or the ECSqlSyncReader is used outside the context of the callback passed to withQueryReader.\";\r\n\r\n/** Column metadata keyed by native statement id and row options. Statement ids change whenever the native cache\r\n * prepares the ECSql again, so entries never outlive the statement they describe.\r\n */\r\nconst maxCachedMetadataEntries = 100;\r\n\r\n/** Rows fetched by the first native batch. Small so single-row and early-exit queries do little extra work. */\r\nconst initialBatchSize = 4;\r\n/** Each subsequent batch grows by this factor up to [[maxBatchSize]]. */\r\nconst batchGrowthFactor = 4;\r\n/** Upper bound on rows rendered per native call; bounds memory held by the read-ahead buffer. */\r\nconst maxBatchSize = 128;\r\n/** Number of distinct row-flag combinations; metadata keys pack the statement id and the row flags into one number. */\r\nconst rowFlagCombinations = 16;\r\nconst metadataCache = new WeakMap<IModelDb | ECDb, Map<number, PropertyMetaDataMap>>();\r\n/** Receives the first batch and statement id from `prepareECSqlBatchReader`. Read immediately after each call. */\r\nconst prepareOut: any[] = [];\r\n\r\n/** Row rendering flags passed to `prepareECSqlBatchReader`.\r\n * @internal\r\n */\r\nexport enum SyncReaderRowFlags {\r\n None = 0,\r\n AbbreviateBlobs = 1,\r\n ClassIdsToClassNames = 2,\r\n UseJsName = 4,\r\n DoNotConvertClassIdsToClassNamesWhenAliased = 8,\r\n}\r\n\r\nfunction getCachedMetadata(db: IModelDb | ECDb, key: number, fetch: () => PropertyMetaDataMap): PropertyMetaDataMap {\r\n let entries = metadataCache.get(db);\r\n if (!entries) {\r\n entries = new Map();\r\n metadataCache.set(db, entries);\r\n }\r\n\r\n let properties = entries.get(key);\r\n if (properties) {\r\n entries.delete(key);\r\n } else {\r\n properties = fetch();\r\n if (entries.size >= maxCachedMetadataEntries)\r\n entries.delete(entries.keys().next().value as number);\r\n }\r\n entries.set(key, properties);\r\n return properties;\r\n}\r\n\r\n// --------------------------------------------------------------------------------------------\r\n// ECSqlRowExecutor\r\n// --------------------------------------------------------------------------------------------\r\n\r\n/**\r\n * Executes an ECSql query on the db's primary connection and hands out rows one at a time. On a read-only db, rows are\r\n * read from native code in growing batches to reduce JS/native transitions; on a writable db native code returns one row\r\n * per call so rows are never read ahead of writes made while iterating. The prepared statement is leased from the native\r\n * concurrent query statement cache and returned to it when the executor is disposed.\r\n * @internal\r\n */\r\nexport class ECSqlRowExecutor implements Disposable {\r\n private _removeListener: () => void;\r\n private _reader?: IModelJsNative.ECSqlBatchReader;\r\n /** Key of this statement's metadata in `metadataCache`, or undefined when its metadata must not be cached. */\r\n private _metadataKey: number | undefined;\r\n private _rows: any[] = [];\r\n private _rowIndex = 0;\r\n private _batchSize = initialBatchSize;\r\n /** Status of the last native batch. Anything other than BE_SQLITE_ROW means no further batch is fetched. */\r\n private _batchStatus = DbResult.BE_SQLITE_ROW;\r\n\r\n public constructor(private readonly _db: IModelDb | ECDb) {\r\n this._removeListener = this._db.onBeforeClose.addListener(() => this.releaseReader());\r\n }\r\n\r\n // --------------------------------------------------------------------------------------------\r\n // Lifecycle\r\n // --------------------------------------------------------------------------------------------\r\n\r\n /** Returns the leased statement to the native cache. Safe to call more than once. */\r\n private releaseReader(): void {\r\n const reader = this._reader;\r\n this._reader = undefined;\r\n this.resetBatch();\r\n reader?.dispose();\r\n }\r\n\r\n private resetBatch(): void {\r\n this._rows = [];\r\n this._rowIndex = 0;\r\n this._batchSize = initialBatchSize;\r\n this._batchStatus = DbResult.BE_SQLITE_ROW;\r\n }\r\n\r\n /** Returns the leased statement and removes the database-close listener owned by this row executor.\r\n * @internal\r\n */\r\n public [Symbol.dispose](): void {\r\n this._removeListener();\r\n this.releaseReader();\r\n }\r\n\r\n // --------------------------------------------------------------------------------------------\r\n // Core execution\r\n // --------------------------------------------------------------------------------------------\r\n\r\n /** Prepare the statement and bind parameters in one step. On a read-only db the first batch of rows is read by the\r\n * same native call.\r\n * @param query - The ECSql text to prepare.\r\n * @param args - Optional bind parameters as flat `[key, type, value, ...]` triples.\r\n * @param rowFlags - [[SyncReaderRowFlags]] used for every row and for metadata.\r\n * @throws IModelError on preparation or binding failure.\r\n * @internal\r\n */\r\n public prepareAndBind(query: string, args: any[] | undefined, rowFlags: SyncReaderRowFlags): void {\r\n this.releaseReader();\r\n try {\r\n this._reader = this._db[_nativeDb].prepareECSqlBatchReader(query, args, rowFlags, this._batchSize, prepareOut);\r\n } catch (error) {\r\n throw new IModelError(DbResult.BE_SQLITE_ERROR, BentleyError.getErrorMessage(error));\r\n }\r\n const firstBatch = prepareOut[0];\r\n // Metadata of a dynamic column (e.g. $->prop) changes as the statement is stepped, so it is never cached.\r\n this._metadataKey = prepareOut[2] ? undefined : prepareOut[1] * rowFlagCombinations + rowFlags;\r\n prepareOut.length = 0;\r\n if (firstBatch !== undefined)\r\n this.acceptBatch(firstBatch);\r\n }\r\n\r\n /** Store a batch returned by native code: a JSON array string, an array of rows, or the final DbResult. */\r\n private acceptBatch(batch: string | any[] | DbResult): void {\r\n this._batchSize = Math.min(this._batchSize * batchGrowthFactor, maxBatchSize);\r\n this._rowIndex = 0;\r\n if (typeof batch === \"number\") {\r\n this._rows = [];\r\n this._batchStatus = batch;\r\n } else {\r\n this._rows = typeof batch === \"string\" ? JSON.parse(batch) : batch;\r\n }\r\n }\r\n\r\n /** Return the next row data array, or `undefined` when the result set is exhausted. Rows are served from the\r\n * read-ahead buffer; a new native batch is fetched only when the buffer is empty.\r\n * @throws IModelError on step failure or when the statement is no longer available. A failure reported by a batch is\r\n * thrown after the rows stepped before it have been returned.\r\n * @internal\r\n */\r\n public stepNextRow(): any {\r\n if (this._rowIndex < this._rows.length)\r\n return this._rows[this._rowIndex++];\r\n\r\n const reader = this._reader;\r\n if (!reader)\r\n throw new IModelError(DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);\r\n\r\n if (this._batchStatus === DbResult.BE_SQLITE_ROW) {\r\n this.acceptBatch(reader.stepBatch(this._batchSize));\r\n if (this._rows.length > 0)\r\n return this._rows[this._rowIndex++];\r\n }\r\n\r\n if (this._batchStatus === DbResult.BE_SQLITE_DONE)\r\n return undefined;\r\n throw new IModelError(this._batchStatus, `Step failed with code ${this._batchStatus}`);\r\n }\r\n\r\n /** Get shared internal column metadata. Readers must copy it before exposing mutable metadata to callers.\r\n * @internal\r\n */\r\n public fetchMetadata(): PropertyMetaDataMap {\r\n const reader = this._reader;\r\n if (!reader)\r\n throw new IModelError(DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);\r\n const fetch = () => new PropertyMetaDataMap(reader.getMetadata());\r\n return this._metadataKey === undefined ? fetch() : getCachedMetadata(this._db, this._metadataKey, fetch);\r\n }\r\n}\r\n"]}
@@ -9,8 +9,7 @@ export type SynchronousQueryOptions = Omit<QueryOptions, "suppressLogErrors" | "
9
9
  * Execute ECSQL statements synchronously and read the results one row at a time.
10
10
  *
11
11
  * This is the synchronous counterpart of [[ECSqlReader]] from `@itwin/core-common`.
12
- * It uses [[ECSqlRowExecutor]] directly — each call to [[step]] fetches exactly one row
13
- * with no internal caching, paging, or offset tracking.
12
+ * It uses [[ECSqlRowExecutor]] directly, with read-ahead batches on read-only dbs and live stepping on writable dbs.
14
13
  *
15
14
  * The query results are returned one row at a time. The format of the row is dictated by the
16
15
  * `rowFormat` specified in the `options` parameter of the constructed ECSqlSyncReader object.
@@ -30,12 +29,16 @@ export declare class ECSqlSyncReader extends ECSqlReaderBase implements Iterable
30
29
  readonly query: string;
31
30
  private _currentRow;
32
31
  private _options;
33
- /** Cached native row-adaptor options — built once and reused for every row. */
34
- private _cachedRowOptions;
32
+ private _ownsMetadata;
35
33
  /**
36
34
  * @internal
37
35
  */
38
36
  constructor(_executor: ECSqlRowExecutor, query: string, param?: QueryBinder, options?: SynchronousQueryOptions);
37
+ /** Flatten the bindings of `param` into `[key, type, value, ...]` triples, the form read by native code without
38
+ * per-property lookups. Entries lacking a type or value are skipped, as the object form skipped them.
39
+ * @internal
40
+ */
41
+ private static flattenArgs;
39
42
  /**
40
43
  * @internal
41
44
  */
@@ -44,11 +47,17 @@ export declare class ECSqlSyncReader extends ECSqlReaderBase implements Iterable
44
47
  * Reads a single row from the executor. Returns the row data or `undefined` when the
45
48
  * result set is exhausted.
46
49
  *
47
- * Uses the fast-path `stepNextRow` method on the executor, which calls `stmt.step()`
48
- * and `stmt.toRow()` directly — no intermediate request/response objects are allocated per row.
50
+ * Uses the fast-path `stepNextRow` method on the executor, which serves rows from a read-ahead buffer filled by
51
+ * native code in batches — no intermediate request/response objects are allocated per row.
49
52
  * @internal
50
53
  */
51
54
  private readRow;
55
+ /**
56
+ * Native rows parsed from JSON hold `null` for null cells and base64 strings for blobs. Convert them in place to
57
+ * `undefined` and `Uint8Array`, matching rows whose cells were created directly.
58
+ * @internal
59
+ */
60
+ private static normalizeRow;
52
61
  /**
53
62
  * Steps the cursor with retry logic for transient busy/interrupt conditions.
54
63
  * Returns the row data array, or `undefined` when the result set is exhausted.
@@ -66,11 +75,15 @@ export declare class ECSqlSyncReader extends ECSqlReaderBase implements Iterable
66
75
  * @beta
67
76
  */
68
77
  getMetaData(): QueryPropertyMetaData[];
78
+ /** Copy shared metadata only when it leaves the reader, including through the row proxy.
79
+ * @internal
80
+ */
81
+ protected getMetaDataInternal(): QueryPropertyMetaData[];
69
82
  /**
70
83
  * Step to the next row of the query result.
71
84
  *
72
- * Each call executes exactly one step on the underlying statement — there is no
73
- * internal row caching or offset tracking.
85
+ * On a writable db each step reads the next row from the underlying statement, so changes made inside the callback
86
+ * are visible to later steps. On a read-only db rows are read ahead in small, growing batches.
74
87
  *
75
88
  * @returns `true` if a row can be read from `current`.<br/>
76
89
  * `false` if there are no more rows; i.e., all rows have been stepped through already.
@@ -1 +1 @@
1
- {"version":3,"file":"ECSqlSyncReader.d.ts","sourceRoot":"","sources":["../../src/ECSqlSyncReader.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,OAAO,EAEL,eAAe,EAEf,WAAW,EAAE,YAAY,EAAuB,qBAAqB,EAAkB,aAAa,EACrG,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,YAAY;AACZ,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,YAAY,EAAE,mBAAmB,GAAG,iBAAiB,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,gBAAgB,GAAG,OAAO,CAAC,CAAC;AAEjL;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,eAAgB,SAAQ,eAAgB,YAAW,gBAAgB,CAAC,aAAa,CAAC;IAS1E,OAAO,CAAC,SAAS;aAAoC,KAAK,EAAE,MAAM;IARrF,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,QAAQ,CAA0B;IAC1C,+EAA+E;IAC/E,OAAO,CAAC,iBAAiB,CAAM;IAE/B;;OAEG;gBACwB,SAAS,EAAE,gBAAgB,EAAkB,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,uBAAuB;IA+B7I;;OAEG;cACgB,cAAc,IAAI,GAAG,EAAE;IAM1C;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IAaf;;;;OAIG;IACH,OAAO,CAAC,aAAa;IASrB;;;;;;;;;OASG;IACI,WAAW,IAAI,qBAAqB,EAAE;IAO7C;;;;;;;;;OASG;IACI,IAAI,IAAI,OAAO;IAYtB;;;;;OAKG;IACI,OAAO,IAAI,GAAG,EAAE;IAQvB;;;;;OAKG;IACI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,aAAa,CAAC;IAI3D;;;;;;;OAOG;IACI,IAAI,IAAI,cAAc,CAAC,aAAa,EAAE,GAAG,CAAC;CAalD"}
1
+ {"version":3,"file":"ECSqlSyncReader.d.ts","sourceRoot":"","sources":["../../src/ECSqlSyncReader.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,OAAO,EAEL,eAAe,EAGf,WAAW,EAAE,YAAY,EAAuB,qBAAqB,EAAkB,aAAa,EACrG,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;AAG1E,YAAY;AACZ,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,YAAY,EAAE,mBAAmB,GAAG,iBAAiB,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,GAAG,OAAO,GAAG,gBAAgB,GAAG,OAAO,CAAC,CAAC;AAEjL;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,eAAgB,SAAQ,eAAgB,YAAW,gBAAgB,CAAC,aAAa,CAAC;IAQ1E,OAAO,CAAC,SAAS;aAAoC,KAAK,EAAE,MAAM;IAPrF,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;gBACwB,SAAS,EAAE,gBAAgB,EAAkB,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,uBAAuB;IA4B7I;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAW1B;;OAEG;cACgB,cAAc,IAAI,GAAG,EAAE;IAM1C;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IAcf;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAc3B;;;;OAIG;IACH,OAAO,CAAC,aAAa;IASrB;;;;;;;;;OASG;IACI,WAAW,IAAI,qBAAqB,EAAE;IAQ7C;;OAEG;cACgB,mBAAmB,IAAI,qBAAqB,EAAE;IAQjE;;;;;;;;;OASG;IACI,IAAI,IAAI,OAAO;IAYtB;;;;;OAKG;IACI,OAAO,IAAI,GAAG,EAAE;IAQvB;;;;;OAKG;IACI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,aAAa,CAAC;IAI3D;;;;;;;OAOG;IACI,IAAI,IAAI,cAAc,CAAC,aAAa,EAAE,GAAG,CAAC;CAalD"}