UNPKG

@google-cloud/spanner

Version:
791 lines 30.2 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.Instance = void 0; const helper_1 = require("./helper"); // 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 snakeCase = require("lodash.snakecase"); const database_1 = require("./database"); const common_1 = require("./common"); const google_gax_1 = require("google-gax"); const backup_1 = require("./backup"); const protos_1 = require("../protos/protos"); /** * The {@link Instance} class represents a [Cloud Spanner * instance](https://cloud.google.com/spanner/docs/instances). * * Create an `Instance` object to interact with a Cloud Spanner instance. * * @class * * @param {Spanner} spanner {@link Spanner} instance. * @param {string} name Name of the instance. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * const instance = spanner.instance('my-instance'); * ``` */ class Instance extends common.GrpcServiceObject { formattedName_; request; requestStream; databases_; metadata; commonHeaders_; _observabilityOptions; constructor(spanner, name) { const formattedName_ = Instance.formatName_(spanner.projectId, name); const methods = { /** * Create an instance. * * Wrapper around {@link v1.InstanceAdminClient#createInstance}. * * @see {@link v1.InstanceAdminClient#createInstance} * @see [CreateInstance API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.instance.v1#google.spanner.admin.instance.v1.InstanceAdmin.CreateInstance) * * @method Instance#create * @param {CreateInstanceRequest} config Configuration object. * @param {CreateInstanceCallback} [callback] Callback function. * @returns {Promise<CreateInstanceResponse>} * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * * const instance = spanner.instance('my-instance'); * * instance.create(function(err, instance, operation, apiResponse) { * if (err) { * // Error handling omitted. * } * * operation * .on('error', function(err) {}) * .on('complete', function() { * // Instance created successfully. * }); * }); * * //- * // If the callback is omitted, we'll return a Promise. * //- * instance.create() * .then(function(data) { * const operation = data[0]; * const apiResponse = data[1]; * * return operation.promise(); * }) * .then(function() { * // Instance created successfully. * }); * ``` */ create: true, }; super({ parent: spanner, /** * @name Instance#id * @type {string} */ id: name, methods, createMethod(_, options, callback) { spanner.createInstance(formattedName_, options, callback); }, }); this.formattedName_ = formattedName_; this.request = spanner.request.bind(spanner); this.requestStream = spanner.requestStream.bind(spanner); this.databases_ = new Map(); this._observabilityOptions = spanner._observabilityOptions; this.commonHeaders_ = (0, common_1.getCommonHeaders)(this.formattedName_, this._observabilityOptions?.enableEndToEndTracing); } /** * Get a reference to a Backup object. * * @throws {GoogleError} If any parameter is not provided. * * @param {string} backupId The name of the backup. * @return {Backup} A Backup object. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * const instance = spanner.instance('my-instance'); * const backup = instance.backup('my-backup'); * ``` */ backup(backupId) { if (!backupId) { throw new google_gax_1.GoogleError('A backup ID is required to create a Backup.'); } return new backup_1.Backup(this, backupId); } /** * Get a reference to a Backup object. * * @throws {GoogleError} If any parameter is not provided. * * @typedef {object} CopyBackupOptions * * @property {string|null} * * sourceBackup The full path of the backup to be copied * * @property {string|number|google.protobuf.Timestamp|external:PreciseDate} * * expireTime The expire time of the backup. * * @property {google.spanner.admin.database.v1.ICopyBackupEncryptionConfig} * * encryptionConfig An encryption configuration describing the * * encryption type and key resources in Cloud KMS to be used to encrypt * * the copy backup. * * @property {object} [gaxOptions] The request configuration options, * * See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} * * for more details. * */ /** * @callback CopyBackupCallback * @param {string} sourceBackupId Full path of the source backup to be copied. * @param {string} backupId The name of the backup. * @param {CopyBackupOptions} * @return {Backup} A Backup object. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * const instance = spanner.instance('my-instance'); * const backup = instance.copyBackup('my-source-backup','my-backup',{ * expireTime: expireTime, * encryptionConfig: { * encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', * kmsKeyName: 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key', * },); * ``` */ copyBackup(sourceBackupId, backupId, options, callback) { if (!backupId || !sourceBackupId) { throw new google_gax_1.GoogleError('A backup ID and source backup ID is required to create a copy of the source backup.'); } const copyOfBackup = new backup_1.Backup(this, backupId, sourceBackupId); if (callback) { return copyOfBackup.create(options, callback); } return copyOfBackup.create(options); } getBackups(optionsOrCallback, cb) { const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const gaxOpts = extend(true, {}, options.gaxOptions); let reqOpts = extend({}, options, { parent: this.formattedName_, }); delete reqOpts.gaxOptions; // Copy over pageSize and pageToken values from gaxOptions. // However values set on options take precedence. if (gaxOpts) { reqOpts = extend({}, { pageSize: gaxOpts.pageSize, pageToken: gaxOpts.pageToken, }, reqOpts); delete gaxOpts.pageSize; delete gaxOpts.pageToken; } this.request({ client: 'DatabaseAdminClient', method: 'listBackups', reqOpts, gaxOpts, headers: this.commonHeaders_, }, (err, backups, nextPageRequest, ...args) => { let backupInstances = null; if (backups) { backupInstances = backups.map(backup => { const backupInstance = this.backup(backup.name); backupInstance.metadata = backup; return backupInstance; }); } const nextQuery = nextPageRequest ? extend({}, options, nextPageRequest) : null; callback(err, backupInstances, nextQuery, ...args); }); } /** * Get a list of backups as a readable object stream. * * Wrapper around {@link v1.DatabaseAdminClient#listBackups}. * * @see {@link v1.DatabaseAdminClient#listBackups} * @see [ListBackups API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.ListBackups) * * @method Spanner#getBackupsStream * @param {GetBackupOptions} [options] Query object for listing backups. * @returns {ReadableStream} A readable stream that emits {@link Backup} * instances. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * * const instance = spanner.instance('my-instance'); * * instance.getBackupsStream() * .on('error', console.error) * .on('data', function(database) { * // `backups` is a `Backup` object. * }) * .on('end', function() { * // All backups retrieved. * }); * * //- * // If you anticipate many results, you can end a stream early to prevent * // unnecessary processing and API requests. * //- * instance.getBackupsStream() * .on('data', function(database) { * this.end(); * }); * ``` */ getBackupsStream(options = {}) { const gaxOpts = extend(true, {}, options.gaxOptions); let reqOpts = extend({}, options, { parent: this.formattedName_, }); delete reqOpts.gaxOptions; // Copy over pageSize and pageToken values from gaxOptions. // However values set on options take precedence. if (gaxOpts) { reqOpts = extend({}, { pageSize: gaxOpts.pageSize, pageToken: gaxOpts.pageToken, }, reqOpts); delete gaxOpts.pageSize; delete gaxOpts.pageToken; } return this.requestStream({ client: 'DatabaseAdminClient', method: 'listBackupsStream', reqOpts, gaxOpts, headers: this.commonHeaders_, }); } /** * List pending and completed backup operations for all databases in the instance. * * @see {@link #listOperations} * * @param {GetBackupOperationsOptions} [options] The query object for listing * backup operations. * @param {gax.CallOptions} [options.gaxOptions] The request configuration * options, See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} * for more details. * @returns {Promise<GetBackupOperationsResponse>} When resolved, contains a * paged list of backup operations. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * const instance = spanner.instance('my-instance'); * const [operations] = await instance.getBackupOperations(); * * //- * // To manually handle pagination, set autoPaginate:false in gaxOptions. * //- * let pageToken = undefined; * do { * const [operations, , response] = await instance.getBackupOperations({ * pageSize: 3, * pageToken, * gaxOptions: {autoPaginate: false}, * }); * operations.forEach(operation => { * // Do something with operation * }); * pageToken = response.nextPageToken; * } while (pageToken); * ``` */ getBackupOperations(optionsOrCallback, cb) { const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const gaxOpts = extend(true, {}, options.gaxOptions); let reqOpts = extend({}, options, { parent: this.formattedName_, }); delete reqOpts.gaxOptions; // Copy over pageSize and pageToken values from gaxOptions. // However values set on options take precedence. if (gaxOpts) { reqOpts = extend({}, { pageSize: gaxOpts.pageSize, pageToken: gaxOpts.pageToken, }, reqOpts); delete gaxOpts.pageSize; delete gaxOpts.pageToken; } this.request({ client: 'DatabaseAdminClient', method: 'listBackupOperations', reqOpts, gaxOpts, headers: this.commonHeaders_, }, (err, operations, nextPageRequest, ...args) => { const nextQuery = nextPageRequest ? extend({}, options, nextPageRequest) : null; callback(err, operations, nextQuery, ...args); }); } /** * List pending and completed operations for all databases in the instance. * * @see {@link Database.getDatabaseOperations} * * @param {GetDatabaseOperationsOptions} [options] The query object for * listing database operations. * @param {gax.CallOptions} [options.gaxOptions] The request configuration * options, See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} * for more details. * @returns {Promise<GetDatabaseOperationsResponse>} When resolved, contains a * paged list of database operations. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * const instance = spanner.instance('my-instance'); * const [operations] = await instance.getDatabaseOperations(); * // ... then do something with the operations * * //- * // To manually handle pagination, set autoPaginate:false in gaxOptions. * //- * let pageToken = undefined; * do { * const [operations, , response] = await instance.getDatabaseOperations({ * pageSize: 3, * pageToken, * gaxOptions: {autoPaginate: false}, * }); * operations.forEach(operation => { * // Do something with operation * }); * pageToken = response.nextPageToken; * } while (pageToken); * ``` */ getDatabaseOperations(optionsOrCallback, cb) { const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const gaxOpts = extend(true, {}, options.gaxOptions); let reqOpts = extend({}, options, { parent: this.formattedName_, }); delete reqOpts.gaxOptions; // Copy over pageSize and pageToken values from gaxOptions. // However values set on options take precedence. if (gaxOpts) { reqOpts = extend({}, { pageSize: gaxOpts.pageSize, pageToken: gaxOpts.pageToken, }, reqOpts); delete gaxOpts.pageSize; delete gaxOpts.pageToken; } this.request({ client: 'DatabaseAdminClient', method: 'listDatabaseOperations', reqOpts, gaxOpts, headers: this.commonHeaders_, }, (err, operations, nextPageRequest, ...args) => { const nextQuery = nextPageRequest ? extend({}, options, nextPageRequest) : null; callback(err, operations, nextQuery, ...args); }); } createDatabase(name, optionsOrCallback, cb) { if (!name) { throw new google_gax_1.GoogleError('A name is required to create a database.'); } const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const poolOptions = options.poolOptions; const poolCtor = options.poolCtor; let createStatement = 'CREATE DATABASE `' + name.split('/').pop() + '`'; if (protos_1.google.spanner.admin.database.v1.DatabaseDialect.POSTGRESQL === options.databaseDialect) { createStatement = 'CREATE DATABASE "' + name.split('/').pop() + '"'; } const reqOpts = extend({ parent: this.formattedName_, createStatement: createStatement, }, options); delete reqOpts.poolOptions; delete reqOpts.poolCtor; delete reqOpts.gaxOptions; if (reqOpts.schema) { reqOpts.extraStatements = (0, helper_1.toArray)(reqOpts.schema); delete reqOpts.schema; } this.request({ client: 'DatabaseAdminClient', method: 'createDatabase', reqOpts, gaxOpts: options.gaxOptions, headers: this.commonHeaders_, }, (err, operation, resp) => { if (err) { callback(err, null, null, resp); return; } const database = this.database(name, poolOptions || poolCtor); database._observabilityOptions = this._observabilityOptions; callback(null, database, operation, resp); }); } /** * Get a reference to a Database object. * * @throws {GoogleError} If a name is not provided. * * @param {string} name The name of the instance. * @param {SessionPoolOptions|SessionPoolCtor} [poolOptions] Session pool * configuration options. * @param {spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions} [queryOptions] * Default query options to use with the database. These options will be * overridden by any query options set in environment variables or that * are specified on a per-query basis. * @return {Database} A Database object. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * * const instance = spanner.instance('my-instance'); * const database = instance.database('my-database'); * ``` */ database(name, poolOptions, queryOptions) { if (!name) { throw new google_gax_1.GoogleError('A name is required to access a Database object.'); } // Only add an additional key for SessionPoolOptions and QueryOptions if an // options object with at least one value was passed in. let optionsKey = poolOptions && Object.keys(poolOptions).length > 0 ? '/' + JSON.stringify(Object.entries(poolOptions).sort()) : ''; if (queryOptions && Object.keys(queryOptions).length > 0) { optionsKey = optionsKey + '/' + JSON.stringify(Object.entries(queryOptions).sort()); } const key = name.split('/').pop() + optionsKey; if (!this.databases_.has(key)) { const db = new database_1.Database(this, name, poolOptions, queryOptions); db._observabilityOptions = this._observabilityOptions; const parent = this.parent; if (parent && parent._nthClientId) { db._clientId = parent._nthClientId; } this.databases_.set(key, db); } return this.databases_.get(key); } delete(optionsOrCallback, cb) { const gaxOpts = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const reqOpts = { name: this.formattedName_, }; void Promise.all(Array.from(this.databases_.values()).map(database => { return database.close(); })) .catch(() => { }) .then(() => { this.databases_.clear(); this.request({ client: 'InstanceAdminClient', method: 'deleteInstance', reqOpts, gaxOpts, headers: this.commonHeaders_, }, (err, resp) => { if (!err) { this.parent.instances_.delete(this.id); } callback(err, resp); }); }); } exists(optionsOrCallback, cb) { const gaxOptions = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const NOT_FOUND = 5; this.getMetadata({ gaxOptions }, err => { if (err && err.code !== NOT_FOUND) { callback(err, null); return; } const exists = !err || err.code !== NOT_FOUND; callback(null, exists); }); } get(optionsOrCallback, cb) { const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const getMetadataOptions = new Object(null); if (options.fieldNames) { getMetadataOptions.fieldNames = options.fieldNames; } if (options.gaxOptions) { getMetadataOptions.gaxOptions = options.gaxOptions; } this.getMetadata(getMetadataOptions, (err, metadata) => { if (err) { if (err.code === 5 && options.autoCreate) { const createOptions = extend(true, {}, options); delete createOptions.fieldNames; delete createOptions.autoCreate; this.create(createOptions, (err, instance, 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); }); } getDatabases(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); let reqOpts = extend({}, options, { parent: this.formattedName_, }); delete reqOpts.gaxOptions; // Copy over pageSize and pageToken values from gaxOptions. // However values set on options take precedence. if (gaxOpts) { reqOpts = extend({}, { pageSize: gaxOpts.pageSize, pageToken: gaxOpts.pageToken, }, reqOpts); delete gaxOpts.pageSize; delete gaxOpts.pageToken; } this.request({ client: 'DatabaseAdminClient', method: 'listDatabases', reqOpts, gaxOpts, headers: this.commonHeaders_, }, (err, rowDatabases, nextPageRequest, ...args) => { let databases = null; if (rowDatabases) { databases = rowDatabases.map(database => { const databaseInstance = self.database(database.name, { min: 0 }); databaseInstance.metadata = database; databaseInstance._observabilityOptions = this._observabilityOptions; return databaseInstance; }); } const nextQuery = nextPageRequest ? extend({}, options, nextPageRequest) : null; callback(err, databases, nextQuery, ...args); }); } /** * Get a list of databases as a readable object stream. * * Wrapper around {@link v1.DatabaseAdminClient#listDatabases}. * * @see {@link v1.DatabaseAdminClient#listDatabases} * @see [ListDatabases API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.ListDatabases) * * @method Spanner#getDatabasesStream * @param {GetDatabasesOptions} [options] Query object for listing databases. * @returns {ReadableStream} A readable stream that emits {@link Database} * instances. * * @example * ``` * const {Spanner} = require('@google-cloud/spanner'); * const spanner = new Spanner(); * * const instance = spanner.instance('my-instance'); * * instance.getDatabasesStream() * .on('error', console.error) * .on('data', function(database) { * // `database` is a `Database` object. * }) * .on('end', function() { * // All databases retrieved. * }); * * //- * // If you anticipate many results, you can end a stream early to prevent * // unnecessary processing and API requests. * //- * instance.getDatabasesStream() * .on('data', function(database) { * this.end(); * }); * ``` */ getDatabasesStream(options = {}) { const gaxOpts = extend(true, {}, options.gaxOptions); let reqOpts = extend({}, options, { parent: this.formattedName_, }); delete reqOpts.gaxOptions; // Copy over pageSize and pageToken values from gaxOptions. // However values set on options take precedence. if (gaxOpts) { reqOpts = extend({}, { pageSize: gaxOpts.pageSize, pageToken: gaxOpts.pageToken, }, reqOpts); delete gaxOpts.pageSize; delete gaxOpts.pageToken; } return this.requestStream({ client: 'DatabaseAdminClient', method: 'listDatabasesStream', reqOpts, gaxOpts, headers: this.commonHeaders_, }); } getMetadata(optionsOrCallback, cb) { const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const reqOpts = { name: this.formattedName_, }; if (options.fieldNames) { reqOpts['fieldMask'] = { paths: (0, helper_1.toArray)(options['fieldNames']).map(snakeCase), }; } return this.request({ client: 'InstanceAdminClient', method: 'getInstance', reqOpts, gaxOpts: options.gaxOptions, headers: this.commonHeaders_, }, (err, resp) => { if (resp) { this.metadata = resp; } callback(err, resp); }); } setMetadata(metadata, optionsOrCallback, cb) { const gaxOpts = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb; const reqOpts = { instance: extend({ name: this.formattedName_, }, metadata), fieldMask: { paths: Object.keys(metadata).map(snakeCase), }, }; return this.request({ client: 'InstanceAdminClient', method: 'updateInstance', reqOpts, gaxOpts, headers: this.commonHeaders_, }, callback); } /** * Format the instance name to include the project ID. * * @private * * @param {string} projectId The project ID. * @param {string} name The instance name. * @returns {string} * * @example * ``` * Instance.formatName_('grape-spaceship-123', 'my-instance'); * // 'projects/grape-spaceship-123/instances/my-instance' * ``` */ static formatName_(projectId, name) { if (name.indexOf('/') > -1) { return name; } const instanceName = name.split('/').pop(); return 'projects/' + projectId + '/instances/' + instanceName; } } exports.Instance = Instance; /*! Developer Documentation * * All async methods (except for streams) will return a Promise in the event * that a callback is omitted. */ (0, promisify_1.promisifyAll)(Instance, { exclude: ['database', 'backup'], }); //# sourceMappingURL=instance.js.map