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 6 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
111 changes: 111 additions & 0 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
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,11 @@
IDatabaseRole,
databaseAdmin.spanner.admin.database.v1.IListDatabaseRolesResponse
>;
type SetDatabaseMetadataCallback = ResourceCallback<

Check failure on line 109 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `⏎····GaxOperation,⏎····IOperation⏎` with `GaxOperation,·IOperation`
GaxOperation,
IOperation
>;
type SetDatabaseMetadataResponse = [GaxOperation, IOperation];
type IDatabaseRole = databaseAdmin.spanner.admin.database.v1.IDatabaseRole;

type CreateBatchTransactionCallback = ResourceCallback<
Expand Down Expand Up @@ -423,6 +431,108 @@
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,

Check failure on line 495 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
gaxOptions?: CallOptions

Check failure on line 496 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
): Promise<SetDatabaseMetadataResponse>;
setMetadata(metadata: IDatabase, callback: SetDatabaseMetadataCallback): void;
setMetadata(
metadata: IDatabase,

Check failure on line 500 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
gaxOptions: CallOptions,

Check failure on line 501 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
callback: SetDatabaseMetadataCallback

Check failure on line 502 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
): void;
setMetadata(
metadata: IDatabase,

Check failure on line 505 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `······` with `····`
optionsOrCallback?: CallOptions | SetDatabaseMetadataCallback,

Check failure on line 506 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
cb?: SetDatabaseMetadataCallback

Check failure on line 507 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `··`
): void | Promise<SetDatabaseMetadataResponse> {
const gaxOpts =
typeof optionsOrCallback === 'object' ? optionsOrCallback : {};

Check failure on line 510 in src/database.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `········` with `······`
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 +3442,7 @@
'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 @@ -36,6 +36,7 @@
LEADER_AWARE_ROUTING_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 @@ -434,7 +435,7 @@
const error = new Error('err');
const response = {};

sandbox.stub(database, 'request').callsFake((_, cb: any) => {

Check warning on line 438 in test/database.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
cb(error, response);
});

Expand All @@ -457,7 +458,7 @@
stub.withArgs(session.name).returns(fakeSessions[i]);
});

sandbox.stub(database, 'request').callsFake((_, cb: any) => {

Check warning on line 461 in test/database.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
cb(null, response);
});

Expand All @@ -470,6 +471,59 @@
});
});

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