UNPKG

@itwin/core-backend

Version:
1,043 lines • 49.1 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module ECSQL */ import { assert, DbResult } from "@itwin/core-bentley"; import { Range3d, XYZ } from "@itwin/core-geometry"; import { ECJsNames, ECSqlValueType, IModelError, PropertyMetaDataMap, QueryRowFormat } from "@itwin/core-common"; import { IModelNative } from "./internal/NativePlatform"; /** The result of an **ECSQL INSERT** statement as returned from [ECSqlStatement.stepForInsert]($backend). * * If the step was successful, the ECSqlInsertResult contains * [DbResult.BE_SQLITE_DONE]($core-bentley) * and the ECInstanceId of the newly created instance. * In case of failure it contains the [DbResult]($core-bentley) error code. * * > Insert statements can be used with ECDb only, not with IModelDb. * @public */ export class ECSqlInsertResult { status; id; constructor(status, id) { this.status = status; this.id = id; } } /** Executes ECSQL statements. * * A statement must be prepared before it can be executed, and it must be released when no longer needed. * See [IModelDb.withPreparedStatement]($backend) or * [ECDb.withPreparedStatement]($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.step]($backend). * In case of an **ECSQL SELECT** statement, the current row can be retrieved with [ECSqlStatement.getRow]($backend) as * a whole, or with [ECSqlStatement.getValue]($backend) when individual values are needed. * Alternatively, query results of an **ECSQL SELECT** statement can be stepped through by using * standard iteration syntax, such as `for of`. * * > 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 * @deprecated in 4.11 - will not be removed until after 2026-06-13. 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 { _stmt; _sql; _props = new PropertyMetaDataMap([]); get sql() { return this._sql; } // eslint-disable-line @typescript-eslint/no-non-null-assertion /** Check if this statement has been prepared successfully or not */ get isPrepared() { return !!this._stmt; } /** 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 * @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 */ prepare(db, ecsql, logErrors = true) { const stat = this.tryPrepare(db, ecsql, logErrors); if (stat.status !== DbResult.BE_SQLITE_OK) { throw new IModelError(stat.status, stat.message); } } /** 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 */ tryPrepare(db, ecsql, logErrors = false) { if (this.isPrepared) throw new Error("ECSqlStatement is already prepared"); this._sql = ecsql; this._stmt = new IModelNative.platform.ECSqlStatement(); return this._stmt.prepare(db, ecsql, logErrors); } /** Reset this statement so that the next call to step will return the first row, if any. */ reset() { assert(undefined !== this._stmt); this._stmt.reset(); this._props = new PropertyMetaDataMap([]); } /** Get the Native SQL statement * @internal */ getNativeSql() { assert(undefined !== this._stmt); 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. */ [Symbol.dispose]() { if (this._stmt) { this._stmt.dispose(); // free native statement this._stmt = undefined; } } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */ dispose() { this[Symbol.dispose](); } /** 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 */ bindValue(parameter, val) { this.getBinder(parameter).bind(val); } /** Binds null to the specified ECSQL parameter. * @param parameter Index (1-based) or name of the parameter */ bindNull(parameter) { 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 */ bindBlob(parameter, blob) { 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 */ bindBoolean(parameter, val) { 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 */ bindDateTime(parameter, isoDateTimeString) { 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 */ bindDouble(parameter, val) { 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 */ bindGuid(parameter, val) { 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 */ bindId(parameter, val) { 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. */ bindInteger(parameter, val) { 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 */ bindPoint2d(parameter, val) { 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 */ bindPoint3d(parameter, val) { 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 */ bindRange3d(parameter, val) { 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 */ bindString(parameter, val) { 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 */ bindNavigation(parameter, val) { 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) */ bindStruct(parameter, val) { 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 */ bindArray(parameter, val) { this.getBinder(parameter).bindArray(val); } bindIdSet(parameter, val) { 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 */ getBinder(parameter) { assert(undefined !== this._stmt); return new ECSqlBinder(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) */ bindValues(values) { if (Array.isArray(values)) { for (let i = 0; i < values.length; i++) { const paramIndex = i + 1; const paramValue = values[i]; if (paramValue === undefined || paramValue === null) continue; this.bindValue(paramIndex, paramValue); } return; } for (const entry of Object.entries(values)) { const paramName = entry[0]; const paramValue = entry[1]; if (paramValue === undefined || paramValue === null) continue; this.bindValue(paramName, paramValue); } } /** Clear any bindings that were previously set on this statement. * @throws [IModelError]($common) in case of errors */ clearBindings() { if (this._stmt) { const stat = this._stmt.clearBindings(); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error clearing bindings"); } } /** Step this statement to the next row. * * For **ECSQL SELECT** statements the method returns * - [DbResult.BE_SQLITE_ROW]($core-bentley) if the statement now points successfully to the next row. * - [DbResult.BE_SQLITE_DONE]($core-bentley) if the statement has no more rows. * - Error status in case of errors. * * For **ECSQL INSERT, UPDATE, DELETE** statements the method returns * - [DbResult.BE_SQLITE_DONE]($core-bentley) if the statement has been executed successfully. * - Error status in case of errors. * * > Insert statements can be used with ECDb only, not with IModelDb. * * See also: [Code Samples]($docs/learning/backend/ECSQLCodeExamples) */ step() { return this._stmt.step(); } // eslint-disable-line @typescript-eslint/no-non-null-assertion /** @internal added this back in for testing purposes */ async stepAsync() { return new Promise((resolve, _reject) => { this._stmt.stepAsync(resolve); // eslint-disable-line @typescript-eslint/no-non-null-assertion }); } /** 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. */ stepForInsert() { assert(undefined !== this._stmt); const r = this._stmt.stepForInsert(); if (r.status === DbResult.BE_SQLITE_DONE) return new ECSqlInsertResult(r.status, r.id); return new ECSqlInsertResult(r.status); } /** Get the query result's column count (only for ECSQL SELECT statements). */ getColumnCount() { return this._stmt.getColumnCount(); } // eslint-disable-line @typescript-eslint/no-non-null-assertion /** Get the current row. * The returned row is formatted as JavaScript object where every SELECT clause item becomes a property in the JavaScript object. * * See also: * - [ECSQL row format]($docs/learning/ECSQLRowFormat) for details about the format of the returned row. * - [Code Samples]($docs/learning/backend/ECSQLCodeExamples#working-with-the-query-result) */ getRow(args) { if (!this._stmt) throw new Error("ECSqlStatement is not prepared"); args = args ?? {}; if (args.rowFormat === undefined) { args.rowFormat = QueryRowFormat.UseJsPropertyNames; } const resp = this._stmt.toRow({ classIdsToClassNames: args.classIdsToClassNames, useJsName: args.rowFormat === QueryRowFormat.UseJsPropertyNames, abbreviateBlobs: false, // In 4.x, people are currently dependent on the behavior of aliased classIds `select classId as aliasedClassId` not being // converted into classNames which is a bug that we must now support.This option preserves this special behavior until // it can be removed in a future version. doNotConvertClassIdsToClassNamesWhenAliased: true, }); return this.formatCurrentRow(resp, args.rowFormat); } formatCurrentRow(currentResp, rowFormat = QueryRowFormat.UseJsPropertyNames) { if (!this._stmt) throw new Error("ECSqlStatement is not prepared"); if (rowFormat === QueryRowFormat.UseECSqlPropertyIndexes) return currentResp.data; if (this._props.length === 0) { const resp = this._stmt.getMetadata(); this._props = new PropertyMetaDataMap(resp.meta); } const formattedRow = {}; for (const prop of this._props) { const propName = rowFormat === QueryRowFormat.UseJsPropertyNames ? prop.jsonName : prop.name; const val = currentResp.data[prop.index]; if (typeof val !== "undefined" && val !== null) { Object.defineProperty(formattedRow, propName, { value: val, enumerable: true, writable: true, }); } } return formattedRow; } /** Calls step when called as an iterator. * * Each iteration returns an [ECSQL row format]($docs/learning/ECSQLRowFormat) as returned * from [ECSqlStatement.getRow]($backend). */ next() { if (DbResult.BE_SQLITE_ROW === this.step()) { return { done: false, value: this.getRow(), }; } else { return { done: true, value: undefined, }; } } /** The iterator that will step through the results of this statement. */ [Symbol.iterator]() { return this; } /** Get the value for the column at the given index in the query result. * @param columnIx Index of ECSQL column in query result (0-based) * * See also: [Code Samples]($docs/learning/backend/ECSQLCodeExamples#working-with-the-query-result) */ // eslint-disable-next-line @typescript-eslint/no-deprecated getValue(columnIx) { assert(undefined !== this._stmt); // eslint-disable-next-line @typescript-eslint/no-deprecated return new ECSqlValue(this._stmt.getValue(columnIx)); } } /** 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 { // eslint-disable-next-line @typescript-eslint/no-deprecated _stmt; // eslint-disable-next-line @typescript-eslint/no-deprecated constructor(stmt) { if (stmt) this._stmt = stmt; else { // eslint-disable-next-line @typescript-eslint/no-deprecated this._stmt = new ECSqlStatement(); } } get sql() { return this._stmt.sql; } /** Check if this statement has been prepared successfully or not */ get isPrepared() { return this._stmt.isPrepared; } /** Get the underlying ECSqlStatement. Needed until we remove ECSqlStatement. * @param * @internal */ // eslint-disable-next-line @typescript-eslint/no-deprecated get stmt() { 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 */ prepare(db, ecsql, logErrors = true) { 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 */ tryPrepare(db, ecsql, logErrors = false) { return this.tryPrepare(db, ecsql, logErrors); } /** Reset this statement so that the next call to step will return the first row, if any. */ reset() { this._stmt.reset(); } /** * Releases the native resources held by this ECSqlWriteStatement. * * This method should be called when the statement is no longer needed to free up native resources. * * > Do not call this method directly on a statement that is being managed by a statement cache. */ [Symbol.dispose]() { if (this._stmt) this._stmt[Symbol.dispose](); } /** Get the Native SQL statement * @internal */ getNativeSql() { return this._stmt.getNativeSql(); } /** 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 */ bindValue(parameter, val) { this.getBinder(parameter).bind(val); } /** Binds null to the specified ECSQL parameter. * @param parameter Index (1-based) or name of the parameter */ bindNull(parameter) { 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 */ bindBlob(parameter, blob) { 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 */ bindBoolean(parameter, val) { 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 */ bindDateTime(parameter, isoDateTimeString) { 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 */ bindDouble(parameter, val) { 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 */ bindGuid(parameter, val) { 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 */ bindId(parameter, val) { 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. */ bindInteger(parameter, val) { 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 */ bindPoint2d(parameter, val) { 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 */ bindPoint3d(parameter, val) { 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 */ bindRange3d(parameter, val) { 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 */ bindString(parameter, val) { 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 */ bindNavigation(parameter, val) { 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) */ bindStruct(parameter, val) { 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 */ bindArray(parameter, val) { this.getBinder(parameter).bindArray(val); } bindIdSet(parameter, val) { 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 */ getBinder(parameter) { 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) */ bindValues(values) { this._stmt.bindValues(values); } /** Clear any bindings that were previously set on this statement. * @throws [IModelError]($common) in case of errors */ clearBindings() { 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. */ stepForInsert() { return this._stmt.stepForInsert(); } step() { return this._stmt.step(); } /** Get the query result's column count (only for ECSQL SELECT statements). */ getColumnCount() { return this._stmt.getColumnCount(); } } /** Binds a value to an ECSQL parameter. * * See also: * * - [ECSqlStatement]($backend) * - [ECSqlStatement.getBinder]($backend) * - [Executing ECSQL]($docs/learning/backend/ExecutingECSQL) * @public */ export class ECSqlBinder { _binder; /** @internal */ constructor(binder) { this._binder = binder; } /** Binds the specified value to the 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 val Value to bind */ bind(val) { ECSqlBindingHelper.bindValue(this, val); } /** Binds null to the ECSQL parameter. */ bindNull() { const stat = this._binder.bindNull(); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding null"); } /** Binds a BLOB value to the ECSQL parameter. * @param BLOB value as either a UInt8Array, ArrayBuffer or a Base64 string */ bindBlob(blob) { const stat = this._binder.bindBlob(blob); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding blob"); } /** Binds a boolean value to the ECSQL parameter. * @param val Boolean value */ bindBoolean(val) { const stat = this._binder.bindBoolean(val); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding boolean"); } /** Binds a DateTime value to the ECSQL parameter. * @param isoDateTimeString DateTime value as ISO8601 string */ bindDateTime(isoDateTimeString) { const stat = this._binder.bindDateTime(isoDateTimeString); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding DateTime"); } /** Binds a double value to the ECSQL parameter. * @param val Double value */ bindDouble(val) { const stat = this._binder.bindDouble(val); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding double"); } /** Binds an GUID value to the ECSQL parameter. * @param val GUID value. If passed as string, it must be formatted as described in [GuidString]($core-bentley). */ bindGuid(val) { const stat = this._binder.bindGuid(val); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding GUID"); } /** Binds an Id value to the ECSQL parameter. * @param val Id value. If passed as string it must be the hexadecimal representation of the Id. */ bindId(val) { const stat = this._binder.bindId(val); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding Id"); } /** Binds an integer value to the ECSQL parameter. * @param val Integer value as number, decimal string or hexadecimal string. */ bindInteger(val) { const stat = this._binder.bindInteger(val); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding integer"); } /** Binds an Point2d value to the ECSQL parameter. * @param val Point2d value */ bindPoint2d(val) { const stat = this._binder.bindPoint2d(val.x, val.y); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding Point2d"); } /** Binds an Point3d value to the ECSQL parameter. * @param val Point3d value */ bindPoint3d(val) { const stat = this._binder.bindPoint3d(val.x, val.y, val.z); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding Point3d"); } /** Binds a Range3d as a blob to the ECSQL parameter. * @param val Range3d value */ bindRange3d(val) { const stat = this._binder.bindBlob(Range3d.toFloat64Array(val).buffer); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding Range3d"); } /** Binds an string to the ECSQL parameter. * @param val String value */ bindString(val) { const stat = this._binder.bindString(val); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding string"); } /** Binds a navigation property value to the ECSQL parameter. * @param val Navigation property value */ bindNavigation(val) { const stat = this._binder.bindNavigation(val.id, val.relClassName, val.relClassTableSpace); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding navigation property"); } /** Binds a struct property value to the ECSQL 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) */ bindStruct(val) { ECSqlBindingHelper.bindStruct(this, val); } /** Gets the binder for the specified member of a struct parameter * * > This is the most low-level way to bind struct parameters with most flexibility. A simpler alternative is * > to just call [ECSqlBinder.bindStruct]($backend). */ bindMember(memberName) { return new ECSqlBinder(this._binder.bindMember(memberName)); } /** Binds a set of Id strings to the ECSQL parameter. * @param val array of Id values. If passed as string they must be the hexadecimal representation of the Ids. */ bindIdSet(vector) { const stat = this._binder.bindIdSet(vector); if (stat !== DbResult.BE_SQLITE_OK) throw new IModelError(stat, "Error binding id set"); } /** Binds an array value to the ECSQL parameter. * @param val Array value. The array value is an array of values of the supported types */ bindArray(val) { ECSqlBindingHelper.bindArray(this, val); } /** Adds a new array element to the array parameter and returns the binder for the new array element * * > This is the most low-level way to bind array parameters with most flexibility. A simpler alternative is * > to just call [ECSqlBinder.bindArray]($backend). */ addArrayElement() { return new ECSqlBinder(this._binder.addArrayElement()); } } /** Value of a column in a row of an ECSQL query result. * * See also: * - [ECSqlStatement]($backend) * - [ECSqlStatement.getValue]($backend) * - [Code Samples]($docs/learning/backend/ECSQLCodeExamples#working-with-the-query-result) * @public * @deprecated in 4.11 - will not be removed until after 2026-06-13. Use [IModelDb.createQueryReader]($backend) or [ECDb.createQueryReader]($backend) instead. */ export class ECSqlValue { _val; /** @internal */ constructor(val) { this._val = val; } /** Get information about the query result's column this value refers to. */ // eslint-disable-next-line @typescript-eslint/no-deprecated get columnInfo() { return this._val.getColumnInfo(); } /** Get the value of this ECSQL value */ get value() { return ECSqlValueHelper.getValue(this); } /** Indicates whether the value is NULL or not. */ get isNull() { return this._val.isNull(); } /** Get the value as BLOB */ getBlob() { return this._val.getBlob(); } /** Get the value as a boolean value */ getBoolean() { return this._val.getBoolean(); } /** Get the value as a DateTime value (formatted as ISO8601 string) */ getDateTime() { return this._val.getDateTime(); } /** Get the value as a double value */ getDouble() { return this._val.getDouble(); } /** Get the value as a IGeometry value (as ECJSON IGeometry) */ getGeometry() { return JSON.parse(this._val.getGeometry()); } /** Get the value as a GUID (formatted as GUID string). * See [GuidString]($core-bentley) */ getGuid() { return this._val.getGuid(); } /** Get the value as a Id (formatted as hexadecimal string). */ getId() { return this._val.getId(); } /** Get the ClassId value formatted as fully qualified class name. */ getClassNameForClassId() { return this._val.getClassNameForClassId(); } /** Get the value as a integer value */ getInteger() { return this._val.getInt64(); } /** Get the value as a string value */ getString() { return this._val.getString(); } /** Get the value as [XAndY]($core-geometry) */ getXAndY() { return this._val.getPoint2d(); } /** Get the value as [XYAndZ]($core-geometry) */ getXYAndZ() { return this._val.getPoint3d(); } /** Get the value as ECEnumeration value * Note: This method is optional. Using [[ECSqlValue.getInteger]] for integral enums and * [[ECSqlValue.getString]] for string enums respectively are the usual way to get * enum values. This method can be used if the context of the underlying ECEnumeration * is required. * The value is broken down into the ECEnumerators that make it up, if the value * is a combination of ECEnumerators. If the value is not a strict match of an ECEnumerator * or a combination of them, undefined is returned. * > Note: You can call [[ECSqlValue.columnInfo.isEnum]] to find out whether * > this method can be called or not. * @return ECEnumeration value(s) or undefined if the ECSqlValue does not represent an ECEnumeration. * or is not a strict match of an ECEnumerator or a combination of them. */ // eslint-disable-next-line @typescript-eslint/no-deprecated getEnum() { return this._val.getEnum(); } /** Get the value as [NavigationValue]($common) */ getNavigation() { return this._val.getNavigation(); } /** Get an iterator for iterating the struct members of this struct value. */ // eslint-disable-next-line @typescript-eslint/no-deprecated getStructIterator() { return new ECSqlValueIterator(this._val.getStructIterator()); } /** Get this struct value's content as object literal */ getStruct() { return ECSqlValueHelper.getStruct(this); } /** Get an iterator for iterating the array elements of this array value. */ // eslint-disable-next-line @typescript-eslint/no-deprecated getArrayIterator() { return new ECSqlValueIterator(this._val.getArrayIterator()); } /** Get this array value as JavaScript array */ getArray() { return ECSqlValueHelper.getArray(this); } } /** 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.11 - will not be removed until after 2026-06-13. Use [IModelDb.createQueryReader]($backend) or [ECDb.createQueryReader]($backend) instead. */ // eslint-disable-next-line @typescript-eslint/no-deprecated export class ECSqlValueIterator { _it; /** @internal */ constructor(it) { this._it = it; } // eslint-disable-next-line @typescript-eslint/no-deprecated next() { if (this._it.moveNext()) { // eslint-disable-next-line @typescript-eslint/no-deprecated return { done: false, value: new ECSqlValue(this._it.getCurrent()) }; } return { done: true, value: undefined }; } // eslint-disable-next-line @typescript-eslint/no-deprecated [Symbol.iterator]() { return this; } } class ECSqlBindingHelper { /** Binds the specified value to the specified binder * @param binder Parameter Binder to bind to * @param val Value to be bound. (See [iTwin.js Types used in ECSQL Parameter Bindings]($docs/learning/ECSQLParameterTypes)) * @throws IModelError in case of errors */ static bindValue(binder, val) { // returns false if val is no primitive and returns true if it is primitive and a binding call was done if (ECSqlBindingHelper.tryBindPrimitiveTypes(binder, val)) return; if (Array.isArray(val)) { ECSqlBindingHelper.bindArray(binder, val); return; } if (typeof (val) === "object") { ECSqlBindingHelper.bindStruct(binder, val); return; } throw new Error(`Bound value is of an unsupported type: ${val}`); } /** Binds the specified primitive value to the specified binder * @param binder Parameter Binder to bind to * @param val Primitive value to be bound. Must be of one of these types described here: * [ECSQL Binding types]($docs/learning/ECSQLParameterTypes) * @throws IModelError in case of errors */ static bindPrimitive(binder, val) { if (!ECSqlBindingHelper.tryBindPrimitiveTypes(binder, val)) throw new IModelError(DbResult.BE_SQLITE_ERROR, `Binding value is of an unsupported primitive type: ${val}`); } /** Binds the specified object to the specified struct binder * @param binder Struct parameter binder to bind to * @param val Value to be bound. Must be an Object with members of the supported types * @throws IModelError in case of errors */ static bindStruct(binder, val) { if (val === null || val === undefined) { binder.bindNull(); return; } for (const member of Object.entries(val)) { const memberName = member[0]; const memberVal = member[1]; ECSqlBindingHelper.bindValue(binder.bindMember(memberName), memberVal); } } /** Binds the specified array to the specified array binder * @param binder Array parameter binder to bind to * @param val Value to be bound. Must be an Array with elements of the supported types * @throws IModelError in case of errors */ static bindArray(binder, val) { if (val === null || val === undefined) { binder.bindNull(); return; } for (const element of val) { ECSqlBindingHelper.bindValue(binder.addArrayElement(), element); } } /** tries to interpret the passed value as known leaf types (primitives and navigation values). * @returns Returns undefined if the value wasn't a primitive. DbResult if it was a primitive and was bound to the binder */ static tryBindPrimitiveTypes(binder, val) { if (val === undefined || val === null) { binder.bindNull(); return true; } if (typeof (val) === "number") { if (Number.isInteger(val)) binder.bindInteger(val); else binder.bindDouble(val); return true; } if (typeof (val) === "boolean") { binder.bindBoolean(val); return true; } if (typeof (val) === "string") { binder.bindString(val); return true; } if (ECSqlTypeHelper.isBlob(val)) { binder.bindBlob(val); return true; } if (ECSqlTypeHelper.isXYAndZ(val)) { binder.bindPoint3d(val); return true; } if (ECSqlTypeHelper.isXAndY(val)) { binder.bindPoint2d(val); return true; } if (ECSqlTypeHelper.isLowAndHighXYZ(val)) { binder.bindRange3d(val); return true; } if (ECSqlTypeHelper.isNavigationBindingValue(val)) { binder.bindNavigation(val); return true; } return false; } } class ECSqlValueHelper { // eslint-disable-next-line @typescript-eslint/no-deprecated static getValue(ecsqlValue) { if (ecsqlValue.isNull) return undefined; const dataType = ecsqlValue.columnInfo.getType(); switch (dataType) { case ECSqlValueType.Struct: return ECSqlValueHelper.getStruct(ecsqlValue); case ECSqlValueType.Navigation: return ecsqlValue.getNavigation(); case ECSqlValueType.PrimitiveArray: case ECSqlValueType.StructArray: return ECSqlValueHelper.getArray(ecsqlValue); default: return ECSqlValueHelper.getPrimitiveValue(ecsqlValue); } } // eslint-disable-next-line @typescript-eslint/no-deprecated static getStruct(ecsqlValue) { if (ecsqlValue.isNull) return undefined; const structVal = {}; const it = ecsqlValue.getStructIterator(); try { for (const memberECSqlVal of it) { if (memberECSqlVal.isNull) continue; const memberName = ECJsNames.toJsName(memberECSqlVal.columnInfo.getPropertyName()); const memberVal = ECSqlValueHelper.getValue(memberECSqlVal); Object.defineProperty(structVal, memberName, { enumerable: true, configurable: true, writable: true, value: memberVal }); } } finally { } return structVal; } // eslint-disable-next-line @typescript-eslint/no-deprecated static getArray(ecsqlValue) { const arrayVal = []; const it = ecsqlValue.getArrayIterator(); try { for (const elementECSqlVal of it) { const memberVal = ECSqlValueHelper.getValue(elementECSqlVal); arrayVal.push(memberVal); } } finally { } return arrayVal; } // eslint-disable-next-line @typescript-eslint/no-deprecated static getPrimitiveValue(ecsqlValue) { if (ecsqlValue.isNull) return undefined; // eslint-disable-next-line @typescript-eslint/no-deprecated const colInfo = ecsqlValue.columnInfo; switch (colInfo.getType()) { case ECSqlValueType.Blob: return ecsqlValue.getBlob(); case ECSqlValueType.Boolean: return ecsqlValue.getBoolean(); case ECSqlValueType.DateTime: return ecsqlValue.getDateTime(); case ECSqlValueType.Double: return ecsqlValue.getDouble(); case ECSqlValueType.Geometry: return ecsqlValue.getGeometry(); case ECSqlValueType.Guid: return ecsqlValue.getGuid(); case ECSqlValueType.Id: { if (colInfo.isSystemProperty() && colInfo.getPropertyName().endsWith("ECClassId")) return ecsqlValue.getClassNameForClassId(); return ecsqlValue.getId(); } case ECSqlValueType.Int: case ECSqlValueType.Int64: return ecsqlValue.getInteger(); case ECSqlValueType.Point2d: return ecsqlValue.getXAndY(); case ECSqlValueType.Point3d: return ecsqlValue.getXYAndZ(); case ECSqlValueType.String: return ecsqlValue.getString(); default: throw new IModelError(DbResult.BE_SQLITE_ERROR, `Unsupported type ${ecsqlValue.columnInfo.getType()} of the ECSQL Value`); } } static queryClassName(ecdb, classId, tableSpace) { 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) => { stmt.bindId(1, classId); if (stmt.step() !== DbResult.BE_SQLITE_ROW) throw new IModelError(DbResult.BE_SQLITE_ERROR, `No class found with ECClassId ${classId} in table space ${tableSpace}.`); return `${stmt.getValue(0).getString()}.${stmt.getValue(1).getString()}`; }); } } class ECSqlTypeHelper { static isBlob(val) { return val instanceof Uint8Array; } static isXAndY(val) { return XYZ.isXAndY(val); } static isXYAndZ(val) { return XYZ.isXYAndZ(val); } static isLowAndHighXYZ(arg) { return arg.low !== undefined && ECSqlTypeHelper.isXYAndZ(arg.low) && arg.high !== undefined && ECSqlTypeHelper.isXYAndZ(arg.high); } static isNavigationBindingValue(val) { return val.id !== undefined && typeof (val.id) === "string"; } } //# sourceMappingURL=ECSqlStatement.js.map