Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for UpdateDatabase #1802

Merged
merged 23 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.
rajatbhatta marked this conversation as resolved.
Show resolved Hide resolved
* @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