@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.
- package/CHANGELOG.md +1 -1
- package/lib/cjs/ConcurrentQuery.d.ts +4 -0
- package/lib/cjs/ConcurrentQuery.d.ts.map +1 -1
- package/lib/cjs/ConcurrentQuery.js +28 -1
- package/lib/cjs/ConcurrentQuery.js.map +1 -1
- package/lib/cjs/ECDb.d.ts.map +1 -1
- package/lib/cjs/ECDb.js +5 -10
- package/lib/cjs/ECDb.js.map +1 -1
- package/lib/cjs/ECSqlRowExecutor.d.ts +39 -55
- package/lib/cjs/ECSqlRowExecutor.d.ts.map +1 -1
- package/lib/cjs/ECSqlRowExecutor.js +118 -140
- package/lib/cjs/ECSqlRowExecutor.js.map +1 -1
- package/lib/cjs/ECSqlSyncReader.d.ts +21 -8
- package/lib/cjs/ECSqlSyncReader.d.ts.map +1 -1
- package/lib/cjs/ECSqlSyncReader.js +67 -25
- package/lib/cjs/ECSqlSyncReader.js.map +1 -1
- package/lib/cjs/IModelDb.d.ts.map +1 -1
- package/lib/cjs/IModelDb.js +5 -10
- package/lib/cjs/IModelDb.js.map +1 -1
- package/lib/cjs/IModelHost.d.ts +7 -0
- package/lib/cjs/IModelHost.d.ts.map +1 -1
- package/lib/cjs/IModelHost.js +1 -0
- package/lib/cjs/IModelHost.js.map +1 -1
- package/lib/cjs/TxnManager.d.ts.map +1 -1
- package/lib/cjs/TxnManager.js +11 -6
- package/lib/cjs/TxnManager.js.map +1 -1
- package/lib/cjs/internal/ChangesetConflictArgs.d.ts +4 -0
- package/lib/cjs/internal/ChangesetConflictArgs.d.ts.map +1 -1
- package/lib/cjs/internal/ChangesetConflictArgs.js +1 -0
- package/lib/cjs/internal/ChangesetConflictArgs.js.map +1 -1
- package/lib/cjs/internal/QueryMetadataCache.d.ts +8 -0
- package/lib/cjs/internal/QueryMetadataCache.d.ts.map +1 -1
- package/lib/cjs/internal/QueryMetadataCache.js +23 -0
- package/lib/cjs/internal/QueryMetadataCache.js.map +1 -1
- package/lib/esm/ConcurrentQuery.d.ts +4 -0
- package/lib/esm/ConcurrentQuery.d.ts.map +1 -1
- package/lib/esm/ConcurrentQuery.js +29 -2
- package/lib/esm/ConcurrentQuery.js.map +1 -1
- package/lib/esm/ECDb.d.ts.map +1 -1
- package/lib/esm/ECDb.js +6 -11
- package/lib/esm/ECDb.js.map +1 -1
- package/lib/esm/ECSqlRowExecutor.d.ts +39 -55
- package/lib/esm/ECSqlRowExecutor.d.ts.map +1 -1
- package/lib/esm/ECSqlRowExecutor.js +119 -140
- package/lib/esm/ECSqlRowExecutor.js.map +1 -1
- package/lib/esm/ECSqlSyncReader.d.ts +21 -8
- package/lib/esm/ECSqlSyncReader.d.ts.map +1 -1
- package/lib/esm/ECSqlSyncReader.js +68 -26
- package/lib/esm/ECSqlSyncReader.js.map +1 -1
- package/lib/esm/IModelDb.d.ts.map +1 -1
- package/lib/esm/IModelDb.js +6 -11
- package/lib/esm/IModelDb.js.map +1 -1
- package/lib/esm/IModelHost.d.ts +7 -0
- package/lib/esm/IModelHost.d.ts.map +1 -1
- package/lib/esm/IModelHost.js +1 -0
- package/lib/esm/IModelHost.js.map +1 -1
- package/lib/esm/TxnManager.d.ts.map +1 -1
- package/lib/esm/TxnManager.js +12 -7
- package/lib/esm/TxnManager.js.map +1 -1
- package/lib/esm/internal/ChangesetConflictArgs.d.ts +4 -0
- package/lib/esm/internal/ChangesetConflictArgs.d.ts.map +1 -1
- package/lib/esm/internal/ChangesetConflictArgs.js +1 -0
- package/lib/esm/internal/ChangesetConflictArgs.js.map +1 -1
- package/lib/esm/internal/QueryMetadataCache.d.ts +8 -0
- package/lib/esm/internal/QueryMetadataCache.d.ts.map +1 -1
- package/lib/esm/internal/QueryMetadataCache.js +23 -0
- package/lib/esm/internal/QueryMetadataCache.js.map +1 -1
- package/lib/esm/test/IModelHost.test.js +103 -2
- package/lib/esm/test/IModelHost.test.js.map +1 -1
- package/lib/esm/test/ecdb/CTE.test.js +21 -0
- package/lib/esm/test/ecdb/CTE.test.js.map +1 -1
- package/lib/esm/test/ecdb/ConcurrentQuery.test.js +72 -0
- package/lib/esm/test/ecdb/ConcurrentQuery.test.js.map +1 -1
- package/lib/esm/test/ecdb/ConcurrentQueryLoad.test.js +51 -9
- package/lib/esm/test/ecdb/ConcurrentQueryLoad.test.js.map +1 -1
- package/lib/esm/test/ecdb/ECSqlQuery.test.js +2 -1
- package/lib/esm/test/ecdb/ECSqlQuery.test.js.map +1 -1
- package/lib/esm/test/ecdb/ECSqlSyncReader.test.js +338 -84
- package/lib/esm/test/ecdb/ECSqlSyncReader.test.js.map +1 -1
- package/lib/esm/test/ecdb/QueryMetadataCache.test.js +40 -4
- package/lib/esm/test/ecdb/QueryMetadataCache.test.js.map +1 -1
- package/lib/esm/test/ecdb/QueryReaders.test.js +14 -7
- package/lib/esm/test/ecdb/QueryReaders.test.js.map +1 -1
- package/lib/esm/test/standalone/ChangeMerge.test.js +71 -1
- package/lib/esm/test/standalone/ChangeMerge.test.js.map +1 -1
- package/lib/esm/test/standalone/IModelWrite.test.js +6 -2
- package/lib/esm/test/standalone/IModelWrite.test.js.map +1 -1
- package/lib/esm/test/standalone/MergeConflict.test.js +15 -0
- package/lib/esm/test/standalone/MergeConflict.test.js.map +1 -1
- package/package.json +16 -16
|
@@ -4,193 +4,171 @@
|
|
|
4
4
|
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
5
|
*--------------------------------------------------------------------------------------------*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.ECSqlRowExecutor = void 0;
|
|
8
|
-
exports.releaseECSqlStatement = releaseECSqlStatement;
|
|
7
|
+
exports.ECSqlRowExecutor = exports.SyncReaderRowFlags = void 0;
|
|
9
8
|
const core_common_1 = require("@itwin/core-common");
|
|
10
9
|
const core_bentley_1 = require("@itwin/core-bentley");
|
|
11
10
|
const Symbols_1 = require("./internal/Symbols");
|
|
12
11
|
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.";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
12
|
+
/** Column metadata keyed by native statement id and row options. Statement ids change whenever the native cache
|
|
13
|
+
* prepares the ECSql again, so entries never outlive the statement they describe.
|
|
14
|
+
*/
|
|
15
|
+
const maxCachedMetadataEntries = 100;
|
|
16
|
+
/** Rows fetched by the first native batch. Small so single-row and early-exit queries do little extra work. */
|
|
17
|
+
const initialBatchSize = 4;
|
|
18
|
+
/** Each subsequent batch grows by this factor up to [[maxBatchSize]]. */
|
|
19
|
+
const batchGrowthFactor = 4;
|
|
20
|
+
/** Upper bound on rows rendered per native call; bounds memory held by the read-ahead buffer. */
|
|
21
|
+
const maxBatchSize = 128;
|
|
22
|
+
/** Number of distinct row-flag combinations; metadata keys pack the statement id and the row flags into one number. */
|
|
23
|
+
const rowFlagCombinations = 16;
|
|
24
|
+
const metadataCache = new WeakMap();
|
|
25
|
+
/** Receives the first batch and statement id from `prepareECSqlBatchReader`. Read immediately after each call. */
|
|
26
|
+
const prepareOut = [];
|
|
27
|
+
/** Row rendering flags passed to `prepareECSqlBatchReader`.
|
|
22
28
|
* @internal
|
|
23
29
|
*/
|
|
24
|
-
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
var SyncReaderRowFlags;
|
|
31
|
+
(function (SyncReaderRowFlags) {
|
|
32
|
+
SyncReaderRowFlags[SyncReaderRowFlags["None"] = 0] = "None";
|
|
33
|
+
SyncReaderRowFlags[SyncReaderRowFlags["AbbreviateBlobs"] = 1] = "AbbreviateBlobs";
|
|
34
|
+
SyncReaderRowFlags[SyncReaderRowFlags["ClassIdsToClassNames"] = 2] = "ClassIdsToClassNames";
|
|
35
|
+
SyncReaderRowFlags[SyncReaderRowFlags["UseJsName"] = 4] = "UseJsName";
|
|
36
|
+
SyncReaderRowFlags[SyncReaderRowFlags["DoNotConvertClassIdsToClassNamesWhenAliased"] = 8] = "DoNotConvertClassIdsToClassNamesWhenAliased";
|
|
37
|
+
})(SyncReaderRowFlags || (exports.SyncReaderRowFlags = SyncReaderRowFlags = {}));
|
|
38
|
+
function getCachedMetadata(db, key, fetch) {
|
|
39
|
+
let entries = metadataCache.get(db);
|
|
40
|
+
if (!entries) {
|
|
41
|
+
entries = new Map();
|
|
42
|
+
metadataCache.set(db, entries);
|
|
34
43
|
}
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
let properties = entries.get(key);
|
|
45
|
+
if (properties) {
|
|
46
|
+
entries.delete(key);
|
|
37
47
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
catch (disposeError) {
|
|
43
|
-
logStatementCleanupError(loggerCategory, "Failed to dispose an ECSQL statement after it could not be returned to the statement cache.", disposeError);
|
|
44
|
-
}
|
|
45
|
-
logStatementCleanupError(loggerCategory, "Failed to return an ECSQL statement to the statement cache; attempted direct disposal instead.", error);
|
|
48
|
+
else {
|
|
49
|
+
properties = fetch();
|
|
50
|
+
if (entries.size >= maxCachedMetadataEntries)
|
|
51
|
+
entries.delete(entries.keys().next().value);
|
|
46
52
|
}
|
|
53
|
+
entries.set(key, properties);
|
|
54
|
+
return properties;
|
|
47
55
|
}
|
|
48
56
|
// --------------------------------------------------------------------------------------------
|
|
49
57
|
// ECSqlRowExecutor
|
|
50
58
|
// --------------------------------------------------------------------------------------------
|
|
51
59
|
/**
|
|
52
|
-
* Executes ECSql
|
|
53
|
-
*
|
|
60
|
+
* 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
|
|
61
|
+
* read from native code in growing batches to reduce JS/native transitions; on a writable db native code returns one row
|
|
62
|
+
* per call so rows are never read ahead of writes made while iterating. The prepared statement is leased from the native
|
|
63
|
+
* concurrent query statement cache and returned to it when the executor is disposed.
|
|
54
64
|
* @internal
|
|
55
65
|
*/
|
|
56
66
|
class ECSqlRowExecutor {
|
|
57
67
|
_db;
|
|
58
|
-
_stmt;
|
|
59
|
-
_loggerCategory;
|
|
60
68
|
_removeListener;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
_reader;
|
|
70
|
+
/** Key of this statement's metadata in `metadataCache`, or undefined when its metadata must not be cached. */
|
|
71
|
+
_metadataKey;
|
|
72
|
+
_rows = [];
|
|
73
|
+
_rowIndex = 0;
|
|
74
|
+
_batchSize = initialBatchSize;
|
|
75
|
+
/** Status of the last native batch. Anything other than BE_SQLITE_ROW means no further batch is fetched. */
|
|
76
|
+
_batchStatus = core_bentley_1.DbResult.BE_SQLITE_ROW;
|
|
77
|
+
constructor(_db) {
|
|
69
78
|
this._db = _db;
|
|
70
|
-
this.
|
|
71
|
-
this._loggerCategory = _loggerCategory;
|
|
72
|
-
this._removeListener = this._db.onBeforeClose.addListener(() => this.cleanup());
|
|
79
|
+
this._removeListener = this._db.onBeforeClose.addListener(() => this.releaseReader());
|
|
73
80
|
}
|
|
74
81
|
// --------------------------------------------------------------------------------------------
|
|
75
82
|
// Lifecycle
|
|
76
83
|
// --------------------------------------------------------------------------------------------
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
84
|
+
/** Returns the leased statement to the native cache. Safe to call more than once. */
|
|
85
|
+
releaseReader() {
|
|
86
|
+
const reader = this._reader;
|
|
87
|
+
this._reader = undefined;
|
|
88
|
+
this.resetBatch();
|
|
89
|
+
reader?.dispose();
|
|
90
|
+
}
|
|
91
|
+
resetBatch() {
|
|
92
|
+
this._rows = [];
|
|
93
|
+
this._rowIndex = 0;
|
|
94
|
+
this._batchSize = initialBatchSize;
|
|
95
|
+
this._batchStatus = core_bentley_1.DbResult.BE_SQLITE_ROW;
|
|
90
96
|
}
|
|
91
|
-
/**
|
|
97
|
+
/** Returns the leased statement and removes the database-close listener owned by this row executor.
|
|
92
98
|
* @internal
|
|
93
99
|
*/
|
|
94
100
|
[Symbol.dispose]() {
|
|
95
|
-
this._isDisposed = true;
|
|
96
101
|
this._removeListener();
|
|
102
|
+
this.releaseReader();
|
|
97
103
|
}
|
|
98
104
|
// --------------------------------------------------------------------------------------------
|
|
99
105
|
// Core execution
|
|
100
106
|
// --------------------------------------------------------------------------------------------
|
|
101
|
-
/** Prepare the statement and bind parameters in one step.
|
|
102
|
-
*
|
|
107
|
+
/** Prepare the statement and bind parameters in one step. On a read-only db the first batch of rows is read by the
|
|
108
|
+
* same native call.
|
|
103
109
|
* @param query - The ECSql text to prepare.
|
|
104
|
-
* @param args - Optional bind parameters.
|
|
110
|
+
* @param args - Optional bind parameters as flat `[key, type, value, ...]` triples.
|
|
111
|
+
* @param rowFlags - [[SyncReaderRowFlags]] used for every row and for metadata.
|
|
105
112
|
* @throws IModelError on preparation or binding failure.
|
|
106
113
|
* @internal
|
|
107
114
|
*/
|
|
108
|
-
prepareAndBind(query, args) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (args) {
|
|
113
|
-
const bindResult = this.bindValues(args);
|
|
114
|
-
if (!bindResult.isSuccessful)
|
|
115
|
-
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, bindResult.message ?? `Failed to bind values: ${query}`);
|
|
115
|
+
prepareAndBind(query, args, rowFlags) {
|
|
116
|
+
this.releaseReader();
|
|
117
|
+
try {
|
|
118
|
+
this._reader = this._db[Symbols_1._nativeDb].prepareECSqlBatchReader(query, args, rowFlags, this._batchSize, prepareOut);
|
|
116
119
|
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, core_bentley_1.BentleyError.getErrorMessage(error));
|
|
122
|
+
}
|
|
123
|
+
const firstBatch = prepareOut[0];
|
|
124
|
+
// Metadata of a dynamic column (e.g. $->prop) changes as the statement is stepped, so it is never cached.
|
|
125
|
+
this._metadataKey = prepareOut[2] ? undefined : prepareOut[1] * rowFlagCombinations + rowFlags;
|
|
126
|
+
prepareOut.length = 0;
|
|
127
|
+
if (firstBatch !== undefined)
|
|
128
|
+
this.acceptBatch(firstBatch);
|
|
117
129
|
}
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
*/
|
|
130
|
-
stepNextRow(options) {
|
|
131
|
-
if (this._isDisposed || !this._stmt.isPrepared)
|
|
132
|
-
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);
|
|
133
|
-
const stepResult = this._stmt.step();
|
|
134
|
-
if (stepResult === core_bentley_1.DbResult.BE_SQLITE_ROW)
|
|
135
|
-
return this._stmt.toRow(options).data;
|
|
136
|
-
if (stepResult === core_bentley_1.DbResult.BE_SQLITE_DONE)
|
|
137
|
-
return undefined;
|
|
138
|
-
throw new core_common_1.IModelError(stepResult, `Step failed with code ${stepResult}`);
|
|
130
|
+
/** Store a batch returned by native code: a JSON array string, an array of rows, or the final DbResult. */
|
|
131
|
+
acceptBatch(batch) {
|
|
132
|
+
this._batchSize = Math.min(this._batchSize * batchGrowthFactor, maxBatchSize);
|
|
133
|
+
this._rowIndex = 0;
|
|
134
|
+
if (typeof batch === "number") {
|
|
135
|
+
this._rows = [];
|
|
136
|
+
this._batchStatus = batch;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this._rows = typeof batch === "string" ? JSON.parse(batch) : batch;
|
|
140
|
+
}
|
|
139
141
|
}
|
|
140
|
-
/**
|
|
141
|
-
*
|
|
142
|
-
* @
|
|
143
|
-
*
|
|
142
|
+
/** Return the next row data array, or `undefined` when the result set is exhausted. Rows are served from the
|
|
143
|
+
* read-ahead buffer; a new native batch is fetched only when the buffer is empty.
|
|
144
|
+
* @throws IModelError on step failure or when the statement is no longer available. A failure reported by a batch is
|
|
145
|
+
* thrown after the rows stepped before it have been returned.
|
|
144
146
|
* @internal
|
|
145
147
|
*/
|
|
146
|
-
|
|
147
|
-
if (this.
|
|
148
|
+
stepNextRow() {
|
|
149
|
+
if (this._rowIndex < this._rows.length)
|
|
150
|
+
return this._rows[this._rowIndex++];
|
|
151
|
+
const reader = this._reader;
|
|
152
|
+
if (!reader)
|
|
148
153
|
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// --------------------------------------------------------------------------------------------
|
|
154
|
-
/** Prepares the ECSql statement when the caller did not supply one from its cache.
|
|
155
|
-
* @param ecsql - The ECSql text to prepare.
|
|
156
|
-
* @returns An `OperationResult` indicating success or failure.
|
|
157
|
-
* @internal
|
|
158
|
-
*/
|
|
159
|
-
prepareStmt(ecsql) {
|
|
160
|
-
if (this._stmt.isPrepared)
|
|
161
|
-
return { isSuccessful: true };
|
|
162
|
-
try {
|
|
163
|
-
this._stmt.prepare(this._db[Symbols_1._nativeDb], ecsql);
|
|
164
|
-
return { isSuccessful: true };
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
this._canCacheStatement = false;
|
|
168
|
-
try {
|
|
169
|
-
this._stmt[Symbol.dispose]();
|
|
170
|
-
}
|
|
171
|
-
catch (disposeError) {
|
|
172
|
-
logStatementCleanupError(this._loggerCategory, "Failed to dispose an ECSQL statement after preparation failed.", disposeError);
|
|
173
|
-
}
|
|
174
|
-
return { isSuccessful: false, message: error.message };
|
|
154
|
+
if (this._batchStatus === core_bentley_1.DbResult.BE_SQLITE_ROW) {
|
|
155
|
+
this.acceptBatch(reader.stepBatch(this._batchSize));
|
|
156
|
+
if (this._rows.length > 0)
|
|
157
|
+
return this._rows[this._rowIndex++];
|
|
175
158
|
}
|
|
159
|
+
if (this._batchStatus === core_bentley_1.DbResult.BE_SQLITE_DONE)
|
|
160
|
+
return undefined;
|
|
161
|
+
throw new core_common_1.IModelError(this._batchStatus, `Step failed with code ${this._batchStatus}`);
|
|
176
162
|
}
|
|
177
|
-
/**
|
|
178
|
-
* @param args - The parameter object to bind, or `undefined` when no parameters are needed.
|
|
179
|
-
* @returns An `OperationResult` indicating success or failure.
|
|
163
|
+
/** Get shared internal column metadata. Readers must copy it before exposing mutable metadata to callers.
|
|
180
164
|
* @internal
|
|
181
165
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this._stmt.bindParams(args);
|
|
189
|
-
return { isSuccessful: true };
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
return { isSuccessful: false, message: error.message };
|
|
193
|
-
}
|
|
166
|
+
fetchMetadata() {
|
|
167
|
+
const reader = this._reader;
|
|
168
|
+
if (!reader)
|
|
169
|
+
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, statementNotPreparedMessage);
|
|
170
|
+
const fetch = () => new core_common_1.PropertyMetaDataMap(reader.getMetadata());
|
|
171
|
+
return this._metadataKey === undefined ? fetch() : getCachedMetadata(this._db, this._metadataKey, fetch);
|
|
194
172
|
}
|
|
195
173
|
}
|
|
196
174
|
exports.ECSqlRowExecutor = ECSqlRowExecutor;
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
48
|
-
*
|
|
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
|
-
*
|
|
73
|
-
*
|
|
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,
|
|
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"}
|
|
@@ -9,13 +9,13 @@ exports.ECSqlSyncReader = void 0;
|
|
|
9
9
|
* @module ECSQL
|
|
10
10
|
*/
|
|
11
11
|
const core_common_1 = require("@itwin/core-common");
|
|
12
|
+
const ECSqlRowExecutor_1 = require("./ECSqlRowExecutor");
|
|
12
13
|
const core_bentley_1 = require("@itwin/core-bentley");
|
|
13
14
|
/**
|
|
14
15
|
* Execute ECSQL statements synchronously and read the results one row at a time.
|
|
15
16
|
*
|
|
16
17
|
* This is the synchronous counterpart of [[ECSqlReader]] from `@itwin/core-common`.
|
|
17
|
-
* It uses [[ECSqlRowExecutor]] directly
|
|
18
|
-
* with no internal caching, paging, or offset tracking.
|
|
18
|
+
* It uses [[ECSqlRowExecutor]] directly, with read-ahead batches on read-only dbs and live stepping on writable dbs.
|
|
19
19
|
*
|
|
20
20
|
* The query results are returned one row at a time. The format of the row is dictated by the
|
|
21
21
|
* `rowFormat` specified in the `options` parameter of the constructed ECSqlSyncReader object.
|
|
@@ -35,8 +35,7 @@ class ECSqlSyncReader extends core_common_1.ECSqlReaderBase {
|
|
|
35
35
|
query;
|
|
36
36
|
_currentRow;
|
|
37
37
|
_options;
|
|
38
|
-
|
|
39
|
-
_cachedRowOptions;
|
|
38
|
+
_ownsMetadata = false;
|
|
40
39
|
/**
|
|
41
40
|
* @internal
|
|
42
41
|
*/
|
|
@@ -51,22 +50,33 @@ class ECSqlSyncReader extends core_common_1.ECSqlReaderBase {
|
|
|
51
50
|
throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "expecting non-empty ecsql statement");
|
|
52
51
|
}
|
|
53
52
|
this._options = resolvedOptions;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Build and cache the native row-adaptor options — reused for every step call.
|
|
53
|
+
// Row options are passed to native code once, as flags, and used for every row.
|
|
54
|
+
let rowFlags = ECSqlRowExecutor_1.SyncReaderRowFlags.DoNotConvertClassIdsToClassNamesWhenAliased;
|
|
55
|
+
if (this._options.abbreviateBlobs)
|
|
56
|
+
rowFlags |= ECSqlRowExecutor_1.SyncReaderRowFlags.AbbreviateBlobs;
|
|
59
57
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
doNotConvertClassIdsToClassNamesWhenAliased: true,
|
|
67
|
-
};
|
|
58
|
+
if (this._options.convertClassIdsToClassNames)
|
|
59
|
+
rowFlags |= ECSqlRowExecutor_1.SyncReaderRowFlags.ClassIdsToClassNames;
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
61
|
+
if (this._options.rowFormat === core_common_1.QueryRowFormat.UseJsPropertyNames)
|
|
62
|
+
rowFlags |= ECSqlRowExecutor_1.SyncReaderRowFlags.UseJsName;
|
|
63
|
+
this._executor.prepareAndBind(this.query, param ? ECSqlSyncReader.flattenArgs(param) : undefined, rowFlags);
|
|
68
64
|
this._currentRow = undefined;
|
|
69
65
|
}
|
|
66
|
+
/** Flatten the bindings of `param` into `[key, type, value, ...]` triples, the form read by native code without
|
|
67
|
+
* per-property lookups. Entries lacking a type or value are skipped, as the object form skipped them.
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
static flattenArgs(param) {
|
|
71
|
+
const serialized = param.serialize();
|
|
72
|
+
let args;
|
|
73
|
+
for (const key of Object.keys(serialized)) {
|
|
74
|
+
const arg = serialized[key];
|
|
75
|
+
if (typeof arg === "object" && arg !== null && "type" in arg && "value" in arg)
|
|
76
|
+
(args ??= []).push(key, arg.type, arg.value);
|
|
77
|
+
}
|
|
78
|
+
return args;
|
|
79
|
+
}
|
|
70
80
|
/**
|
|
71
81
|
* @internal
|
|
72
82
|
*/
|
|
@@ -79,8 +89,8 @@ class ECSqlSyncReader extends core_common_1.ECSqlReaderBase {
|
|
|
79
89
|
* Reads a single row from the executor. Returns the row data or `undefined` when the
|
|
80
90
|
* result set is exhausted.
|
|
81
91
|
*
|
|
82
|
-
* Uses the fast-path `stepNextRow` method on the executor, which
|
|
83
|
-
*
|
|
92
|
+
* Uses the fast-path `stepNextRow` method on the executor, which serves rows from a read-ahead buffer filled by
|
|
93
|
+
* native code in batches — no intermediate request/response objects are allocated per row.
|
|
84
94
|
* @internal
|
|
85
95
|
*/
|
|
86
96
|
readRow() {
|
|
@@ -89,19 +99,40 @@ class ECSqlSyncReader extends core_common_1.ECSqlReaderBase {
|
|
|
89
99
|
}
|
|
90
100
|
// Fetch metadata once on the first call.
|
|
91
101
|
if (this._props.length === 0) {
|
|
92
|
-
this._props = this._executor.fetchMetadata(
|
|
102
|
+
this._props = this._executor.fetchMetadata();
|
|
103
|
+
this._ownsMetadata = false;
|
|
93
104
|
}
|
|
94
105
|
return this.stepWithRetry();
|
|
95
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Native rows parsed from JSON hold `null` for null cells and base64 strings for blobs. Convert them in place to
|
|
109
|
+
* `undefined` and `Uint8Array`, matching rows whose cells were created directly.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
static normalizeRow(row) {
|
|
113
|
+
for (let i = 0; i < row.length; ++i) {
|
|
114
|
+
const val = row[i];
|
|
115
|
+
if (val === null) {
|
|
116
|
+
row[i] = undefined;
|
|
117
|
+
}
|
|
118
|
+
else if (typeof val === "string") {
|
|
119
|
+
if (core_common_1.Base64EncodedString.hasPrefix(val))
|
|
120
|
+
row[i] = core_common_1.Base64EncodedString.toUint8Array(val);
|
|
121
|
+
}
|
|
122
|
+
else if (typeof val === "object" && !(val instanceof Uint8Array)) {
|
|
123
|
+
ECSqlSyncReader.replaceBase64WithUint8Array(val);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
96
127
|
/**
|
|
97
128
|
* Steps the cursor with retry logic for transient busy/interrupt conditions.
|
|
98
129
|
* Returns the row data array, or `undefined` when the result set is exhausted.
|
|
99
130
|
* @internal
|
|
100
131
|
*/
|
|
101
132
|
stepWithRetry() {
|
|
102
|
-
const result = this._executor.stepNextRow(
|
|
133
|
+
const result = this._executor.stepNextRow();
|
|
103
134
|
if (result) {
|
|
104
|
-
ECSqlSyncReader.
|
|
135
|
+
ECSqlSyncReader.normalizeRow(result);
|
|
105
136
|
return result;
|
|
106
137
|
}
|
|
107
138
|
return undefined; // undefined (done) → undefined
|
|
@@ -118,15 +149,26 @@ class ECSqlSyncReader extends core_common_1.ECSqlReaderBase {
|
|
|
118
149
|
*/
|
|
119
150
|
getMetaData() {
|
|
120
151
|
if (this._props.length === 0) {
|
|
121
|
-
this._props = this._executor.fetchMetadata(
|
|
152
|
+
this._props = this._executor.fetchMetadata();
|
|
153
|
+
this._ownsMetadata = false;
|
|
154
|
+
}
|
|
155
|
+
return this.getMetaDataInternal();
|
|
156
|
+
}
|
|
157
|
+
/** Copy shared metadata only when it leaves the reader, including through the row proxy.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
getMetaDataInternal() {
|
|
161
|
+
if (!this._ownsMetadata) {
|
|
162
|
+
this._props = new core_common_1.PropertyMetaDataMap(this._props.properties.map((property) => ({ ...property })));
|
|
163
|
+
this._ownsMetadata = true;
|
|
122
164
|
}
|
|
123
165
|
return this._props.properties;
|
|
124
166
|
}
|
|
125
167
|
/**
|
|
126
168
|
* Step to the next row of the query result.
|
|
127
169
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
170
|
+
* On a writable db each step reads the next row from the underlying statement, so changes made inside the callback
|
|
171
|
+
* are visible to later steps. On a read-only db rows are read ahead in small, growing batches.
|
|
130
172
|
*
|
|
131
173
|
* @returns `true` if a row can be read from `current`.<br/>
|
|
132
174
|
* `false` if there are no more rows; i.e., all rows have been stepped through already.
|