/*---------------------------------------------------------------------------------------------
* 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);
    }
    /**
     * Used by ECSqlRowExecutor to get row data as json with options determined by request parameters.
     * @internal */
    toRow(args) {
        if (!this._stmt)
            throw new Error("ECSqlStatement is not prepared");
        const resp = this._stmt.toRow(args);
        return resp;
    }
    /**
     * Used by ECSqlRowExecutor to get metadata as json.
     * @internal */
    getMetadata(args) {
        if (!this._stmt)
            throw new Error("ECSqlStatement is not prepared");
        const resp = this._stmt.getMetadata(args);
        return new PropertyMetaDataMap(resp.meta);
    }
    /**
     * Used by ECSqlRowExecutor to bind params to the statement.
     * @internal */
    bindParams(args) {
        if (!this._stmt)
            throw new Error("ECSqlStatement is not prepared");
        this._stmt.reset();
        this._stmt.clearBindings();
        const { status, message } = this._stmt.bindParams(args);
        if (!status)
            throw new Error(`Failed to bind parameters: ${message}`);
    }
    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