UNPKG

@google-cloud/spanner

Version:
1,711 lines 67.3 kB
"use strict";
/*!
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Database = void 0;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const common = require('./common-grpc/service-object');
const promisify_1 = require("@google-cloud/promisify");
const extend = require("extend");
const streamEvents = require("stream-events");
const through = require("through2");
const google_gax_1 = require("google-gax");
const backup_1 = require("./backup");
const batch_transaction_1 = require("./batch-transaction");
const session_factory_1 = require("./session-factory");
const session_1 = require("./session");
const session_pool_1 = require("./session-pool");
const table_1 = require("./table");
const transaction_runner_1 = require("./transaction-runner");
const common_1 = require("./common");
const stream_1 = require("stream");
const helper_1 = require("./helper");
const snakeCase = require("lodash.snakecase");
const instrument_1 = require("./instrument");
const request_id_header_1 = require("./request_id_header");
/**
 * Create a Database object to interact with a Cloud Spanner database.
 *
 * @class
 *
 * @param {string} name Name of the database.
 * @param {SessionPoolOptions|SessionPoolInterface} options Session pool
 *     configuration options or custom pool interface.
 * @param {google.spanner.v1.ExecuteSqlRequest.IQueryOptions} queryOptions
 *     The default query options to use for queries on the database.
 *
 * @example
 * ```
 * const {Spanner} = require('@google-cloud/spanner');
 * const spanner = new Spanner();
 * const instance = spanner.instance('my-instance');
 * const database = instance.database('my-database');
 * ```
 */
