Skip to content

Commit

Permalink
feat: add support for UpdateDatabase (#1802)
Browse files Browse the repository at this point in the history
* feat: add support for drop database protection

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
rajatbhatta and gcf-owl-bot[bot] committed May 17, 2023
1 parent 72efb81 commit f4fbe71
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre
| Gets the schema definition of an existing database | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-get-ddl.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/database-get-ddl.js,samples/README.md) |
| Gets the default leader option of an existing database | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-get-default-leader.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/database-get-default-leader.js,samples/README.md) |
| Updates the default leader of an existing database | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-update-default-leader.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/database-update-default-leader.js,samples/README.md) |
| Updates a Cloud Spanner Database. | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-update.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/database-update.js,samples/README.md) |
| Datatypes | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/datatypes.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/datatypes.js,samples/README.md) |
| Delete using DML returning. | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/dml-returning-delete.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/dml-returning-delete.js,samples/README.md) |
| Insert using DML returning. | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/dml-returning-insert.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/dml-returning-insert.js,samples/README.md) |
Expand Down
18 changes: 18 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ and automatic, synchronous replication for high availability.
* [Gets the schema definition of an existing database](#gets-the-schema-definition-of-an-existing-database)
* [Gets the default leader option of an existing database](#gets-the-default-leader-option-of-an-existing-database)
* [Updates the default leader of an existing database](#updates-the-default-leader-of-an-existing-database)
* [Updates a Cloud Spanner Database.](#updates-a-cloud-spanner-database.)
* [Datatypes](#datatypes)
* [Delete using DML returning.](#delete-using-dml-returning.)
* [Insert using DML returning.](#insert-using-dml-returning.)
Expand Down Expand Up @@ -473,6 +474,23 @@ __Usage:__



### Updates a Cloud Spanner Database.

View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-update.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/database-update.js,samples/README.md)

__Usage:__


`node database-update.js <INSTANCE_ID> <DATABASE_ID> <PROJECT_ID>`


-----




### Datatypes

View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/datatypes.js).
Expand Down
72 changes: 72 additions & 0 deletions samples/database-update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2023 Google LLC
//
// 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.

// sample-metadata:
// title: Updates a Cloud Spanner Database.
// usage: node database-update.js <INSTANCE_ID> <DATABASE_ID> <PROJECT_ID>

'use strict';

function main(
instanceId = 'my-instance',
databaseId = 'my-database',
projectId = 'my-project-id'
) {
// [START spanner_update_database]
/**
* TODO(developer): Uncomment these variables before running the sample.
*/
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const projectId = 'my-project-id';

// Imports the Google Cloud Spanner client library
const {Spanner} = require('@google-cloud/spanner');

// Instantiates a client
const spanner = new Spanner({
projectId: projectId,
});

async function updateDatabase() {
// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

try {
console.log(`Updating database ${database.id}.`);
const [operation] = await database.setMetadata({
enableDropProtection: true,
});
console.log(
`Waiting for update operation for ${database.id} to complete...`
);
await operation.promise();
console.log(`Updated database ${database.id}.`);
} catch (err) {
console.log('ERROR:', err);
} finally {
// Close the database when finished.
database.close();
}
}
updateDatabase();
// [END spanner_update_database]
}

process.on('unhandledRejection', err => {
console.error(err.message);
process.exitCode = 1;
});
main(...process.argv.slice(2));
19 changes: 19 additions & 0 deletions samples/system-test/spanner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,25 @@ describe('Spanner', () => {
);
});

// update_database
it('should set database metadata', async () => {
const output = execSync(
`node database-update.js ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}`
);
assert.match(
output,
new RegExp(
`Waiting for update operation for ${DATABASE_ID} to complete...`
)
);
assert.match(output, new RegExp(`Updated database ${DATABASE_ID}.`));
// cleanup
const [operation] = await instance
.database(DATABASE_ID)
.setMetadata({enableDropProtection: false});
await operation.promise();
});

describe('encrypted database', () => {
after(async () => {
const instance = spanner.instance(INSTANCE_ID);
Expand Down
108 changes: 108 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ import {ServiceError} from 'google-gax';
import IPolicy = google.iam.v1.IPolicy;
import Policy = google.iam.v1.Policy;
import FieldMask = google.protobuf.FieldMask;
import IDatabase = google.spanner.admin.database.v1.IDatabase;
import snakeCase = require('lodash.snakecase');

export type GetDatabaseRolesCallback = RequestCallback<
IDatabaseRole,
databaseAdmin.spanner.admin.database.v1.IListDatabaseRolesResponse
Expand All @@ -103,6 +106,8 @@ export type GetDatabaseRolesResponse = PagedResponse<
IDatabaseRole,
databaseAdmin.spanner.admin.database.v1.IListDatabaseRolesResponse
>;
type SetDatabaseMetadataCallback = ResourceCallback<GaxOperation, IOperation>;
type SetDatabaseMetadataResponse = [GaxOperation, IOperation];
type IDatabaseRole = databaseAdmin.spanner.admin.database.v1.IDatabaseRole;

type CreateBatchTransactionCallback = ResourceCallback<
Expand Down Expand Up @@ -423,6 +428,108 @@ class Database extends common.GrpcServiceObject {
Database.getEnvironmentQueryOptions()
);
}
/**
* @typedef {array} SetDatabaseMetadataResponse
* @property {object} 0 The {@link Database} metadata.
* @property {object} 1 The full API response.
*/
/**
* @callback SetDatabaseMetadataCallback
* @param {?Error} err Request error, if any.
* @param {object} metadata The {@link Database} metadata.
* @param {object} apiResponse The full API response.
*/
/**
* Update the metadata for this database. Note that this method follows PATCH
* semantics, so previously-configured settings will persist.
*
* Wrapper around {@link v1.DatabaseAdminClient#updateDatabase}.
*
* @see {@link v1.DatabaseAdminClient#updateDatabase}
* @see [UpdateDatabase API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabase)
*
* @param {object} metadata The metadata you wish to set.
* @param {object} [gaxOptions] Request configuration options,
* See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions}
* for more details.
* @param {SetDatabaseMetadataCallback} [callback] Callback function.
* @returns {Promise<SetDatabaseMetadataResponse>}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const spanner = new Spanner();
*
* const instance = spanner.instance('my-instance');
* const database = instance.database('my-database');
*
* const metadata = {
* enableDropProtection: true
* };
*
* database.setMetadata(metadata, function(err, operation, apiResponse) {
* if (err) {
* // Error handling omitted.
* }
*
* operation
* .on('error', function(err) {})
* .on('complete', function() {
* // Metadata updated successfully.
* });
* });
*
* //-
* // If the callback is omitted, we'll return a Promise.
* //-
* database.setMetadata(metadata).then(function(data) {
* const operation = data[0];
* const apiResponse = data[1];
* });
* ```
*/
setMetadata(
metadata: IDatabase,
gaxOptions?: CallOptions
): Promise<SetDatabaseMetadataResponse>;
setMetadata(metadata: IDatabase, callback: SetDatabaseMetadataCallback): void;
setMetadata(
metadata: IDatabase,
gaxOptions: CallOptions,
callback: SetDatabaseMetadataCallback
): void;
setMetadata(
metadata: IDatabase,
optionsOrCallback?: CallOptions | SetDatabaseMetadataCallback,
cb?: SetDatabaseMetadataCallback
): void | Promise<SetDatabaseMetadataResponse> {
const gaxOpts =
typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
const callback =
typeof optionsOrCallback === 'function' ? optionsOrCallback : cb!;

const reqOpts = {
database: extend(
{
name: this.formattedName_,
},
metadata
),
updateMask: {
paths: Object.keys(metadata).map(snakeCase),
},
};
return this.request(
{
client: 'DatabaseAdminClient',
method: 'updateDatabase',
reqOpts,
gaxOpts,
headers: this.resourceHeader_,
},
callback!
);
}

static getEnvironmentQueryOptions() {
const options =
Expand Down Expand Up @@ -3332,6 +3439,7 @@ callbackifyAll(Database, {
'runTransaction',
'runTransactionAsync',
'session',
'setMetadata',
'table',
'updateSchema',
],
Expand Down
52 changes: 50 additions & 2 deletions system-test/spanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ describe('Spanner', () => {
const RESOURCES_TO_CLEAN: Array<Instance | Backup | Database> = [];
const INSTANCE_CONFIGS_TO_CLEAN: Array<InstanceConfig> = [];
const DATABASE = instance.database(generateName('database'), {incStep: 1});
const DATABASE_DROP_PROTECTION = instance.database(generateName('database'), {
incStep: 1,
});
const TABLE_NAME = 'Singers';
const PG_DATABASE = instance.database(generateName('pg-db'), {incStep: 1});

Expand All @@ -119,17 +122,28 @@ describe('Spanner', () => {
`Not creating temp instance, using + ${instance.formattedName_}...`
);
}
const [, googleSqlOperation] = await DATABASE.create({
const [, googleSqlOperation1] = await DATABASE.create({
schema: `
CREATE TABLE ${TABLE_NAME} (
SingerId STRING(1024) NOT NULL,
Name STRING(1024),
) PRIMARY KEY(SingerId)`,
gaxOptions: GAX_OPTIONS,
});
await googleSqlOperation.promise();
await googleSqlOperation1.promise();
RESOURCES_TO_CLEAN.push(DATABASE);

const [, googleSqlOperation2] = await DATABASE_DROP_PROTECTION.create({
schema: `
CREATE TABLE ${TABLE_NAME} (
SingerId STRING(1024) NOT NULL,
Name STRING(1024),
) PRIMARY KEY(SingerId)`,
gaxOptions: GAX_OPTIONS,
});
await googleSqlOperation2.promise();
RESOURCES_TO_CLEAN.push(DATABASE_DROP_PROTECTION);

if (!IS_EMULATOR_ENABLED) {
const [pg_database, postgreSqlOperation] = await PG_DATABASE.create({
databaseDialect: Spanner.POSTGRESQL,
Expand Down Expand Up @@ -1973,6 +1987,10 @@ describe('Spanner', () => {
instance.getDatabases((err, databases) => {
assert.ifError(err);
assert(databases!.length > 0);
// check if enableDropProtection is populated for databases.
databases!.map(db => {
assert.notStrictEqual(db.metadata.enableDropProtection, null);
});
done();
});
});
Expand Down Expand Up @@ -2139,6 +2157,36 @@ describe('Spanner', () => {
await listDatabaseOperation(PG_DATABASE);
});

it('enable_drop_protection should be disabled by default', async function () {
if (IS_EMULATOR_ENABLED) {
this.skip();
}
const [databaseMetadata] = await DATABASE_DROP_PROTECTION.getMetadata();
assert.strictEqual(databaseMetadata!.enableDropProtection, false);
});

it('enable_drop_protection on database', async function () {
if (IS_EMULATOR_ENABLED) {
this.skip();
}
const [operation1] = await DATABASE_DROP_PROTECTION.setMetadata({
enableDropProtection: true,
});
await operation1.promise();

try {
await DATABASE_DROP_PROTECTION.delete();
assert.ok(false);
} catch (err) {
assert.ok(true);
}

const [operation2] = await DATABASE_DROP_PROTECTION.setMetadata({
enableDropProtection: false,
});
await operation2.promise();
});

describe('FineGrainedAccessControl', () => {
before(function () {
if (SKIP_FGAC_TESTS === 'true') {
Expand Down
Loading

0 comments on commit f4fbe71

Please sign in to comment.