From 9ff3334ee62892756ba4201a8aff6208a88d42f7 Mon Sep 17 00:00:00 2001 From: Colin Kerr <33028649+ColinKerr@users.noreply.github.com> Date: Fri, 1 Nov 2024 14:55:35 -0400 Subject: [PATCH] First cut deprecation --- core/backend/src/ChangeSummaryManager.ts | 12 +- core/backend/src/ECDb.ts | 82 +++++- core/backend/src/ECSqlStatement.ts | 247 +++++++++++++++++- core/backend/src/IModelDb.ts | 2 + core/backend/src/test/ecdb/ECDb.test.ts | 4 +- .../backend/src/test/ecdb/ECSqlReader.test.ts | 4 +- .../src/test/ecdb/ECSqlStatement.test.ts | 126 ++++----- core/common/src/ConcurrentQuery.ts | 2 + 8 files changed, 403 insertions(+), 76 deletions(-) diff --git a/core/backend/src/ChangeSummaryManager.ts b/core/backend/src/ChangeSummaryManager.ts index fb8cce70ca97..a4c7b8b41af0 100644 --- a/core/backend/src/ChangeSummaryManager.ts +++ b/core/backend/src/ChangeSummaryManager.ts @@ -12,7 +12,7 @@ import * as path from "path"; import { BackendLoggerCategory } from "./BackendLoggerCategory"; import { BriefcaseManager } from "./BriefcaseManager"; import { ECDb, ECDbOpenMode } from "./ECDb"; -import { ECSqlStatement } from "./ECSqlStatement"; +import { ECSqlInsertResult, ECSqlStatement, ECSqlWriteStatement } from "./ECSqlStatement"; import { BriefcaseDb, IModelDb, TokenArg } from "./IModelDb"; import { IModelHost, KnownLocations } from "./IModelHost"; import { IModelJsFs } from "./IModelJsFs"; @@ -195,8 +195,8 @@ export class ChangeSummaryManager { } private static addExtendedInfos(changesFile: ECDb, changeSummaryId: Id64String, changesetWsgId: GuidString, changesetParentWsgId?: GuidString, description?: string, changesetPushDate?: string, changeSetUserCreated?: GuidString): void { - changesFile.withPreparedStatement("INSERT INTO imodelchange.ChangeSet(Summary.Id,WsgId,ParentWsgId,Description,PushDate,UserCreated) VALUES(?,?,?,?,?,?)", - (stmt: ECSqlStatement) => { + changesFile.withCachedWriteStatement("INSERT INTO imodelchange.ChangeSet(Summary.Id,WsgId,ParentWsgId,Description,PushDate,UserCreated) VALUES(?,?,?,?,?,?)", + (stmt: ECSqlWriteStatement) => { stmt.bindId(1, changeSummaryId); stmt.bindString(2, changesetWsgId); if (changesetParentWsgId) @@ -211,9 +211,9 @@ export class ChangeSummaryManager { if (changeSetUserCreated) stmt.bindString(6, changeSetUserCreated); - const r: DbResult = stmt.step(); - if (r !== DbResult.BE_SQLITE_DONE) - throw new IModelError(r, `Failed to add changeset information to extracted change summary ${changeSummaryId}`); + const r: ECSqlInsertResult = stmt.stepForInsert(); + if (r.status !== DbResult.BE_SQLITE_DONE) + throw new IModelError(r.status, `Failed to add changeset information to extracted change summary ${changeSummaryId}`); }); } diff --git a/core/backend/src/ECDb.ts b/core/backend/src/ECDb.ts index 7299b2dd2523..c42b033dbfc6 100644 --- a/core/backend/src/ECDb.ts +++ b/core/backend/src/ECDb.ts @@ -10,7 +10,7 @@ import { IModelJsNative } from "@bentley/imodeljs-native"; import { DbQueryRequest, ECSchemaProps, ECSqlReader, IModelError, QueryBinder, QueryOptions, QueryOptionsBuilder } from "@itwin/core-common"; import { BackendLoggerCategory } from "./BackendLoggerCategory"; import { ConcurrentQuery } from "./ConcurrentQuery"; -import { ECSqlStatement } from "./ECSqlStatement"; +import { ECSqlStatement, ECSqlWriteStatement } from "./ECSqlStatement"; import { IModelNative } from "./internal/NativePlatform"; import { SqliteStatement, StatementCache } from "./SqliteStatement"; import { _nativeDb } from "./internal/Symbols"; @@ -32,6 +32,7 @@ export enum ECDbOpenMode { */ export class ECDb implements IDisposable { private _nativeDb?: IModelJsNative.ECDb; + // eslint-disable-next-line @typescript-eslint/no-deprecated private readonly _statementCache = new StatementCache(); private _sqliteStatementCache = new StatementCache(); @@ -146,6 +147,76 @@ export class ECDb implements IDisposable { return this[_nativeDb].getSchemaProps(name); } + /** + * Use a prepared ECSQL statement, potentially from the statement cache. If the requested statement doesn't exist + * in the statement cache, a new statement is prepared. After the callback completes, the statement is reset and saved + * in the statement cache so it can be reused in the future. Use this method for ECSQL statements that will be + * reused often and are expensive to prepare. The statement cache holds the most recently used statements, discarding + * the oldest statements as it fills. For statements you don't intend to reuse, instead use [[withStatement]]. + * @param sql The SQLite SQL statement to execute + * @param callback the callback to invoke on the prepared statement + * @param logErrors Determines if error will be logged if statement fail to prepare + * @returns the value returned by `callback`. + * @see [[withWriteStatement]] + * @beta + */ + public withCachedWriteStatement(ecsql: string, callback: (stmt: ECSqlWriteStatement) => T, logErrors = true): T { + // eslint-disable-next-line @typescript-eslint/no-deprecated + const stmt = this._statementCache.findAndRemove(ecsql) ?? this.prepareStatement(ecsql, logErrors); + const release = () => this._statementCache.addOrDispose(stmt); + try { + const val = callback(new ECSqlWriteStatement(stmt)); + if (val instanceof Promise) { + val.then(release, release); + } else { + release(); + } + return val; + } catch (err) { + release(); + throw err; + } + } + + /** + * Prepared and execute a callback on an ECSQL statement. After the callback completes the statement is disposed. + * Use this method for ECSQL statements are either not expected to be reused, or are not expensive to prepare. + * For statements that will be reused often, instead use [[withPreparedStatement]]. + * @param sql The SQLite SQL statement to execute + * @param callback the callback to invoke on the prepared statement + * @param logErrors Determines if error will be logged if statement fail to prepare + * @returns the value returned by `callback`. + * @see [[withCachedWriteStatement]] + * @beta + */ + public withWriteStatement(ecsql: string, callback: (stmt: ECSqlWriteStatement) => T, logErrors = true): T { + const stmt = this.prepareWriteStatement(ecsql, logErrors); + const release = () => stmt.dispose(); + try { + const val = callback(stmt); + if (val instanceof Promise) { + val.then(release, release); + } else { + release(); + } + return val; + } catch (err) { + release(); + throw err; + } + } + + /** Prepare an ECSQL statement. + * @param ecsql The ECSQL statement to prepare + * @param logErrors Determines if error will be logged if statement fail to prepare + * @throws [IModelError]($common) if there is a problem preparing the statement. + * @beta + */ + public prepareWriteStatement(ecsql: string, logErrors = true): ECSqlWriteStatement { + // eslint-disable-next-line @typescript-eslint/no-deprecated + return new ECSqlWriteStatement(this.prepareStatement(ecsql, logErrors)); + } + /** * Use a prepared ECSQL statement, potentially from the statement cache. If the requested statement doesn't exist * in the statement cache, a new statement is prepared. After the callback completes, the statement is reset and saved @@ -158,8 +229,11 @@ export class ECDb implements IDisposable { * @returns the value returned by `callback`. * @see [[withStatement]] * @public + * @deprecated in 4.10. Use [[createQueryReader]] for SELECT statements and [[withCachedWriteStatement]] for INSERT/UPDATE/DELETE instead. */ + // eslint-disable-next-line @typescript-eslint/no-deprecated public withPreparedStatement(ecsql: string, callback: (stmt: ECSqlStatement) => T, logErrors = true): T { + // eslint-disable-next-line @typescript-eslint/no-deprecated const stmt = this._statementCache.findAndRemove(ecsql) ?? this.prepareStatement(ecsql, logErrors); const release = () => this._statementCache.addOrDispose(stmt); try { @@ -186,8 +260,11 @@ export class ECDb implements IDisposable { * @returns the value returned by `callback`. * @see [[withPreparedStatement]] * @public + * @deprecated in 4.10. Use [[createQueryReader]] for SELECT statements and [[withWriteStatement]] for INSERT/UPDATE/DELETE instead. */ + // eslint-disable-next-line @typescript-eslint/no-deprecated public withStatement(ecsql: string, callback: (stmt: ECSqlStatement) => T, logErrors = true): T { + // eslint-disable-next-line @typescript-eslint/no-deprecated const stmt = this.prepareStatement(ecsql, logErrors); const release = () => stmt.dispose(); try { @@ -208,8 +285,11 @@ export class ECDb implements IDisposable { * @param ecsql The ECSQL statement to prepare * @param logErrors Determines if error will be logged if statement fail to prepare * @throws [IModelError]($common) if there is a problem preparing the statement. + * @deprecated in 4.10. Use [[prepareWriteStatement]] when preparing an INSERT/UPDATE/DELETE statement or [[createQueryReader]] to execute a SELECT statement. */ + // eslint-disable-next-line @typescript-eslint/no-deprecated public prepareStatement(ecsql: string, logErrors = true): ECSqlStatement { + // eslint-disable-next-line @typescript-eslint/no-deprecated const stmt = new ECSqlStatement(); stmt.prepare(this[_nativeDb], ecsql, logErrors); return stmt; diff --git a/core/backend/src/ECSqlStatement.ts b/core/backend/src/ECSqlStatement.ts index 24d14db4ec7c..ff194a5fbe56 100644 --- a/core/backend/src/ECSqlStatement.ts +++ b/core/backend/src/ECSqlStatement.ts @@ -63,6 +63,8 @@ export interface ECSqlRowArg { * - [Executing ECSQL]($docs/learning/backend/ExecutingECSQL) provides more background on ECSQL and an introduction on how to execute ECSQL with the iTwin.js API. * - [Code Examples]($docs/learning/backend/ECSQLCodeExamples) illustrate the use of the iTwin.js API for executing and working with ECSQL * @public + * @deprecated in 4.10. Use [IModelDb.createQueryReader]($backend) or [ECDb.createQueryReader]($backend) to query. + * For ECDb, use [ECDb.withCachedWriteStatement]($backend) or [ECDb.withWriteStatement]($backend) to Insert/Update/Delete. */ export class ECSqlStatement implements IterableIterator, IDisposable { private _stmt: IModelJsNative.ECSqlStatement | undefined; @@ -414,6 +416,236 @@ export class ECSqlStatement implements IterableIterator, IDisposable { } } +/** Executes ECSQL INSERT/UPDATE/DELETE statements. + * + * A statement must be prepared before it can be executed, and it must be released when no longer needed. + * See [ECDb.withCachedWriteStatement]($backend) for a convenient and + * reliable way to prepare, execute, and then release a statement. + * + * A statement may contain parameters that must be filled in before use by the **bind** methods. + * + * Once prepared (and parameters are bound, if any), the statement is executed by calling [ECSqlStatement.stepForInsert]($backend). + * + * > Preparing a statement can be time-consuming. The best way to reduce the effect of this overhead is to cache and reuse prepared + * > statements. A cached prepared statement may be used in different places in an app, as long as the statement is general enough. + * > The key to making this strategy work is to phrase a statement in a general way and use placeholders to represent parameters that will vary on each use. + * + * See also + * - [Executing ECSQL]($docs/learning/backend/ExecutingECSQL) provides more background on ECSQL and an introduction on how to execute ECSQL with the iTwin.js API. + * - [Code Examples]($docs/learning/backend/ECSQLCodeExamples) illustrate the use of the iTwin.js API for executing and working with ECSQL + * @public + */ +export class ECSqlWriteStatement implements IDisposable { + // eslint-disable-next-line @typescript-eslint/no-deprecated + private _stmt: ECSqlStatement; + + public constructor(stmt?: ECSqlStatement) { + if (stmt) + this._stmt = stmt; + else + this._stmt = new ECSqlStatement(); + } + + public get sql() { return this._stmt.sql; } + + /** Check if this statement has been prepared successfully or not */ + public get isPrepared(): boolean { return this._stmt.isPrepared; } + + /** Get the underlying ECSqlStatement. Needed until we remove ECSqlStatement. + * @param + * @internal + */ + public get stmt(): ECSqlStatement { return this._stmt; } + + /** Prepare this statement prior to first use. + * @param db The ECDb to prepare the statement against + * @param ecsql The ECSQL statement string to prepare + * @param logErrors Determine if errors are logged or not + * @throws [IModelError]($common) if the ECSQL statement cannot be prepared. Normally, prepare fails due to ECSQL syntax errors or references to tables or properties that do not exist. + * The error.message property will provide details. + * @internal + */ + public prepare(db: IModelJsNative.ECDb, ecsql: string, logErrors = true): void { + this._stmt.prepare(db, ecsql, logErrors); + } + + /** Prepare this statement prior to first use. + * @param db The DgnDb or ECDb to prepare the statement against + * @param ecsql The ECSQL statement string to prepare + * @param logErrors Determine if errors are logged or not, its set to false by default for tryPrepare() + * @returns An object with a `status` member equal to [DbResult.BE_SQLITE_OK]($bentley) on success. Upon error, the `message` member will provide details. + * @internal + */ + public tryPrepare(db: IModelJsNative.DgnDb | IModelJsNative.ECDb, ecsql: string, logErrors = false): { status: DbResult, message: string } { + return this.tryPrepare(db, ecsql, logErrors); + } + + /** Reset this statement so that the next call to step will return the first row, if any. */ + public reset(): void { + this._stmt.reset(); + } + + /** Get the Native SQL statement + * @internal + */ + public getNativeSql(): string { + return this._stmt.getNativeSql(); + } + + /** Call this function when finished with this statement. This releases the native resources held by the statement. + * + * > Do not call this method directly on a statement that is being managed by a statement cache. + */ + public dispose(): void { + this._stmt.dispose(); // free native statement + } + + /** Binds the specified value to the specified ECSQL parameter. + * The section "[iTwin.js Types used in ECSQL Parameter Bindings]($docs/learning/ECSQLParameterTypes)" describes the + * iTwin.js types to be used for the different ECSQL parameter types. + * @param parameter Index (1-based) or name of the parameter + */ + public bindValue(parameter: number | string, val: any): void { this.getBinder(parameter).bind(val); } + + /** Binds null to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + */ + public bindNull(parameter: number | string): void { this.getBinder(parameter).bindNull(); } + + /** Binds a BLOB value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param BLOB value as either a Uint8Array, ArrayBuffer or a Base64 string + */ + public bindBlob(parameter: number | string, blob: string | Uint8Array | ArrayBuffer | SharedArrayBuffer): void { this.getBinder(parameter).bindBlob(blob); } + + /** Binds a boolean value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Boolean value + */ + public bindBoolean(parameter: number | string, val: boolean): void { this.getBinder(parameter).bindBoolean(val); } + + /** Binds a DateTime value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param isoDateTimeString DateTime value as ISO8601 string + */ + public bindDateTime(parameter: number | string, isoDateTimeString: string): void { this.getBinder(parameter).bindDateTime(isoDateTimeString); } + + /** Binds a double value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Double value + */ + public bindDouble(parameter: number | string, val: number): void { this.getBinder(parameter).bindDouble(val); } + + /** Binds an GUID value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val GUID value + */ + public bindGuid(parameter: number | string, val: GuidString): void { this.getBinder(parameter).bindGuid(val); } + + /** Binds an Id value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Id value + */ + public bindId(parameter: number | string, val: Id64String): void { this.getBinder(parameter).bindId(val); } + + /** Binds an integer value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Integer value as number, decimal string or hexadecimal string. + */ + public bindInteger(parameter: number | string, val: number | string): void { this.getBinder(parameter).bindInteger(val); } + + /** Binds an Point2d value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Point2d value + */ + public bindPoint2d(parameter: number | string, val: XAndY): void { this.getBinder(parameter).bindPoint2d(val); } + + /** Binds an Point3d value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Point3d value + */ + public bindPoint3d(parameter: number | string, val: XYAndZ): void { this.getBinder(parameter).bindPoint3d(val); } + + /** Binds a Range3d as a blob to the specified ECSQL parameter + * @param parameter Index(1-based) or name of the parameter + * @param val Range3d value + */ + public bindRange3d(parameter: number | string, val: LowAndHighXYZ): void { this.getBinder(parameter).bindRange3d(val); } + + /** Binds an string to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val String value + */ + public bindString(parameter: number | string, val: string): void { this.getBinder(parameter).bindString(val); } + + /** Binds a navigation property value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Navigation property value + */ + public bindNavigation(parameter: number | string, val: NavigationBindingValue): void { this.getBinder(parameter).bindNavigation(val); } + + /** Binds a struct property value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Struct value. The struct value is an object composed of pairs of a struct member property name and its value + * (of one of the supported types) + */ + public bindStruct(parameter: number | string, val: object): void { this.getBinder(parameter).bindStruct(val); } + + /** Binds an array value to the specified ECSQL parameter. + * @param parameter Index (1-based) or name of the parameter + * @param val Array value. The array value is an array of values of the supported types + */ + public bindArray(parameter: number | string, val: any[]): void { this.getBinder(parameter).bindArray(val); } + + public bindIdSet(parameter: number | string, val: Id64String[]): void { this.getBinder(parameter).bindIdSet(val); } + /** + * Gets a binder to bind a value for an ECSQL parameter + * > This is the most low-level API to bind a value to a specific parameter. Alternatively you can use the ECSqlStatement.bindXX methods + * > or [ECSqlStatement.bindValues]($backend). + * @param parameter Index (1-based) or name of the parameter + */ + public getBinder(parameter: string | number): ECSqlBinder { + return this._stmt.getBinder(parameter); + } + + /** Bind values to all parameters in the statement. + * @param values The values to bind to the parameters. + * Pass an *array* of values if the parameters are *positional*. + * Pass an *object of the values keyed on the parameter name* for *named parameters*. + * The values in either the array or object must match the respective types of the parameter. + * + * The section "[iTwin.js Types used in ECSQL Parameter Bindings]($docs/learning/ECSQLParameterTypes)" describes the + * iTwin.js types to be used for the different ECSQL parameter types. + * + * See also these [Code Samples]($docs/learning/backend/ECSQLCodeExamples#binding-to-all-parameters-at-once) + */ + public bindValues(values: any[] | object): void { + this._stmt.bindValues(values); + } + + /** Clear any bindings that were previously set on this statement. + * @throws [IModelError]($common) in case of errors + */ + public clearBindings(): void { + this._stmt.clearBindings(); + } + + /** Step this INSERT statement and returns status and the ECInstanceId of the newly + * created instance. + * + * > Insert statements can be used with ECDb only, not with IModelDb. + * + * @returns Returns the generated ECInstanceId in case of success and the status of the step + * call. In case of error, the respective error code is returned. + */ + public stepForInsert(): ECSqlInsertResult { + return this._stmt.stepForInsert(); + } + + /** Get the query result's column count (only for ECSQL SELECT statements). */ + public getColumnCount(): number { return this._stmt.getColumnCount(); } +} + /** Binds a value to an ECSQL parameter. * * See also: @@ -596,6 +828,7 @@ export class ECSqlBinder { * - [[ECSqlStatement.getValue]] * - [Code Samples]($docs/learning/backend/ECSQLCodeExamples#working-with-the-query-result) * @public + * @deprecated in 4.10. Use [IModelDb.createQueryReader]($backend) or [ECDb.createQueryReader]($backend) instead. */ export interface ECEnumValue { schema: string; @@ -611,7 +844,8 @@ export interface ECEnumValue { * - [ECSqlStatement.getValue]($backend) * - [Code Samples]($docs/learning/backend/ECSQLCodeExamples#working-with-the-query-result) * @public - */ + * @deprecated in 4.10. Use [IModelDb.createQueryReader]($backend) or [ECDb.createQueryReader]($backend) instead. +*/ export class ECSqlValue { private _val: IModelJsNative.ECSqlValue; @@ -686,7 +920,8 @@ export class ECSqlValue { /** Iterator over members of a struct [ECSqlValue]($backend) or the elements of an array [ECSqlValue]($backend). * See [ECSqlValue.getStructIterator]($backend) or [ECSqlValue.getArrayIterator]($backend). * @public - */ + * @deprecated in 4.10. Use [IModelDb.createQueryReader]($backend) or [ECDb.createQueryReader]($backend) instead. +*/ export class ECSqlValueIterator implements IterableIterator { private _it: IModelJsNative.ECSqlValueIterator; @@ -706,6 +941,7 @@ export class ECSqlValueIterator implements IterableIterator { /** Information about an ECSQL column in an ECSQL query result. * See [ECSqlValue.columnInfo]($backend), [ECSqlStatement.getValue]($backend), [ECSqlStatement]($backend) * @public + * @deprecated in 4.10. Use [IModelDb.createQueryReader]($backend) or [ECDb.createQueryReader]($backend) instead. */ export interface ECSqlColumnInfo { /** Gets the data type of the column. @@ -886,6 +1122,7 @@ class ECSqlBindingHelper { } class ECSqlValueHelper { + // eslint-disable-next-line @typescript-eslint/no-deprecated public static getValue(ecsqlValue: ECSqlValue): any { if (ecsqlValue.isNull) return undefined; @@ -907,6 +1144,7 @@ class ECSqlValueHelper { } } + // eslint-disable-next-line @typescript-eslint/no-deprecated public static getStruct(ecsqlValue: ECSqlValue): any { if (ecsqlValue.isNull) return undefined; @@ -928,6 +1166,7 @@ class ECSqlValueHelper { return structVal; } + // eslint-disable-next-line @typescript-eslint/no-deprecated public static getArray(ecsqlValue: ECSqlValue): any[] { const arrayVal: any[] = []; const it = ecsqlValue.getArrayIterator(); @@ -941,10 +1180,12 @@ class ECSqlValueHelper { return arrayVal; } + // eslint-disable-next-line @typescript-eslint/no-deprecated private static getPrimitiveValue(ecsqlValue: ECSqlValue): any { if (ecsqlValue.isNull) return undefined; + // eslint-disable-next-line @typescript-eslint/no-deprecated const colInfo: ECSqlColumnInfo = ecsqlValue.columnInfo; switch (colInfo.getType()) { case ECSqlValueType.Blob: @@ -983,7 +1224,9 @@ class ECSqlValueHelper { if (!tableSpace) tableSpace = "main"; + // eslint-disable-next-line @typescript-eslint/no-deprecated return ecdb.withPreparedStatement(`SELECT s.Name, c.Name FROM [${tableSpace}].meta.ECSchemaDef s, JOIN [${tableSpace}].meta.ECClassDef c ON s.ECInstanceId=c.SchemaId WHERE c.ECInstanceId=?`, + // eslint-disable-next-line @typescript-eslint/no-deprecated (stmt: ECSqlStatement) => { stmt.bindId(1, classId); if (stmt.step() !== DbResult.BE_SQLITE_ROW) diff --git a/core/backend/src/IModelDb.ts b/core/backend/src/IModelDb.ts index 5cbdbef49985..9dbe21361df5 100644 --- a/core/backend/src/IModelDb.ts +++ b/core/backend/src/IModelDb.ts @@ -414,6 +414,7 @@ export abstract class IModelDb extends IModel { * @returns the value returned by `callback`. * @see [[withStatement]] * @public + * @deprecated in 4.10. Use [[createQueryReader]] instead. */ public withPreparedStatement(ecsql: string, callback: (stmt: ECSqlStatement) => T, logErrors = true): T { const stmt = this._statementCache.findAndRemove(ecsql) ?? this.prepareStatement(ecsql, logErrors); @@ -442,6 +443,7 @@ export abstract class IModelDb extends IModel { * @returns the value returned by `callback`. * @see [[withPreparedStatement]] * @public + * @deprecated in 4.10. Use [[createQueryReader]] instead. */ public withStatement(ecsql: string, callback: (stmt: ECSqlStatement) => T, logErrors = true): T { const stmt = this.prepareStatement(ecsql, logErrors); diff --git a/core/backend/src/test/ecdb/ECDb.test.ts b/core/backend/src/test/ecdb/ECDb.test.ts index 876f65d19689..a874e8bfa7a9 100644 --- a/core/backend/src/test/ecdb/ECDb.test.ts +++ b/core/backend/src/test/ecdb/ECDb.test.ts @@ -6,7 +6,7 @@ import { assert, expect } from "chai"; import * as path from "path"; import * as sinon from "sinon"; import { DbResult, Id64, Id64String, Logger, using } from "@itwin/core-bentley"; -import { ECDb, ECDbOpenMode, ECSqlInsertResult, ECSqlStatement, IModelJsFs, SqliteStatement, SqliteValue, SqliteValueType } from "../../core-backend"; +import { ECDb, ECDbOpenMode, ECSqlInsertResult, ECSqlStatement, ECSqlWriteStatement, IModelJsFs, SqliteStatement, SqliteValue, SqliteValueType } from "../../core-backend"; import { KnownTestLocations } from "../KnownTestLocations"; import { ECDbTestHelper } from "./ECDbTestHelper"; @@ -71,7 +71,7 @@ describe("ECDb", () => { `), (testECDb: ECDb) => { assert.isTrue(testECDb.isOpen); - id = testECDb.withPreparedStatement("INSERT INTO test.Person(Name,Age) VALUES('Mary', 45)", (stmt: ECSqlStatement) => { + id = testECDb.withCachedWriteStatement("INSERT INTO test.Person(Name,Age) VALUES('Mary', 45)", (stmt: ECSqlWriteStatement) => { const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); assert.isDefined(res.id); diff --git a/core/backend/src/test/ecdb/ECSqlReader.test.ts b/core/backend/src/test/ecdb/ECSqlReader.test.ts index 3bee8e7e3512..48020b66752e 100644 --- a/core/backend/src/test/ecdb/ECSqlReader.test.ts +++ b/core/backend/src/test/ecdb/ECSqlReader.test.ts @@ -7,7 +7,7 @@ import { DbResult, using } from "@itwin/core-bentley"; import { ECSqlReader, QueryBinder, QueryOptionsBuilder, QueryRowFormat } from "@itwin/core-common"; import { SnapshotDb } from "../../core-backend"; import { ECDb } from "../../ECDb"; -import { ECSqlStatement } from "../../ECSqlStatement"; +import { ECSqlStatement, ECSqlWriteStatement } from "../../ECSqlStatement"; import { IModelTestUtils } from "../IModelTestUtils"; import { KnownTestLocations } from "../KnownTestLocations"; import { ECDbTestHelper } from "./ECDbTestHelper"; @@ -56,7 +56,7 @@ describe("ECSqlReader", (() => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - const r = await ecdb.withStatement("INSERT INTO ts.Foo(n) VALUES(20)", async (stmt: ECSqlStatement) => { + const r = await ecdb.withWriteStatement("INSERT INTO ts.Foo(n) VALUES(20)", async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); ecdb.saveChanges(); diff --git a/core/backend/src/test/ecdb/ECSqlStatement.test.ts b/core/backend/src/test/ecdb/ECSqlStatement.test.ts index 3d7ba228f973..1aa60120396d 100644 --- a/core/backend/src/test/ecdb/ECSqlStatement.test.ts +++ b/core/backend/src/test/ecdb/ECSqlStatement.test.ts @@ -6,7 +6,7 @@ import { assert } from "chai"; import { DbResult, Guid, GuidString, Id64, Id64String, using } from "@itwin/core-bentley"; import { NavigationValue, QueryBinder, QueryOptions, QueryOptionsBuilder, QueryRowFormat } from "@itwin/core-common"; import { Point2d, Point3d, Range3d, XAndY, XYAndZ } from "@itwin/core-geometry"; -import { _nativeDb, ECDb, ECEnumValue, ECSqlColumnInfo, ECSqlInsertResult, ECSqlStatement, ECSqlValue, SnapshotDb } from "../../core-backend"; +import { _nativeDb, ECDb, ECEnumValue, ECSqlColumnInfo, ECSqlInsertResult, ECSqlStatement, ECSqlValue, ECSqlWriteStatement, SnapshotDb } from "../../core-backend"; import { IModelTestUtils } from "../IModelTestUtils"; import { KnownTestLocations } from "../KnownTestLocations"; import { SequentialLogMatcher } from "../SequentialLogMatcher"; @@ -68,7 +68,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - const r = await ecdb.withStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", async (stmt: ECSqlStatement) => { + const r = await ecdb.withWriteStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); ecdb.saveChanges(); @@ -88,10 +88,10 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - await ecdb.withStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", async (stmt: ECSqlStatement) => { + await ecdb.withWriteStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", async (stmt: ECSqlWriteStatement) => { stmt.stepForInsert(); }); - await ecdb.withStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(30,TIMESTAMP '2019-10-18T12:00:00Z',30)", async (stmt: ECSqlStatement) => { + await ecdb.withWriteStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(30,TIMESTAMP '2019-10-18T12:00:00Z',30)", async (stmt: ECSqlWriteStatement) => { stmt.stepForInsert(); }); ecdb.saveChanges(); @@ -128,7 +128,7 @@ describe("ECSqlStatement", () => { const ROW_COUNT = 27; // insert test rows for (let i = 1; i <= ROW_COUNT; i++) { - const r = await ecdb.withStatement(`insert into ts.Foo(n) values(${i})`, async (stmt: ECSqlStatement) => { + const r = await ecdb.withWriteStatement(`INSERT into ts.Foo(n) values(${i})`, async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); assert.equal(r.status, DbResult.BE_SQLITE_DONE); @@ -168,7 +168,7 @@ describe("ECSqlStatement", () => { const ROW_COUNT = 100; // insert test rows for (let i = 1; i <= ROW_COUNT; i++) { - const r = await ecdb.withStatement(`insert into ts.Foo(n) values(${i})`, async (stmt: ECSqlStatement) => { + const r = await ecdb.withWriteStatement(`INSERT into ts.Foo(n) values(${i})`, async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); assert.equal(r.status, DbResult.BE_SQLITE_DONE); @@ -200,7 +200,7 @@ describe("ECSqlStatement", () => { const ROW_COUNT = 100; // insert test rows for (let i = 1; i <= ROW_COUNT; i++) { - const r = await ecdb.withStatement(`insert into ts.Foo(n) values(${i})`, async (stmt: ECSqlStatement) => { + const r = await ecdb.withWriteStatement(`INSERT into ts.Foo(n) values(${i})`, async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); assert.equal(r.status, DbResult.BE_SQLITE_DONE); @@ -257,7 +257,7 @@ describe("ECSqlStatement", () => { const ROW_COUNT = 27; // insert test rows for (let i = 1; i <= ROW_COUNT; i++) { - const r = await ecdb.withStatement(`insert into ts.Foo(n) values(${i})`, async (stmt: ECSqlStatement) => { + const r = await ecdb.withWriteStatement(`INSERT into ts.Foo(n) values(${i})`, async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); assert.equal(r.status, DbResult.BE_SQLITE_DONE); @@ -285,7 +285,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); for (let i = 1; i <= 5; i++) { - const r = await ecdb.withPreparedStatement(`insert into ts.Foo(n) values(${i})`, async (stmt: ECSqlStatement) => { + const r = await ecdb.withCachedWriteStatement(`insert into ts.Foo(n) values(${i})`, async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); assert.equal(r.status, DbResult.BE_SQLITE_DONE); @@ -317,7 +317,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); for (let i = 1; i <= 2; i++) { - const r = await ecdb.withStatement(`insert into ts.Foo(n) values(${i})`, async (stmt: ECSqlStatement) => { + const r = await ecdb.withWriteStatement(`INSERT into ts.Foo(n) values(${i})`, async (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); assert.equal(r.status, DbResult.BE_SQLITE_DONE); @@ -340,7 +340,7 @@ describe("ECSqlStatement", () => { const maxRows = 10; const guids: GuidString[] = []; for (let i = 0; i < maxRows; i++) { - const r = await ecdb.withPreparedStatement(`insert into ts.Foo(guid) values(?)`, async (stmt: ECSqlStatement) => { + const r = await ecdb.withCachedWriteStatement(`insert into ts.Foo(guid) values(?)`, async (stmt: ECSqlWriteStatement) => { guids.push(Guid.createValue()); stmt.bindGuid(1, guids[i]); return stmt.stepForInsert(); @@ -448,7 +448,7 @@ describe("ECSqlStatement", () => { }; let expectedId = Id64.fromLocalAndBriefcaseIds(4444, 0); - let r: ECSqlInsertResult = ecdb.withPreparedStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlStatement) => { + let r: ECSqlInsertResult = ecdb.withCachedWriteStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindId(1, expectedId); stmt.bindString(2, "4444.txt"); return stmt.stepForInsert(); @@ -456,7 +456,7 @@ describe("ECSqlStatement", () => { await verify(ecdb, r, expectedId); expectedId = Id64.fromLocalAndBriefcaseIds(4445, 0); - r = ecdb.withPreparedStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(:id,:name)", (stmt: ECSqlStatement) => { + r = ecdb.withCachedWriteStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(:id,:name)", (stmt: ECSqlWriteStatement) => { stmt.bindId("id", expectedId); stmt.bindString("name", "4445.txt"); @@ -465,14 +465,14 @@ describe("ECSqlStatement", () => { await verify(ecdb, r, expectedId); expectedId = Id64.fromLocalAndBriefcaseIds(4446, 0); - r = ecdb.withPreparedStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlStatement) => { + r = ecdb.withCachedWriteStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindValues([expectedId, "4446.txt"]); return stmt.stepForInsert(); }); await verify(ecdb, r, expectedId); expectedId = Id64.fromLocalAndBriefcaseIds(4447, 0); - r = ecdb.withPreparedStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(:id,:name)", (stmt: ECSqlStatement) => { + r = ecdb.withCachedWriteStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(:id,:name)", (stmt: ECSqlWriteStatement) => { stmt.bindValues({ id: expectedId, name: "4447.txt" }); return stmt.stepForInsert(); }); @@ -511,7 +511,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - const r: ECSqlInsertResult = ecdb.withPreparedStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", (stmt: ECSqlStatement) => { + const r: ECSqlInsertResult = ecdb.withCachedWriteStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", (stmt: ECSqlWriteStatement) => { return stmt.stepForInsert(); }); ecdb.saveChanges(); @@ -809,7 +809,7 @@ describe("ECSqlStatement", () => { assert.isTrue(ecdb.isOpen); const doubleVal: number = 3.5; - let id = await ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindDouble')", async (stmt: ECSqlStatement) => { + let id = await ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindDouble')", async (stmt: ECSqlWriteStatement) => { stmt.bindDouble(1, doubleVal); stmt.bindDouble(2, doubleVal); stmt.bindDouble(3, doubleVal); @@ -837,7 +837,7 @@ describe("ECSqlStatement", () => { }), 1); const smallIntVal: number = 3; - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, small int')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, small int')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, smallIntVal); stmt.bindInteger(2, smallIntVal); stmt.bindInteger(3, smallIntVal); @@ -869,7 +869,7 @@ describe("ECSqlStatement", () => { const largeUnsafeNumberStr: string = "12312312312312323654"; const largeUnsafeNumberHexStr: string = "0xaade1ed08b0b5e46"; - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large unsafe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large unsafe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeUnsafeNumberStr); stmt.bindInteger(2, largeUnsafeNumberStr); stmt.bindInteger(3, largeUnsafeNumberStr); @@ -896,7 +896,7 @@ describe("ECSqlStatement", () => { // assert.equal(row.hl, largeUnsafeNumberHexStr); // }), 1); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large unsafe number as hexstring')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large unsafe number as hexstring')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeUnsafeNumberHexStr); stmt.bindInteger(2, largeUnsafeNumberHexStr); stmt.bindInteger(3, largeUnsafeNumberHexStr); @@ -923,7 +923,7 @@ describe("ECSqlStatement", () => { // assert.equal(row.hl, largeUnsafeNumberHexStr); // }), 1); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large unsafe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large unsafe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, largeUnsafeNumberStr); stmt.bindString(2, largeUnsafeNumberStr); stmt.bindString(3, largeUnsafeNumberStr); @@ -951,7 +951,7 @@ describe("ECSqlStatement", () => { assert.equal(row.s, largeUnsafeNumberStr); }), 1); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large unsafe number as hexstring')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large unsafe number as hexstring')", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, largeUnsafeNumberHexStr); stmt.bindString(2, largeUnsafeNumberHexStr); stmt.bindString(3, largeUnsafeNumberHexStr); @@ -982,7 +982,7 @@ describe("ECSqlStatement", () => { assert.isFalse(Number.isSafeInteger(largeNegUnsafeNumber)); const largeNegUnsafeNumberStr: string = "-123123123123123236"; - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large negative unsafe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large negative unsafe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeNegUnsafeNumberStr); stmt.bindInteger(2, largeNegUnsafeNumberStr); stmt.bindInteger(3, largeNegUnsafeNumberStr); @@ -1007,7 +1007,7 @@ describe("ECSqlStatement", () => { assert.equal(row.s, largeNegUnsafeNumberStr); }), 1); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large negative unsafe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large negative unsafe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, largeNegUnsafeNumberStr); stmt.bindString(2, largeNegUnsafeNumberStr); stmt.bindString(3, largeNegUnsafeNumberStr); @@ -1037,7 +1037,7 @@ describe("ECSqlStatement", () => { const largeSafeNumberStr: string = largeSafeNumber.toString(); const largeSafeNumberHexStr: string = "0x45fcc5c2c8500"; - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large safe number')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large safe number')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeSafeNumber); stmt.bindInteger(2, largeSafeNumber); stmt.bindInteger(3, largeSafeNumber); @@ -1072,7 +1072,7 @@ describe("ECSqlStatement", () => { // assert.equal(row.s, largeSafeNumberStr); // }); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large safe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large safe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeSafeNumberStr); stmt.bindInteger(2, largeSafeNumberStr); stmt.bindInteger(3, largeSafeNumberStr); @@ -1099,7 +1099,7 @@ describe("ECSqlStatement", () => { assert.equal(row.s, largeSafeNumberStr); }), 1); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large safe number as hexstring')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large safe number as hexstring')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeSafeNumberHexStr); stmt.bindInteger(2, largeSafeNumberHexStr); stmt.bindInteger(3, largeSafeNumberHexStr); @@ -1126,7 +1126,7 @@ describe("ECSqlStatement", () => { assert.equal(row.s, largeSafeNumberStr); // even though it was bound as hex str, it gets converted to int64 before persisting }), 1); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large safe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large safe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, largeSafeNumberStr); stmt.bindString(2, largeSafeNumberStr); stmt.bindString(3, largeSafeNumberStr); @@ -1154,7 +1154,7 @@ describe("ECSqlStatement", () => { }), 1); // SQLite does not parse hex strs bound as strings. - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large safe number as hexstring')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large safe number as hexstring')", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, largeSafeNumberHexStr); stmt.bindString(2, largeSafeNumberHexStr); stmt.bindString(3, largeSafeNumberHexStr); @@ -1185,7 +1185,7 @@ describe("ECSqlStatement", () => { assert.isTrue(Number.isSafeInteger(largeNegSafeNumber)); const largeNegSafeNumberStr: string = largeNegSafeNumber.toString(); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large negative safe number')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large negative safe number')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeNegSafeNumber); stmt.bindInteger(2, largeNegSafeNumber); stmt.bindInteger(3, largeNegSafeNumber); @@ -1212,7 +1212,7 @@ describe("ECSqlStatement", () => { assert.equal(row.s, largeNegSafeNumberStr); }), 1); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large negative safe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindInteger, large negative safe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindInteger(1, largeNegSafeNumberStr); stmt.bindInteger(2, largeNegSafeNumberStr); stmt.bindInteger(3, largeNegSafeNumberStr); @@ -1239,7 +1239,7 @@ describe("ECSqlStatement", () => { assert.equal(row.s, largeNegSafeNumberStr); }); - id = ecdb.withPreparedStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large negative safe number as string')", (stmt: ECSqlStatement) => { + id = ecdb.withCachedWriteStatement("INSERT INTO Test.Foo(D,I,L,S,Description) VALUES(?,?,?,?,'bindString, large negative safe number as string')", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, largeNegSafeNumberStr); stmt.bindString(2, largeNegSafeNumberStr); stmt.bindString(3, largeNegSafeNumberStr); @@ -1372,7 +1372,7 @@ describe("ECSqlStatement", () => { }; const ids = new Array(); - ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,P2d,P3d,S,Struct.Bl,Struct.Bo,Struct.D,Struct.Dt,Struct.I,Struct.P2d,Struct.P3d,Struct.S) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (stmt: ECSqlStatement) => { + ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,P2d,P3d,S,Struct.Bl,Struct.Bo,Struct.D,Struct.Dt,Struct.I,Struct.P2d,Struct.P3d,Struct.S) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindBlob(1, blobVal); stmt.bindBoolean(2, boolVal); stmt.bindDouble(3, doubleVal); @@ -1402,7 +1402,7 @@ describe("ECSqlStatement", () => { ids.push(res.id!); }); - ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,P2d,P3d,S,Struct.Bl,Struct.Bo,Struct.D,Struct.Dt,Struct.I,Struct.P2d,Struct.P3d,Struct.S) VALUES(:bl,:bo,:d,:dt,:i,:p2d,:p3d,:s,:s_bl,:s_bo,:s_d,:s_dt,:s_i,:s_p2d,:s_p3d,:s_s)", (stmt: ECSqlStatement) => { + ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,P2d,P3d,S,Struct.Bl,Struct.Bo,Struct.D,Struct.Dt,Struct.I,Struct.P2d,Struct.P3d,Struct.S) VALUES(:bl,:bo,:d,:dt,:i,:p2d,:p3d,:s,:s_bl,:s_bo,:s_d,:s_dt,:s_i,:s_p2d,:s_p3d,:s_s)", (stmt: ECSqlWriteStatement) => { stmt.bindBlob("bl", blobVal); stmt.bindBoolean("bo", boolVal); stmt.bindDouble("d", doubleVal); @@ -1503,7 +1503,7 @@ describe("ECSqlStatement", () => { }), 1); }); }; - await ecdb.withPreparedStatement("INSERT INTO test.Foo(Struct) VALUES(?)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Struct) VALUES(?)", async (stmt: ECSqlWriteStatement) => { stmt.bindStruct(1, structVal); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -1511,7 +1511,7 @@ describe("ECSqlStatement", () => { await verify(res.id!); }); - await ecdb.withPreparedStatement("INSERT INTO test.Foo(Struct) VALUES(?)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Struct) VALUES(?)", async (stmt: ECSqlWriteStatement) => { stmt.bindValues([structVal]); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -1519,7 +1519,7 @@ describe("ECSqlStatement", () => { await verify(res.id!); }); - await ecdb.withPreparedStatement("INSERT INTO test.Foo(Struct) VALUES(:str)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Struct) VALUES(:str)", async (stmt: ECSqlWriteStatement) => { stmt.bindStruct("str", structVal); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -1527,7 +1527,7 @@ describe("ECSqlStatement", () => { await verify(res.id!); }); - await ecdb.withPreparedStatement("INSERT INTO test.Foo(Struct) VALUES(:str)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Struct) VALUES(:str)", async (stmt: ECSqlWriteStatement) => { stmt.bindValues({ str: structVal }); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -1601,7 +1601,7 @@ describe("ECSqlStatement", () => { }), 1); }; - await ecdb.withPreparedStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(?,?,?)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(?,?,?)", async (stmt: ECSqlWriteStatement) => { stmt.bindArray(1, intArray); stmt.bindArray(2, dtArray); stmt.bindArray(3, addressArray); @@ -1611,7 +1611,7 @@ describe("ECSqlStatement", () => { await verify(res.id!); }); - await ecdb.withPreparedStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(?,?,?)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(?,?,?)", async (stmt: ECSqlWriteStatement) => { stmt.bindValues([intArray, dtArray, addressArray]); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -1619,7 +1619,7 @@ describe("ECSqlStatement", () => { await verify(res.id!); }); - await ecdb.withPreparedStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(:iarray,:dtarray,:addresses)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(:iarray,:dtarray,:addresses)", async (stmt: ECSqlWriteStatement) => { stmt.bindArray("iarray", intArray); stmt.bindArray("dtarray", dtArray); stmt.bindArray("addresses", addressArray); @@ -1629,7 +1629,7 @@ describe("ECSqlStatement", () => { await verify(res.id!); }); - await ecdb.withPreparedStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(:iarray,:dtarray,:addresses)", async (stmt: ECSqlStatement) => { + await ecdb.withCachedWriteStatement("INSERT INTO test.Foo(I_Array,Dt_Array,Addresses) VALUES(:iarray,:dtarray,:addresses)", async (stmt: ECSqlWriteStatement) => { stmt.bindValues({ iarray: intArray, dtarray: dtArray, addresses: addressArray }); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -1661,7 +1661,7 @@ describe("ECSqlStatement", () => { assert.isTrue(ecdb.isOpen); - const parentId: Id64String = ecdb.withStatement("INSERT INTO test.Parent(Code) VALUES('Parent 1')", (stmt: ECSqlStatement) => { + const parentId: Id64String = ecdb.withWriteStatement("INSERT INTO test.Parent(Code) VALUES('Parent 1')", (stmt: ECSqlWriteStatement) => { const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); assert.isDefined(res.id); @@ -1669,7 +1669,7 @@ describe("ECSqlStatement", () => { }); const childIds = new Array(); - ecdb.withStatement("INSERT INTO test.Child(Name,Parent) VALUES(?,?)", (stmt: ECSqlStatement) => { + ecdb.withWriteStatement("INSERT INTO test.Child(Name,Parent) VALUES(?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, "Child 1"); stmt.bindNavigation(2, { id: parentId, relClassName: "Test.ParentHasChildren" }); let res: ECSqlInsertResult = stmt.stepForInsert(); @@ -1687,7 +1687,7 @@ describe("ECSqlStatement", () => { childIds.push(res.id!); }); - ecdb.withStatement("INSERT INTO test.Child(Name,Parent) VALUES(:name,:parent)", (stmt: ECSqlStatement) => { + ecdb.withWriteStatement("INSERT INTO test.Child(Name,Parent) VALUES(:name,:parent)", (stmt: ECSqlWriteStatement) => { stmt.bindString("name", "Child 3"); stmt.bindNavigation("parent", { id: parentId, relClassName: "Test.ParentHasChildren" }); let res: ECSqlInsertResult = stmt.stepForInsert(); @@ -1760,7 +1760,7 @@ describe("ECSqlStatement", () => { assert.isTrue(ecdb.isOpen); - const id: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo([Range3d]) VALUES(?)", (stmt: ECSqlStatement) => { + const id: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo([Range3d]) VALUES(?)", (stmt: ECSqlWriteStatement) => { stmt.bindRange3d(1, testRange); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -1785,7 +1785,7 @@ describe("ECSqlStatement", () => { assert.isTrue(ecdb.isOpen); const idNumbers: number[] = [4444, 4545, 1234, 6758, 1312]; - ecdb.withPreparedStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlStatement) => { + ecdb.withCachedWriteStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlWriteStatement) => { idNumbers.forEach((idNum: number) => { const expectedId = Id64.fromLocalAndBriefcaseIds(idNum, 0); stmt.bindId(1, expectedId); @@ -1874,7 +1874,7 @@ describe("ECSqlStatement", () => { }); // *** test withstatement cache - ecdb.withPreparedStatement("INSERT INTO test.Person(Name,Age,Location) VALUES(?,?,?)", (stmt: ECSqlStatement) => { + ecdb.withCachedWriteStatement("INSERT INTO test.Person(Name,Age,Location) VALUES(?,?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, "Mary Miller"); stmt.bindInteger(2, 30); stmt.bindStruct(3, { Street: "2000 Main Street", City: "New York", Zip: 12311 }); @@ -1950,7 +1950,7 @@ describe("ECSqlStatement", () => { const p3dVal = new Point3d(1, 2, 3); const strVal: string = "Hello world"; - const id: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,P2d,P3d,S) VALUES(?,?,?,?,?,?,?,?)", (stmt: ECSqlStatement) => { + const id: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,P2d,P3d,S) VALUES(?,?,?,?,?,?,?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindBlob(1, blobVal); stmt.bindBoolean(2, boolVal); stmt.bindDouble(3, doubleVal); @@ -2105,28 +2105,28 @@ describe("ECSqlStatement", () => { const abbreviatedSingleBlobVal = `{"bytes":${singleBlobVal.byteLength}}`; const emptyBlobVal = new Uint8Array(); - const fullId: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlStatement) => { + const fullId: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlWriteStatement) => { stmt.bindBlob(1, blobVal); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); return res.id!; }); - const singleId: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlStatement) => { + const singleId: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlWriteStatement) => { stmt.bindBlob(1, singleBlobVal); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); return res.id!; }); - const emptyId: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlStatement) => { + const emptyId: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlWriteStatement) => { stmt.bindBlob(1, emptyBlobVal); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); return res.id!; }); - const nullId: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlStatement) => { + const nullId: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl) VALUES(?)", (stmt: ECSqlWriteStatement) => { stmt.bindNull(1); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -2228,7 +2228,7 @@ describe("ECSqlStatement", () => { `), async (ecdb) => { assert.isTrue(ecdb.isOpen); let rowCount: number; - const parentId: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Parent(Code) VALUES('Parent 1')", (stmt: ECSqlStatement) => { + const parentId: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Parent(Code) VALUES('Parent 1')", (stmt: ECSqlWriteStatement) => { const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); assert.isDefined(res.id); @@ -2236,7 +2236,7 @@ describe("ECSqlStatement", () => { }); const childIds = new Array(); - ecdb.withPreparedStatement("INSERT INTO test.Child(Name,Parent) VALUES(?,?)", (stmt: ECSqlStatement) => { + ecdb.withCachedWriteStatement("INSERT INTO test.Child(Name,Parent) VALUES(?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindString(1, "Child 1"); stmt.bindNavigation(2, { id: parentId, relClassName: "Test.ParentHasChildren" }); let res: ECSqlInsertResult = stmt.stepForInsert(); @@ -2374,7 +2374,7 @@ describe("ECSqlStatement", () => { const p3dVal: XYAndZ = { x: 1, y: 2, z: 3 }; const stringVal: string = "Hello World"; - const id: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(Struct) VALUES(?)", (stmt: ECSqlStatement) => { + const id: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Struct) VALUES(?)", (stmt: ECSqlWriteStatement) => { stmt.bindStruct(1, { bl: blobVal, bo: boolVal, d: doubleVal, dt: dtVal, i: intVal, p2d: p2dVal, p3d: p3dVal, s: stringVal }); const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); @@ -2495,7 +2495,7 @@ describe("ECSqlStatement", () => { i: 3, l: 12312312312312, p2d: { x: 1, y: 2 }, p3d: { x: 1, y: 2, z: 3 }, s: "Hello World", }; - const id: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,L,P2d,P3d,S) VALUES(:bl,:bo,:d,:dt,:i,:l,:p2d,:p3d,:s)", (stmt: ECSqlStatement) => { + const id: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(Bl,Bo,D,Dt,I,L,P2d,P3d,S) VALUES(:bl,:bo,:d,:dt,:i,:l,:p2d,:p3d,:s)", (stmt: ECSqlWriteStatement) => { stmt.bindValues({ bl: blobVal, bo: expectedRow.bo, d: expectedRow.d, dt: expectedRow.dt, i: expectedRow.i, l: expectedRow.l, p2d: expectedRow.p2d, p3d: expectedRow.p3d, s: expectedRow.s, @@ -2584,7 +2584,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - const id: Id64String = ecdb.withPreparedStatement("INSERT INTO test.Foo(MyStat,MyStats,MyDomain,MyDomains) VALUES(test.Status.[On],?,test.Domain.Org,?)", (stmt: ECSqlStatement) => { + const id: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(MyStat,MyStats,MyDomain,MyDomains) VALUES(test.Status.[On],?,test.Domain.Org,?)", (stmt: ECSqlWriteStatement) => { stmt.bindValue(1, [1, 2]); stmt.bindValue(2, ["Org", "Com"]); const res: ECSqlInsertResult = stmt.stepForInsert(); @@ -2697,7 +2697,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - const ids: { unored: Id64String, ored: Id64String, unmatched: Id64String } = ecdb.withPreparedStatement("INSERT INTO test.Foo(MyColor,MyDomain) VALUES(?,?)", (stmt: ECSqlStatement) => { + const ids: { unored: Id64String, ored: Id64String, unmatched: Id64String } = ecdb.withCachedWriteStatement("INSERT INTO test.Foo(MyColor,MyDomain) VALUES(?,?)", (stmt: ECSqlWriteStatement) => { stmt.bindValue(1, 4); stmt.bindValue(2, "com"); let res: ECSqlInsertResult = stmt.stepForInsert(); @@ -2839,7 +2839,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - const r = await ecdb.withPreparedStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", async (stmt: ECSqlStatement) => { + const r = await ecdb.withCachedWriteStatement("INSERT INTO ts.Foo(n,dt,fooId) VALUES(20,TIMESTAMP '2018-10-18T12:00:00Z',20)", async (stmt: ECSqlWriteStatement) => { const nativesql: string = stmt.getNativeSql(); assert.isTrue(nativesql.startsWith("INSERT INTO [ts_Foo]")); return stmt.stepForInsert(); @@ -2859,7 +2859,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - const id: Id64String = ecdb.withPreparedStatement("INSERT INTO test.MyClass(MyProperty) VALUES('Value')", (stmt: ECSqlStatement) => { + const id: Id64String = ecdb.withCachedWriteStatement("INSERT INTO test.MyClass(MyProperty) VALUES('Value')", (stmt: ECSqlWriteStatement) => { const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); assert.isDefined(res.id); @@ -2930,7 +2930,7 @@ describe("ECSqlStatement", () => { `), async (ecdb: ECDb) => { assert.isTrue(ecdb.isOpen); - ecdb.withPreparedStatement("INSERT INTO Test.A (f.c.a, f.c.b, f.d, g) VALUES ('f.c.a' ,'f.c.b', 'f.d', 'g')", (stmt: ECSqlStatement) => { + ecdb.withCachedWriteStatement("INSERT INTO Test.A (f.c.a, f.c.b, f.d, g) VALUES ('f.c.a' ,'f.c.b', 'f.d', 'g')", (stmt: ECSqlWriteStatement) => { const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); assert.isDefined(res.id); @@ -2996,7 +2996,7 @@ describe("ECSqlStatement", () => { assert.equal(originPropertyName4, "g"); }); - ecdb.withPreparedStatement("INSERT INTO Test.B (h.a, h.b, i) VALUES ('h.a' ,'h.b', 'i')", (stmt: ECSqlStatement) => { + ecdb.withCachedWriteStatement("INSERT INTO Test.B (h.a, h.b, i) VALUES ('h.a' ,'h.b', 'i')", (stmt: ECSqlWriteStatement) => { const res: ECSqlInsertResult = stmt.stepForInsert(); assert.equal(res.status, DbResult.BE_SQLITE_DONE); assert.isDefined(res.id); diff --git a/core/common/src/ConcurrentQuery.ts b/core/common/src/ConcurrentQuery.ts index 1711fd52735e..49da3ad68663 100644 --- a/core/common/src/ConcurrentQuery.ts +++ b/core/common/src/ConcurrentQuery.ts @@ -27,6 +27,7 @@ export enum QueryRowFormat { UseECSqlPropertyIndexes, /** Each row is an object in which each non-null column value can be accessed by a [remapped property name]($docs/learning/ECSqlRowFormat.md). * This format is backwards-compatible with the format produced by iTwin.js 2.x. Null values are omitted. + * @depreacted in 4.10. Switch to UseECSqlPropertyIndexes for best performance, and UseECSqlPropertyNames if you want a JSON object as the result. */ UseJsPropertyNames, } @@ -135,6 +136,7 @@ export interface QueryOptions extends BaseReaderOptions { /** * Convert ECClassId, SourceECClassId, TargetECClassId and RelClassId to respective name. * When true, XXXXClassId property will be returned as className. + * @deprecated in 4.10 Use ecsql function ec_classname to get class name instead. * */ convertClassIdsToClassNames?: boolean; /**