class Database extends common.GrpcServiceObject {
    instance;
    formattedName_;
    pool_;
    sessionFactory_;
    queryOptions_;
    isMuxEnabledForRW_;
    commonHeaders_;
    request;
    databaseRole;
    labels;
    databaseDialect;
    _observabilityOptions; // TODO: exmaine if we can remove it
    _traceConfig;
    _nthRequest;
    _clientId;
    constructor(instance, name, poolOptions, queryOptions, databaseRole) {
        const methods = {
            /**
             * Create a database.
             *
             * @method Database#create
             * @param {CreateDatabaseRequest} [options] Configuration object.
             * @param {CreateDatabaseCallback} [callback] Callback function.
             * @returns {Promise<CreateDatabaseResponse>}
             *
             * @example
             * ```
             * const {Spanner} = require('@google-cloud/spanner');
             * const spanner = new Spanner();
             * const instance = spanner.instance('my-instance');
             * const database = instance.database('my-database');
             *
             * database.create(function(err, database, operation, apiResponse) {
             *   if (err) {
             *     // Error handling omitted.
             *   }
             *
             *   operation
             *     .on('error', function(err) {})
             *     .on('complete', function() {
             *       // Database created successfully.
             *     });
             * });
             *
             * //-
             * // If the callback is omitted, we'll return a Promise.
             * //-
             * database.create()
             *   .then(function(data) {
             *     const operation = data[0];
             *     const apiResponse = data[1];
             *
             *     return operation.promise();
             *   })
             *   .then(function() {
             *     // Database created successfully.
             *   });
             * ```
             */
            create: true,
        };
        const formattedName_ = Database.formatName_(instance.formattedName_, name);
        super({
            parent: instance,
            id: name,
            methods,
            createMethod: (_, options, callback) => {
                const pool = this.pool_;
                if (pool._pending > 0) {
                    // If there are BatchCreateSessions requests pending, then we should
                    // wait until these have finished before we try to create the database.
                    // Otherwise the results of these requests might be propagated to
                    // client requests that are submitted after the database has been
                    // created. If the pending requests have not finished within 10 seconds,
                    // they will be ignored and the database creation will proceed.
                    let timeout;
                    const promises = [
                        new Promise(resolve => (timeout = setTimeout(resolve, 10000))),
                        new Promise(resolve => {
                            pool
                                .on('available', () => {
                                if (pool._pending === 0) {
                                    clearTimeout(timeout);
                                    resolve();
                                }
                            })
                                .on('createError', () => {
                                if (pool._pending === 0) {
                                    clearTimeout(timeout);
                                    resolve();
                                }
                            });
                        }),
                    ];
                    Promise.race(promises)
                        .then(() => instance.createDatabase(formattedName_, options, callback))
                        .catch(() => { });
                }
                else {
                    return instance.createDatabase(formattedName_, options, callback);
                }
            },
        });
        this.formattedName_ = formattedName_;
        this.instance = instance;
        const poolOpts = typeof poolOptions === 'object' ? poolOptions : null;
        this.databaseRole = databaseRole || poolOpts?.databaseRole || null;
        this.labels = this._getSpanner().sessionLabels || poolOpts?.labels || null;
        this._observabilityOptions = instance._observabilityOptions;
        this._traceConfig = {
            opts: this._observabilityOptions,
            dbName: this.formattedName_,
        };
        this.request = instance.request;
        this._nthRequest = (0, request_id_header_1.newAtomicCounter)(0);
        if (this.parent && this.parent.parent) {
            this._clientId = this.parent.parent._nthClientId;
        }
        else {
            this._clientId = instance._nthClientId;
        }
        this._observabilityOptions = instance._observabilityOptions;
        this.commonHeaders_ = {
            ...instance.commonHeaders_,
            [common_1.CLOUD_RESOURCE_HEADER]: this.formattedName_,
        };
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.requestStream = instance.requestStream;
        this.sessionFactory_ = new session_factory_1.SessionFactory(this, name, poolOptions);
        this.pool_ = this.sessionFactory_.getPool();
        this.isMuxEnabledForRW_ = this.sessionFactory_.isMultiplexedEnabledForRW();
        const sessionPoolInstance = this.pool_;
        if (sessionPoolInstance) {
            sessionPoolInstance._observabilityOptions =
                instance._observabilityOptions;
        }
        this.queryOptions_ = Object.assign(Object.assign({}, queryOptions), Database.getEnvironmentQueryOptions());
    }
    _nextNthRequest() {
        return this._nthRequest.increment();
    }
    setMetadata(metadata, optionsOrCallback, cb) {
        const gaxOpts = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        const reqOpts = {
            database: Object.assign({
                name: this.formattedName_,
            }, metadata),
            updateMask: {
                paths: Object.keys(metadata).map(snakeCase),
            },
        };
        return this.request({
            client: 'DatabaseAdminClient',
            method: 'updateDatabase',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, callback);
    }
    static getEnvironmentQueryOptions() {
        const options = {};
        if (process.env.SPANNER_OPTIMIZER_VERSION) {
            options.optimizerVersion = process.env.SPANNER_OPTIMIZER_VERSION;
        }
        if (process.env.SPANNER_OPTIMIZER_STATISTICS_PACKAGE) {
            options.optimizerStatisticsPackage =
                process.env.SPANNER_OPTIMIZER_STATISTICS_PACKAGE;
        }
        return options;
    }
    batchCreateSessions(options, callback) {
        if (typeof options === 'number') {
            options = { count: options };
        }
        const count = options.count;
        const labels = options.labels || this.labels || null;
        const databaseRole = options.databaseRole || this.databaseRole || null;
        const reqOpts = {
            database: this.formattedName_,
            sessionTemplate: { labels: labels, creatorRole: databaseRole },
            sessionCount: count,
        };
        const headers = this.commonHeaders_;
        if (this._getSpanner().routeToLeaderEnabled) {
            (0, common_1.addLeaderAwareRoutingHeader)(headers);
        }
        const allHeaders = this._metadataWithRequestId(this._nextNthRequest(), 1, headers);
        (0, instrument_1.startTrace)('Database.batchCreateSessions', this._traceConfig, span => {
            this.request({
                client: 'SpannerClient',
                method: 'batchCreateSessions',
                reqOpts,
                gaxOpts: options.gaxOptions,
                headers: allHeaders,
            }, (err, resp) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    span.end();
                    callback(err, null, resp);
                    return;
                }
                const sessions = (resp.session || []).map(metadata => {
                    const session = this.session(metadata.name);
                    session._observabilityOptions = this._traceConfig.opts;
                    session.metadata = metadata;
                    return session;
                });
                span.end();
                callback(null, sessions, resp);
            });
        });
    }
    _metadataWithRequestId(nthRequest, attempt, priorMetadata) {
        if (!priorMetadata) {
            priorMetadata = {};
        }
        const withReqId = {
            ...priorMetadata,
        };
        withReqId[request_id_header_1.X_GOOG_SPANNER_REQUEST_ID_HEADER] = (0, request_id_header_1.craftRequestId)(this._clientId || 1, 1, // TODO: Properly infer the channelId
        nthRequest, attempt);
        return withReqId;
    }
    /**
     * Get a reference to a {@link BatchTransaction} object.
     *
     * @see {@link BatchTransaction#identifier} to generate an identifier.
     *
     * @param {TransactionIdentifier} identifier The transaction identifier.
     * @param {object} [options] [Transaction options](https://cloud.google.com/spanner/docs/timestamp-bounds).
     * @returns {BatchTransaction} A batch transaction object.
     *
     * @example
     * ```
     * const {Spanner} = require('@google-cloud/spanner');
     * const spanner = new Spanner();
     *
     * const instance = spanner.instance('my-instance');
     * const database = instance.database('my-database');
     *
     * const transaction = database.batchTransaction({
     *   session: 'my-session',
     *   transaction: 'my-transaction',
     *   readTimestamp: 1518464696657
     * });
     * ```
     */
    batchTransaction(identifier, options) {
        const session = typeof identifier.session === 'string'
            ? this.session(identifier.session)
            : identifier.session;
        const id = identifier.transaction;
        const transaction = new batch_transaction_1.BatchTransaction(session, options);
        transaction.id = id;
        transaction._observabilityOptions = this._traceConfig.opts;
        transaction.readTimestamp = identifier.timestamp;
        return transaction;
    }
    close(callback) {
        const key = this.id.split('/').pop();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.parent.databases_.delete(key);
        this.pool_.close(callback);
    }
    createBatchTransaction(optionsOrCallback, cb) {
        const callback = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : cb;
        const options = typeof optionsOrCallback === 'object'
            ? optionsOrCallback
            : {};
        return (0, instrument_1.startTrace)('Database.createBatchTransaction', this._traceConfig, span => {
            this.sessionFactory_.getSession((err, session) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    span.end();
                    callback(err, null, undefined);
                    return;
                }
                const transaction = this.batchTransaction({ session: session }, options);
                this._releaseOnEnd(session, transaction, span);
                transaction.begin((err, resp) => {
                    if (err) {
                        (0, instrument_1.setSpanError)(span, err);
                        if ((0, session_pool_1.isSessionNotFoundError)(err)) {
                            span.addEvent('No session available', {
                                'session.id': session?.id,
                            });
                        }
                        span.end();
                        callback(err, null, resp);
                        return;
                    }
                    span.addEvent('Using Session', { 'session.id': session?.id });
                    span.end();
                    callback(null, transaction, resp);
                });
            });
        });
    }
    createSession(optionsOrCallback, cb) {
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        const options = typeof optionsOrCallback === 'object' && optionsOrCallback
            ? Object.assign({}, optionsOrCallback)
            : {};
        const reqOpts = {
            database: this.formattedName_,
        };
        reqOpts.session = {};
        if (options.multiplexed) {
            reqOpts.session.multiplexed = options.multiplexed;
        }
        reqOpts.session.labels = options.labels || this.labels || null;
        reqOpts.session.creatorRole =
            options.databaseRole || this.databaseRole || null;
        const headers = this._metadataWithRequestId(this._nextNthRequest(), 1, this.commonHeaders_);
        if (this._getSpanner().routeToLeaderEnabled) {
            (0, common_1.addLeaderAwareRoutingHeader)(headers);
        }
        (0, instrument_1.startTrace)('Database.createSession', this._traceConfig, span => {
            this.request({
                client: 'SpannerClient',
                method: 'createSession',
                reqOpts,
                gaxOpts: options.gaxOptions,
                headers: headers,
            }, (err, resp) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    span.end();
                    callback(err, null, resp);
                    return;
                }
                const session = this.session(resp.name);
                session.metadata = resp;
                session._observabilityOptions = this._traceConfig.opts;
                span.end();
                callback(null, session, resp);
            });
        });
    }
    createTable(schema, gaxOptionsOrCallback, cb) {
        const gaxOptions = typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
        const callback = typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb;
        this.updateSchema(schema, gaxOptions, (err, operation, resp) => {
            if (err) {
                callback(err, null, null, resp);
                return;
            }
            const tableName = schema.match(/CREATE TABLE `*([^\s`(]+)/)[1];
            const table = this.table(tableName);
            table._observabilityOptions = this._traceConfig.opts;
            callback(null, table, operation, resp);
        });
    }
    /**
     * Decorates transaction so that when end() is called it will return the session
     * back into the pool.
     *
     * @private
     *
     * @param {Session} session The session to release.
     * @param {Transaction} transaction The transaction to observe.
     * @returns {Transaction}
     */
    _releaseOnEnd(session, transaction, span) {
        transaction.once('end', () => {
            try {
                this.sessionFactory_.release(session);
            }
            catch (e) {
                (0, instrument_1.setSpanErrorAndException)(span, e);
                this.emit('error', e);
            }
            finally {
                span.end();
            }
        });
    }
    delete(optionsOrCallback, cb) {
        const gaxOpts = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        const reqOpts = {
            database: this.formattedName_,
        };
        this.close(() => {
            this.request({
                client: 'DatabaseAdminClient',
                method: 'dropDatabase',
                reqOpts,
                gaxOpts,
                headers: this.commonHeaders_,
            }, callback);
        });
    }
    exists(gaxOptionsOrCallback, cb) {
        const gaxOptions = typeof gaxOptionsOrCallback === 'object' ? gaxOptionsOrCallback : {};
        const callback = typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb;
        const NOT_FOUND = 5;
        this.getMetadata(gaxOptions, err => {
            if (err && err.code !== NOT_FOUND) {
                callback(err);
                return;
            }
            const exists = !err || err.code !== NOT_FOUND;
            callback(null, exists);
        });
    }
    get(optionsOrCallback, cb) {
        const options = typeof optionsOrCallback === 'object'
            ? optionsOrCallback
            : {};
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        this.getMetadata(options.gaxOptions, (err, metadata) => {
            if (err) {
                if (options.autoCreate && err.code === 5) {
                    this.create(options, (err, database, operation) => {
                        if (err) {
                            callback(err);
                            return;
                        }
                        operation
                            .on('error', callback)
                            .on('complete', (metadata) => {
                            this.metadata = metadata;
                            callback(null, this, metadata);
                        });
                    });
                    return;
                }
                callback(err);
                return;
            }
            callback(null, this, metadata);
        });
    }
    getMetadata(gaxOptionsOrCallback, cb) {
        const callback = typeof gaxOptionsOrCallback === 'function'
            ? gaxOptionsOrCallback
            : cb;
        const gaxOpts = typeof gaxOptionsOrCallback === 'object'
            ? gaxOptionsOrCallback
            : {};
        const reqOpts = {
            name: this.formattedName_,
        };
        return this.request({
            client: 'DatabaseAdminClient',
            method: 'getDatabase',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, (err, resp) => {
            if (resp) {
                this.metadata = resp;
            }
            callback(err, resp);
        });
    }
    async getRestoreInfo(optionsOrCallback) {
        const gaxOptions = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const [metadata] = await this.getMetadata(gaxOptions);
        return metadata.restoreInfo ? metadata.restoreInfo : undefined;
    }
    async getState(optionsOrCallback) {
        const gaxOptions = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const [metadata] = await this.getMetadata(gaxOptions);
        return metadata.state || undefined;
    }
    async getDatabaseDialect(optionsOrCallback, callback) {
        const gaxOptions = typeof optionsOrCallback === 'object'
            ? optionsOrCallback
            : {};
        const cb = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : callback;
        try {
            if (this.databaseDialect === 'DATABASE_DIALECT_UNSPECIFIED' ||
                this.databaseDialect === null ||
                this.databaseDialect === undefined) {
                const [metadata] = await this.getMetadata(gaxOptions);
                this.databaseDialect = metadata.databaseDialect;
            }
            if (cb) {
                cb(null, this.databaseDialect);
                return;
            }
            return this.databaseDialect || undefined;
        }
        catch (err) {
            cb(err);
            return;
        }
    }
    getSchema(optionsOrCallback, cb) {
        const gaxOpts = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        const reqOpts = {
            database: this.formattedName_,
        };
        this.request({
            client: 'DatabaseAdminClient',
            method: 'getDatabaseDdl',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, 
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (err, statements, ...args) => {
            callback(err, statements ? statements.statements : null, ...args);
        });
    }
    getIamPolicy(optionsOrCallback, cb) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        const reqOpts = {
            resource: this.formattedName_,
            options: {
                requestedPolicyVersion: options.requestedPolicyVersion || null,
            },
        };
        this.request({
            client: 'DatabaseAdminClient',
            method: 'getIamPolicy',
            reqOpts,
            gaxOpts: options.gaxOptions,
            headers: this.commonHeaders_,
        }, (err, resp) => {
            callback(err, resp);
        });
    }
    getSessions(optionsOrCallback, cb) {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const self = this;
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        const options = typeof optionsOrCallback === 'object'
            ? optionsOrCallback
            : {};
        const gaxOpts = extend(true, {}, options.gaxOptions);
        const reqOpts = Object.assign({}, options, {
            database: this.formattedName_,
        });
        delete reqOpts.gaxOptions;
        // Copy over pageSize and pageToken values from gaxOptions.
        // However values set on options take precedence.
        if (gaxOpts) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.pageToken;
            }
        }
        const headers = this._metadataWithRequestId(this._nextNthRequest(), 1, this.commonHeaders_);
        return (0, instrument_1.startTrace)('Database.getSessions', this._traceConfig, span => {
            this.request({
                client: 'SpannerClient',
                method: 'listSessions',
                reqOpts,
                gaxOpts,
                headers: headers,
            }, (err, sessions, nextPageRequest, ...args) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                }
                let sessionInstances = null;
                if (sessions) {
                    sessionInstances = sessions.map(metadata => {
                        const session = self.session(metadata.name);
                        session.metadata = metadata;
                        session._observabilityOptions = this._traceConfig.opts;
                        return session;
                    });
                }
                span.end();
                const nextQuery = nextPageRequest
                    ? Object.assign({}, options, nextPageRequest)
                    : null;
                callback(err, sessionInstances, nextQuery, ...args);
            });
        });
    }
    /**
     * Get a list of sessions as a readable object stream.
     *
     * Wrapper around {@link v1.SpannerClient#listSessions}
     *
     * @see {@link v1.SpannerClient#listSessions}
     * @see [ListSessions API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.Spanner.ListSessions)
     *
     * @method Spanner#getSessionsStream
     * @param {GetSessionsOptions} [options] Options object for listing sessions.
     * @returns {ReadableStream} A readable stream that emits {@link Session}
     *     instances.
     *
     * @example
     * ```
     * const {Spanner} = require('@google-cloud/spanner');
     * const spanner = new Spanner();
     *
     * const instance = spanner.instance('my-instance');
     * const database = instance.database('my-database');
     *
     * database.getSessionsStream()
     *   .on('error', console.error)
     *   .on('data', function(database) {
     *     // `sessions` is a `Session` object.
     *   })
     *   .on('end', function() {
     *     // All sessions retrieved.
     *   });
     *
     * //-
     * // If you anticipate many results, you can end a stream early to prevent
     * // unnecessary processing and API requests.
     * //-
     * database.getSessionsStream()
     *   .on('data', function(session) {
     *     this.end();
     *   });
     * ```
     */
    getSessionsStream(options = {}) {
        const gaxOpts = extend(true, {}, options.gaxOptions);
        const reqOpts = Object.assign({}, options, {
            database: this.formattedName_,
        });
        delete reqOpts.gaxOptions;
        // Copy over pageSize and pageToken values from gaxOptions.
        // However values set on options take precedence.
        if (gaxOpts) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.pageToken;
            }
        }
        return this.requestStream({
            client: 'SpannerClient',
            method: 'listSessionsStream',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        });
    }
    getSnapshot(optionsOrCallback, cb) {
        const callback = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : cb;
        const options = typeof optionsOrCallback === 'object'
            ? optionsOrCallback
            : {};
        if (('maxStaleness' in options &&
            options.maxStaleness !== null &&
            options.maxStaleness !== undefined) ||
            ('minReadTimestamp' in options &&
                options.minReadTimestamp !== null &&
                options.minReadTimestamp !== undefined)) {
            const error = Object.assign(new Error('maxStaleness / minReadTimestamp is not supported for multi-use read-only transactions.'), {
                code: 3, // invalid argument
            });
            callback(error);
            return;
        }
        return (0, instrument_1.startTrace)('Database.getSnapshot', this._traceConfig, span => {
            this.sessionFactory_.getSession((err, session) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    span.end();
                    callback(err);
                    return;
                }
                const snapshot = session.snapshot(options, this.queryOptions_);
                snapshot.begin(err => {
                    if (err) {
                        (0, instrument_1.setSpanError)(span, err);
                        if ((0, session_pool_1.isSessionNotFoundError)(err) &&
                            !this.sessionFactory_.isMultiplexedEnabled()) {
                            span.addEvent('No session available', {
                                'session.id': session?.id,
                            });
                            session.lastError = err;
                            this.sessionFactory_.release(session);
                            span.end();
                            this.getSnapshot(options, callback);
                        }
                        else {
                            span.addEvent('Using Session', { 'session.id': session?.id });
                            this.sessionFactory_.release(session);
                            span.end();
                            callback(err);
                        }
                        return;
                    }
                    this._releaseOnEnd(session, snapshot, span);
                    span.end();
                    callback(err, snapshot);
                });
            });
        });
    }
    getTransaction(optionsOrCallback, callback) {
        const cb = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : callback;
        const options = typeof optionsOrCallback === 'object' && optionsOrCallback
            ? optionsOrCallback
            : {};
        return (0, instrument_1.startTrace)('Database.getTransaction', {
            ...this._traceConfig,
            transactionTag: options.requestOptions?.transactionTag,
        }, span => {
            this.sessionFactory_.getSessionForReadWrite((err, session, transaction) => {
                if (!err) {
                    if (options.requestOptions) {
                        transaction.requestOptions = Object.assign(transaction.requestOptions || {}, options.requestOptions);
                    }
                    transaction?.setReadWriteTransactionOptions(options);
                    span.addEvent('Using Session', { 'session.id': session?.id });
                    transaction._observabilityOptions = this._observabilityOptions;
                    this._releaseOnEnd(session, transaction, span);
                }
                else {
                    (0, instrument_1.setSpanError)(span, err);
                }
                span.end();
                cb(err, transaction);
            });
        });
    }
    async getOperations(optionsOrCallback) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        // Create a query that lists database operations only on this database from
        // the instance. Operation name will be prefixed with the database path for
        // all operations on this database
        let dbSpecificFilter = `name:${this.formattedName_}`;
        if (options && options.filter) {
            dbSpecificFilter = `(${dbSpecificFilter}) AND (${options.filter})`;
        }
        const dbSpecificQuery = {
            ...options,
            filter: dbSpecificFilter,
        };
        return this.instance.getDatabaseOperations(dbSpecificQuery);
    }
    getDatabaseRoles(optionsOrCallback, cb) {
        const gaxOpts = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        const reqOpts = {
            parent: this.formattedName_,
        };
        // Copy over pageSize and pageToken values from gaxOptions.
        // However, values set on options take precedence.
        if (gaxOpts) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.pageToken;
            }
        }
        this.request({
            client: 'DatabaseAdminClient',
            method: 'listDatabaseRoles',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, (err, roles, nextPageRequest, ...args) => {
            const nextQuery = nextPageRequest
                ? Object.assign({}, gaxOpts, nextPageRequest)
                : null;
            callback(err, roles, nextQuery, ...args);
        });
    }
    makePooledRequest_(config, callback) {
        const sessionFactory_ = this.sessionFactory_;
        sessionFactory_.getSessionForReadWrite((err, session) => {
            if (err) {
                callback(err, null);
                return;
            }
            const span = (0, instrument_1.getActiveOrNoopSpan)();
            span.addEvent('Using Session', { 'session.id': session?.id });
            config.reqOpts.session = session.formattedName_;
            this.request(config, (err, ...args) => {
                sessionFactory_.release(session);
                callback(err, ...args);
            });
        });
    }
    /**
     * Make an API request as a stream, first assuring an active session is used.
     *
     * @private
     *
     * @param {object} config Request config
     * @returns {Stream}
     */
    makePooledStreamingRequest_(config) {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const self = this;
        const sessionFactory_ = this.sessionFactory_;
        let requestStream;
        let session;
        const waitForSessionStream = streamEvents(through.obj());
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        waitForSessionStream.abort = () => {
            releaseSession();
            if (requestStream) {
                requestStream.cancel();
            }
        };
        function destroyStream(err) {
            waitForSessionStream.destroy(err);
        }
        function releaseSession() {
            if (session) {
                sessionFactory_.release(session);
                session = null;
            }
        }
        waitForSessionStream.on('reading', () => {
            sessionFactory_.getSession((err, session_) => {
                const span = (0, instrument_1.getActiveOrNoopSpan)();
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    destroyStream(err);
                    return;
                }
                span.addEvent('Using Session', { 'session.id': session_?.id });
                session = session_;
                config.reqOpts.session = session.formattedName_;
                requestStream = self.requestStream(config);
                requestStream
                    .on('error', releaseSession)
                    .on('error', destroyStream)
                    .on('end', releaseSession)
                    .pipe(waitForSessionStream);
            });
        });
        return waitForSessionStream;
    }
    restore(backupName, optionsOrCallback, cb) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : cb;
        const gaxOpts = 'gaxOptions' in options
            ? options.gaxOptions
            : options;
        const reqOpts = {
            parent: this.instance.formattedName_,
            databaseId: this.id,
            backup: backup_1.Backup.formatName_(this.instance.formattedName_, backupName),
        };
        if ('encryptionConfig' in options &&
            options.encryptionConfig) {
            reqOpts.encryptionConfig = options.encryptionConfig;
        }
        return this.request({
            client: 'DatabaseAdminClient',
            method: 'restoreDatabase',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, (err, operation, resp) => {
            if (err) {
                callback(err, null, null, resp);
                return;
            }
            callback(null, this, operation, resp);
        });
    }
    run(query, optionsOrCallback, cb) {
        let stats;
        let metadata;
        const rows = [];
        const callback = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : cb;
        const options = typeof optionsOrCallback === 'object'
            ? optionsOrCallback
            : {};
        return (0, instrument_1.startTrace)('Database.run', {
            ...query,
            ...this._traceConfig,
        }, span => {
            this.runStream(query, options)
                .on('error', err => {
                (0, instrument_1.setSpanError)(span, err);
                span.end();
                callback(err, rows, stats, metadata);
            })
                .on('response', response => {
                if (response.metadata) {
                    metadata = response.metadata;
                }
            })
                .on('stats', _stats => (stats = _stats))
                .on('data', row => {
                rows.push(row);
            })
                .on('end', () => {
                span.end();
                callback(null, rows, stats, metadata);
            });
        });
    }
    runPartitionedUpdate(query, callback) {
        return (0, instrument_1.startTrace)('Database.runPartitionedUpdate', {
            ...query,
            ...this._traceConfig,
            requestTag: query?.requestOptions
                ?.requestTag,
        }, span => {
            this.sessionFactory_.getSessionForPartitionedOps((err, session) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    span.end();
                    callback(err, 0);
                    return;
                }
                void this._runPartitionedUpdate(session, query, (err, count) => {
                    if (err) {
                        (0, instrument_1.setSpanError)(span, err);
                    }
                    span.end();
                    callback(err, count);
                });
            });
        });
    }
    _runPartitionedUpdate(session, query, callback) {
        const transaction = session.partitionedDml();
        if (typeof query !== 'string' && query.excludeTxnFromChangeStreams) {
            transaction.excludeTxnFromChangeStreams();
        }
        transaction.begin(err => {
            if (err) {
                this.sessionFactory_.release(session);
                callback(err, 0);
                return;
            }
            transaction.runUpdate(query, async (err, updateCount) => {
                if (err) {
                    if (err.code !== google_gax_1.grpc.status.ABORTED) {
                        this.sessionFactory_.release(session);
                        callback(err, 0);
                        return;
                    }
                    void this._runPartitionedUpdate(session, query, callback);
                }
                else {
                    this.sessionFactory_.release(session);
                    callback(null, updateCount);
                    return;
                }
            });
        });
    }
    /**
     * Create a readable object stream to receive resulting rows from a SQL
     * statement.
     *
     * Wrapper around {@link v1.SpannerClient#executeStreamingSql}.
     *
     * @see {@link v1.SpannerClient#executeStreamingSql}
     * @see [Query Syntax](https://cloud.google.com/spanner/docs/query-syntax)
     * @see [ExecuteSql API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.Spanner.ExecuteSql)
     *
     * @fires PartialResultStream#response
     *
     * @param {string|ExecuteSqlRequest} query A SQL query or
     *     {@link ExecuteSqlRequest} object.
     * @param {TimestampBounds} [options] Snapshot timestamp bounds.
     * @returns {PartialResultStream} A readable stream that emits rows.
     *
     * @example
     * ```
     * const {Spanner} = require('@google-cloud/spanner');
     * const spanner = new Spanner();
     *
     * const instance = spanner.instance('my-instance');
     * const database = instance.database('my-database');
     *
     * const query = 'SELECT * FROM Singers';
     *
     * database.runStream(query)
     *   .on('error', function(err) {})
     *   .on('data', function(row) {
     *     // row = [
     *     //   {
     *     //     name: 'SingerId',
     *     //     value: '1'
     *     //   },
     *     //   {
     *     //     name: 'Name',
     *     //     value: 'Eddie Wilson'
     *     //   }
     *     // ]
     *   // ]
     *   })
     *   .on('end', function() {
     *     // All results retrieved.
     *   });
     *
     * //-
     * // Rows are returned as an array of objects. Each object has a `name` and
     * // `value` property. To get a serialized object, call `toJSON()`.
     * //-
     * database.runStream(query)
     *   .on('error', function(err) {})
     *   .on('data', function(row) {
     *     // row.toJSON() = {
     *     //   SingerId: '1',
     *     //   Name: 'Eddie Wilson'
     *     // }
     *   })
     *   .on('end', function() {
     *     // All results retrieved.
     *   });
     *
     * //-
     * // Alternatively, set `query.json` to `true`, and this step will be performed
     * // automatically.
     * //-
     * query.json = true;
     *
     * database.runStream(query)
     *   .on('error', function(err) {})
     *   .on('data', function(row) {
     *     // row = {
     *     //   SingerId: '1',
     *     //   Name: 'Eddie Wilson'
     *     // }
     *   })
     *   .on('end', function() {
     *     // All results retrieved.
     *   });
     *
     * //-
     * // The SQL query string can contain parameter placeholders. A parameter
     * // placeholder consists of '@' followed by the parameter name.
     * //-
     * const query = {
     *   sql: 'SELECT * FROM Singers WHERE name = @name',
     *   params: {
     *     name: 'Eddie Wilson'
     *   }
     * };
     *
     * database.runStream(query)
     *   .on('error', function(err) {})
     *   .on('data', function(row) {})
     *   .on('end', function() {});
     *
     * //-
     * // If you need to enforce a specific param type, a types map can be provided.
     * // This is typically useful if your param value can be null.
     * //-
     * const query = {
     *   sql: 'SELECT * FROM Singers WHERE name = @name',
     *   params: {
     *     name: 'Eddie Wilson'
     *   },
     *   types: {
     *     name: 'string'
     *   }
     * };
     *
     * database.runStream(query)
     *   .on('error', function(err) {})
     *   .on('data', function(row) {})
     *   .on('end', function() {});
     *
     * //-
     * // If you anticipate many results, you can end a stream early to prevent
     * // unnecessary processing and API requests.
     * //-
     * database.runStream(query)
     *   .on('data', function(row) {
     *     this.end();
     *   });
     * ```
     */
    runStream(query, options) {
        const proxyStream = through.obj();
        return (0, instrument_1.startTrace)('Database.runStream', {
            ...query,
            ...this._traceConfig,
            requestTag: query?.requestOptions?.requestTag,
        }, span => {
            this.sessionFactory_.getSession((err, session) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    proxyStream.destroy(err);
                    span.end();
                    return;
                }
                span.addEvent('Using Session', { 'session.id': session?.id });
                const snapshot = session.snapshot(options, this.queryOptions_);
                this._releaseOnEnd(session, snapshot, span);
                let dataReceived = false;
                let dataStream = snapshot.runStream(query);
                const endListener = () => {
                    snapshot.end();
                };
                dataStream
                    .once('data', () => (dataReceived = true))
                    .once('error', err => {
                    (0, instrument_1.setSpanError)(span, err);
                    if (!dataReceived &&
                        (0, session_pool_1.isSessionNotFoundError)(err) &&
                        !this.sessionFactory_.isMultiplexedEnabled()) {
                        // If it is a 'Session not found' error and we have not yet received
                        // any data, we can safely retry the query on a new session.
                        // Register the error on the session so the pool can discard it.
                        if (session) {
                            session.lastError = err;
                        }
                        span.addEvent('No session available', {
                            'session.id': session?.id,
                        });
                        // Remove the current data stream from the end user stream.
                        dataStream.unpipe(proxyStream);
                        dataStream.removeListener('end', endListener);
                        dataStream.end();
                        snapshot.end();
                        span.end();
                        // Create a new data stream and add it to the end user stream.
                        dataStream = this.runStream(query, options);
                        dataStream.pipe(proxyStream);
                    }
                    else {
                        proxyStream.destroy(err);
                        snapshot.end();
                    }
                })
                    .on('stats', stats => proxyStream.emit('stats', stats))
                    .on('response', response => proxyStream.emit('response', response))
                    .once('end', endListener)
                    .pipe(proxyStream);
            });
            (0, stream_1.finished)(proxyStream, err => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                }
                span.end();
            });
            return proxyStream;
        });
    }
    runTransaction(optionsOrRunFn, fn) {
        const runFn = typeof optionsOrRunFn === 'function'
            ? optionsOrRunFn
            : fn;
        const options = typeof optionsOrRunFn === 'object' && optionsOrRunFn
            ? optionsOrRunFn
            : {};
        (0, instrument_1.startTrace)('Database.runTransaction', {
            ...this._traceConfig,
            transactionTag: options.requestOptions?.transactionTag,
        }, span => {
            this.sessionFactory_.getSessionForReadWrite((err, session, transaction) => {
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                }
                if (err && (0, session_pool_1.isSessionNotFoundError)(err)) {
                    span.addEvent('No session available', {
                        'session.id': session?.id,
                    });
                    span.end();
                    this.runTransaction(options, runFn);
                    return;
                }
                if (err) {
                    span.end();
                    runFn(err);
                    return;
                }
                transaction._observabilityOptions = this._observabilityOptions;
                transaction.requestOptions = Object.assign(transaction.requestOptions || {}, options.requestOptions);
                transaction.setReadWriteTransactionOptions(options);
                const release = () => {
                    this.sessionFactory_.release(session);
                    span.end();
                };
                const runner = new transaction_runner_1.TransactionRunner(session, transaction, runFn, options);
                runner.run().then(release, err => {
                    (0, instrument_1.setSpanError)(span, err);
                    if ((0, session_pool_1.isSessionNotFoundError)(err)) {
                        span.addEvent('No session available', {
                            'session.id': session?.id,
                        });
                        release();
                        this.runTransaction(options, runFn);
                    }
                    else {
                        setImmediate(runFn, err);
                        release();
                    }
                });
            });
        });
    }
    /**
     * @callback AsyncRunTransactionCallback
     * @param {Transaction} transaction The transaction object. The transaction has
     *     already been created, and is ready to be queried and committed against.
     */
    /**
     * A transaction in Cloud Spanner is a set of reads and writes that execute
     * atomically at a single logical point in time across columns, rows, and tables
     * in a database.
     *
     * Note that Cloud Spanner does not support nested transactions. If a new
     * transaction is started inside of the run function, it will be an independent
     * transaction.
     *
     * The async function you provide will become the "run function". It
     * will be executed with a {@link Transaction}
     * object. The Transaction object will let you run queries and queue mutations
     * until you are ready to {@link Transaction#commit}.
     *
     * In the event that an aborted error occurs, we will re-run the `runFn` in its
     * entirety. If you prefer to handle aborted errors for yourself please refer to
     * {@link Database#getTransaction}.
     *
     * **NOTE:** In the event that you encounter an error while reading/writing,
     * if you decide to forgo calling {@link Transaction#commit} or
     * {@link Transaction#rollback}, then you need to call
     * {@link Transaction#end} to release the underlying {@link Session} object.
     * **Failure to do could result in a Session leak.**
     *
     * For a more complete listing of functionality available to a Transaction, see
     * the {@link Transaction} API documentation. For a general overview of
     * transactions within Cloud Spanner, see
     * [Transactions](https://cloud.google.com/spanner/docs/transactions) from the
     * official Cloud Spanner documentation.
     *
     * @see [Transactions](https://cloud.google.com/spanner/docs/transactions)
     *
     * @param {RunTransactionOptions} [options] Transaction runner options.
     * @param {AsyncRunTransactionCallback} callback A function to execute in the
     *      context of a transaction.
     * @returns {Promise}
     *
     * @example
     * ```
     * const {Spanner} = require('@google-cloud/spanner');
     * const spanner = new Spanner();
     *
     * const instance = spanner.instance('my-instance');
     * const database = instance.database('my-database');
     *
     * const data = await database.runTransactionAsync(async (transaction) => {
     *   const [rows] = await transaction.run('SELECT * FROM MyTable');
     *   const data = rows.map(row => row.thing);
     *
     *   await transaction.commit();
     *   return data;
     * });
     * ```
     */
    async runTransactionAsync(optionsOrRunFn, fn) {
        const runFn = typeof optionsOrRunFn === 'function'
            ? optionsOrRunFn
            : fn;
        const options = typeof optionsOrRunFn === 'object'
            ? optionsOrRunFn
            : {};
        let sessionId = '';
        const getSession = this.sessionFactory_.getSessionForReadWrite.bind(this.sessionFactory_);
        return (0, instrument_1.startTrace)('Database.runTransactionAsync', {
            ...this._traceConfig,
            transactionTag: options?.requestOptions?.transactionTag,
        }, async (span) => {
            // Loop to retry 'Session not found' errors.
            // (and yes, we like while (true) more than for (;;) here)
            // eslint-disable-next-line no-constant-condition
            while (true) {
                try {
                    const [session, transaction] = await (0, promisify_1.promisify)(getSession)();
                    transaction.requestOptions = Object.assign(transaction.requestOptions || {}, options.requestOptions);
                    transaction.setReadWriteTransactionOptions(options);
                    sessionId = session?.id;
                    span.addEvent('Using Session', { 'session.id': sessionId });
                    const runner = new transaction_runner_1.AsyncTransactionRunner(session, transaction, runFn, options);
                    try {
                        return await runner.run();
                    }
                    catch (e) {
                        (0, instrument_1.setSpanErrorAndException)(span, e);
                        throw e;
                    }
                    finally {
                        span.end();
                        this.sessionFactory_.release(session);
                    }
                }
                catch (e) {
                    if ((0, session_pool_1.isSessionNotFoundError)(e)) {
                        span.addEvent('No session available', {
                            'session.id': sessionId,
                        });
                    }
                    else {
                        span.end();
                        throw e;
                    }
                }
            }
        });
    }
    /**
     * Write a batch of mutations to Spanner.
     *
     * All mutations in a group are committed atomically. However, mutations across
     * groups can be committed non-atomically in an unspecified order and thus, they
     * must be independent of each other. Partial failure is possible, i.e., some groups
     * may have been committed successfully, while some may have failed. The results of
     * individual batches are streamed into the response as the batches are applied.
     *
     * batchWriteAtLeastOnce requests are not replay protected, meaning that each mutation group may
     * be applied more than once. Replays of non-idempotent mutations may have undesirable
     * effects. For example, replays of an insert mutation may produce an already exists
     * error or if you use generated or commit timestamp-based keys, it may result in additional
     * rows being added to the mutation's table. We recommend structuring your mutation groups to
     * be idempotent to avoid this issue.
     *
     * @method Spanner#batchWriteAtLeastOnce
     *
     * @param {MutationGroup[]} [mutationGroups] The group of mutations to be applied.
     * @param {BatchWriteOptions} [options] Options object for batch write request.
     *
     * @returns {ReadableStream} An object stream which emits
     *   {@link protos.google.spanner.v1.BatchWriteResponse|BatchWriteResponse}
     *  on 'data' event.
     *
     * @example
     * ```
     * const {Spanner} = require('@google-cloud/spanner');
     * const spanner = new Spanner();
     *
     * const instance = spanner.instance('my-instance');
     * const database = instance.database('my-database');
     * const mutationGroup = new MutationGroup();
     * mutationGroup.insert('Singers', {
     *  SingerId: '1',
     *  FirstName: 'Marc',
     *  LastName: 'Richards',
     *  });
     *
     * database.batchWriteAtLeastOnce([mutationGroup])
     *   .on('error', console.error)
     *   .on('data', response => {
     *        console.log('response: ', response);
     *   })
     *   .on('end', () => {
     *        console.log('Request completed successfully');
     *   });
     *
     * //-
     * // If you anticipate many results, you can end a stream early to prevent
     * // unnecessary processing and API requests.
     * //-
     * database.batchWriteAtLeastOnce()
     *   .on('data', response => {
     *     this.end();
     *   });
     * ```
     */
    batchWriteAtLeastOnce(mutationGroups, options) {
        const proxyStream = through.obj();
        return (0, instrument_1.startTrace)('Database.batchWriteAtLeastOnce', {
            ...this._traceConfig,
            transactionTag: options?.requestOptions?.transactionTag,
        }, span => {
            this.sessionFactory_.getSessionForReadWrite((err, session) => {
                if (err) {
                    proxyStream.destroy(err);
                    (0, instrument_1.setSpanError)(span, err);
                    span.end();
                    return;
                }
                span.addEvent('Using Session', { 'session.id': session?.id });
                const gaxOpts = extend(true, {}, options?.gaxOptions);
                const reqOpts = Object.assign({}, {
                    session: session.formattedName_,
                    mutationGroups: mutationGroups.map(mg => mg.proto()),
                    requestOptions: options?.requestOptions,
                    excludeTxnFromChangeStream: options?.excludeTxnFromChangeStreams,
                });
                let dataReceived = false;
                let dataStream = this.requestStream({
                    client: 'SpannerClient',
                    method: 'batchWrite',
                    reqOpts,
                    gaxOpts,
                    headers: this.commonHeaders_,
                });
                dataStream
                    .once('data', () => (dataReceived = true))
                    .once('error', err => {
                    (0, instrument_1.setSpanError)(span, err);
                    if (!dataReceived &&
                        (0, session_pool_1.isSessionNotFoundError)(err)) {
                        // If there's a 'Session not found' error and we have not yet received
                        // any data, we can safely retry the writes on a new session.
                        // Register the error on the session so the pool can discard it.
                        if (session) {
                            session.lastError = err;
                        }
                        span.addEvent('No session available', {
                            'session.id': session?.id,
                        });
                        // Remove the current data stream from the end user stream.
                        dataStream.unpipe(proxyStream);
                        dataStream.end();
                        span.end();
                        // Create a new stream and add it to the end user stream.
                        dataStream = this.batchWriteAtLeastOnce(mutationGroups, options);
                        dataStream.pipe(proxyStream);
                    }
                    else {
                        span.end();
                        proxyStream.destroy(err);
                    }
                })
                    .once('end', () => {
                    span.end();
                    this.sessionFactory_.release(session);
                })
                    .pipe(proxyStream);
            });
            return proxyStream;
        });
    }
    writeAtLeastOnce(mutations, optionsOrCallback, callback) {
        const cb = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : callback;
        const options = typeof optionsOrCallback === 'object' && optionsOrCallback
            ? optionsOrCallback
            : {};
        return (0, instrument_1.startTrace)('Database.writeAtLeastOnce', this._traceConfig, span => {
            this.sessionFactory_.getSession((err, session, transaction) => {
                if (err &&
                    (0, session_pool_1.isSessionNotFoundError)(err) &&
                    !this.sessionFactory_.isMultiplexedEnabled()) {
                    span.addEvent('No session available', {
                        'session.id': session?.id,
                    });
                    span.end();
                    this.writeAtLeastOnce(mutations, options, cb);
                    return;
                }
                if (err) {
                    (0, instrument_1.setSpanError)(span, err);
                    span.end();
                    cb(err);
                    return;
                }
                span.addEvent('Using Session', { 'session.id': session?.id });
                this._releaseOnEnd(session, transaction, span);
                try {
                    transaction.setReadWriteTransactionOptions(options);
                    transaction?.setQueuedMutations(mutations.proto());
                    return transaction?.commit(options, (err, resp) => {
                        if (err) {
                            (0, instrument_1.setSpanError)(span, err);
                        }
                        span.end();
                        cb(err, resp);
                    });
                }
                catch (e) {
                    (0, instrument_1.setSpanErrorAndException)(span, e);
                    span.end();
                    throw e;
                }
            });
        });
    }
    /**
     * Create a Session object.
     *
     * It is unlikely you will need to interact with sessions directly. By default,
     * sessions are created and utilized for maximum performance automatically.
     *
     * @param {string} [name] The name of the session. If not provided, it is
     *     assumed you are going to create it.
     * @returns {Session} A Session object.
     *
     * @example
     * ```
     * var session = database.session('session-name');
     * ```
     */
    session(name) {
        return new session_1.Session(this, name);
    }
    setIamPolicy(policy, optionsOrCallback, cb) {
        const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function'
            ? optionsOrCallback
            : cb;
        const gaxOpts = options;
        const reqOpts = {
            resource: this.formattedName_,
            policy: policy.policy,
            updateMask: policy.updateMask || null,
        };
        this.request({
            client: 'DatabaseAdminClient',
            method: 'setIamPolicy',
            reqOpts,
            gaxOpts: gaxOpts,
            headers: this.commonHeaders_,
        }, (err, resp) => {
            callback(err, resp);
        });
    }
    table(name) {
        if (!name) {
            throw new google_gax_1.GoogleError('A name is required to access a Table object.');
        }
        return new table_1.Table(this, name);
    }
    updateSchema(statements, optionsOrCallback, cb) {
        const gaxOpts = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
        const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
        if (typeof statements === 'string' || Array.isArray(statements)) {
            statements = {
                statements: (0, helper_1.toArray)(statements),
            };
        }
        const reqOpts = Object.assign({
            database: this.formattedName_,
        }, statements);
        return this.request({
            client: 'DatabaseAdminClient',
            method: 'updateDatabaseDdl',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, callback);
    }
    /**
     * Format the database name to include the instance name.
     *
     * @private
     *
     * @param {string} instanceName The formatted instance name.
     * @param {string} name The table name.
     * @returns {string}
     *
     * @example
     * ```
     * Database.formatName_(
     *   'projects/grape-spaceship-123/instances/my-instance',
     *   'my-database'
     * );
     * // 'projects/grape-spaceship-123/instances/my-instance/databases/my-database'
     * ```
     */
    static formatName_(instanceName, name) {
        if (name.indexOf('/') > -1) {
            return name;
        }
        const databaseName = name.split('/').pop();
        return instanceName + '/databases/' + databaseName;
    }
    /**
     * Gets the Spanner object
     *
     * @private
     *
     * @returns {Spanner}
     */
    _getSpanner() {
        return this.instance.parent;
    }
}
exports.Database = Database;
/*! Developer Documentation
 *
 * All async methods (except for streams) will return a Promise in the event
 * that a callback is omitted.
 */
(0, promisify_1.promisifyAll)(Database, {
    exclude: [
        'batchTransaction',
        'batchWriteAtLeastOnce',
        'getRestoreInfo',
        'getState',
        'getDatabaseDialect',
        'getOperations',
        'runTransaction',
        'runTransactionAsync',
        'table',
        'session',
    ],
});
/*! Developer Documentation
 *
 * All async methods (except for streams) will return a Promise in the event
 * that a callback is omitted.
 */
(0, promisify_1.callbackifyAll)(Database, {
    exclude: [
        'create',
        'batchCreateSessions',
        'batchTransaction',
        'batchWriteAtLeastOnce',
        'writeAtLeastOnce',
        'close',
        'createBatchTransaction',
        'createSession',
        'createTable',
        'delete',
        'exists',
        'get',
        'getDatabaseDialect',
        'getMetadata',
        'getSchema',
        'getSessions',
        'getSnapshot',
        'getTransaction',
        'getIamPolicy',
        'restore',
        'run',
        'runPartitionedUpdate',
        'runTransaction',
        'runTransactionAsync',
        'session',
        'setMetadata',
        'table',
        'updateSchema',
    ],
});
//# sourceMappingURL=database.js.map