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 2 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
101 changes: 101 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,18 @@ 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 SetDatabaseMetadataCallback = ResourceCallback<
rajatbhatta marked this conversation as resolved.
Show resolved Hide resolved
GaxOperation,
IOperation
>;
export type GetDatabaseRolesCallback = RequestCallback<
IDatabaseRole,
databaseAdmin.spanner.admin.database.v1.IListDatabaseRolesResponse
>;
export type SetDatabaseMetadataResponse = [GaxOperation, IOperation];
export type GetDatabaseRolesResponse = PagedResponse<
IDatabaseRole,
databaseAdmin.spanner.admin.database.v1.IListDatabaseRolesResponse
Expand Down Expand Up @@ -423,6 +431,98 @@ class Database extends common.GrpcServiceObject {
);
}

/**
* 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 =
{} as spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions;
Expand Down Expand Up @@ -3310,6 +3410,7 @@ callbackifyAll(Database, {
'runTransaction',
'runTransactionAsync',
'session',
'setMetadata',
'table',
'updateSchema',
],
Expand Down
37 changes: 37 additions & 0 deletions system-test/spanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,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 @@ -2016,6 +2020,39 @@ describe('Spanner', () => {
});
});

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

it('enable_drop_protection on database', async () => {
if (!IS_EMULATOR_ENABLED) {
const [operation1] = await DATABASE.setMetadata({
enableDropProtection: true
});
await operation1.promise();
const [databaseMetadata1] = await DATABASE.getMetadata();
assert.strictEqual(databaseMetadata1!.enableDropProtection, true);
try {
await DATABASE.delete();
assert.ok(false);
} catch(err) {}
try {
await instance.delete();
assert.ok(false);
} catch(err) {}
// Disabling drop protection on database (for cleanup tasks later).
const [operation2] = await DATABASE.setMetadata({
enableDropProtection: false
});
await operation2.promise();
const [databaseMetadata2] = await DATABASE.getMetadata();
assert.strictEqual(databaseMetadata2!.enableDropProtection, false);
}
});

const createTable = (done, database, dialect, createTableStatement) => {
database.updateSchema(
[createTableStatement],
Expand Down
54 changes: 54 additions & 0 deletions test/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {MockError} from './mockserver/mockspanner';
import {IOperation} from '../src/instance';
import {CLOUD_RESOURCE_HEADER} from '../src/common';
import {google} from '../protos/protos';
import * as inst from '../src/instance';
import RequestOptions = google.spanner.v1.RequestOptions;
import EncryptionType = google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.EncryptionType;

Expand Down Expand Up @@ -456,6 +457,59 @@ describe('Database', () => {
});
});

describe('setMetadata', () => {
const METADATA = {
needsToBeSnakeCased: true,
} as inst.IDatabase;
const ORIGINAL_METADATA = extend({}, METADATA);

it('should make and return the request', () => {
const requestReturnValue = {};

function callback() {}

database.request = (config, callback_) => {
assert.strictEqual(config.client, 'DatabaseAdminClient');
assert.strictEqual(config.method, 'updateDatabase');

const expectedReqOpts = extend({}, METADATA, {
name: database.formattedName_,
});

assert.deepStrictEqual(config.reqOpts.database, expectedReqOpts);
assert.deepStrictEqual(config.reqOpts.updateMask, {
paths: ['needs_to_be_snake_cased'],
});

assert.deepStrictEqual(METADATA, ORIGINAL_METADATA);
assert.deepStrictEqual(config.gaxOpts, {});
assert.deepStrictEqual(config.headers, database.resourceHeader_);

assert.strictEqual(callback_, callback);

return requestReturnValue;
};

const returnValue = database.setMetadata(METADATA, callback);
assert.strictEqual(returnValue, requestReturnValue);
});

it('should accept gaxOptions', done => {
const gaxOptions = {};
database.request = config => {
assert.strictEqual(config.gaxOpts, gaxOptions);
done();
};
database.setMetadata(METADATA, gaxOptions, assert.ifError);
});

it('should not require a callback', () => {
assert.doesNotThrow(() => {
database.setMetadata(METADATA);
});
});
});

describe('batchTransaction', () => {
const SESSION = {id: 'hijklmnop'};
const ID = 'abcdefg';
Expand Down