@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
791 lines • 30.2 kB
JavaScript
"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