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: Support for using multiple databases in datastore #1090

Merged
merged 75 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
11524c1
A simple test with multidb support
danieljbruce Mar 20, 2023
e06bee0
A simple test with multidb support
danieljbruce Mar 20, 2023
1f464a5
Merge branch 'multi-db' of https://github.com/danieljbruce/nodejs-dat…
danieljbruce Mar 20, 2023
10da9c8
Add additional checks with get
danieljbruce Mar 20, 2023
ecfce7d
Add a test for saving with another database
danieljbruce Mar 20, 2023
3b6a42c
other datastore should delete the entity
danieljbruce Mar 20, 2023
e254be5
Add a test for the getDatastore method
danieljbruce Mar 20, 2023
77970c8
Add a unit test for database id
danieljbruce Mar 20, 2023
3b86ce2
Do an additional check for the sake of tests
danieljbruce Mar 20, 2023
ea54946
initialize generated client so it can be mocked
danieljbruce Mar 20, 2023
38d955c
comments and setDatabaseId
danieljbruce Mar 21, 2023
57bc2eb
Refactor of database name
danieljbruce Mar 21, 2023
c9856b2
correction to test against old database
danieljbruce Mar 21, 2023
543e486
getRequestWithDatabaseId function
danieljbruce Mar 22, 2023
dde1eba
Change function name to addDatabaseIdToRequest
danieljbruce Mar 22, 2023
8ba512a
Comments in all the places to add reqOpts
danieljbruce Mar 22, 2023
de946b2
Add a warning to a certain test for easier debug
danieljbruce Mar 22, 2023
dd7b06c
This fixed the bug for the save operation
danieljbruce Mar 22, 2023
f167ce3
datastore request id
danieljbruce Mar 22, 2023
364845b
addDatabaseIdToRequest
danieljbruce Mar 22, 2023
7b5f902
Solve all the test errors for createReadStream
danieljbruce Mar 22, 2023
a3bdb92
addDatabaseIdToRequest mock for runQueryStream
danieljbruce Mar 22, 2023
9d7fab1
By default, mock out addDatabaseIdToRequest
danieljbruce Mar 22, 2023
d9a96bf
Revert "addDatabaseIdToRequest mock for runQueryStream"
danieljbruce Mar 22, 2023
7543f57
Revert "Solve all the test errors for createReadStream"
danieljbruce Mar 22, 2023
3f3e965
linting
danieljbruce Mar 23, 2023
32bb92b
Revert "datastore request id"
danieljbruce Mar 23, 2023
92f8778
Revert "This fixed the bug for the save operation"
danieljbruce Mar 23, 2023
6159882
Revert "Comments in all the places to add reqOpts"
danieljbruce Mar 23, 2023
9410652
Introduce addDatabaseIdRequest in request
danieljbruce Mar 23, 2023
b34bf17
Declare a constant so that tests don’t run
danieljbruce Mar 23, 2023
2e1999a
Remove setDatabaseId
danieljbruce Mar 28, 2023
bd97d00
Add comment for addDatabaseIdToRequest function
danieljbruce Mar 28, 2023
d41ca60
Address header issue
danieljbruce Mar 28, 2023
1519ad3
Merge branch 'main' of https://github.com/googleapis/nodejs-datastore…
danieljbruce Mar 28, 2023
0ebabd5
Add databaseId for encoding and decoding keys
danieljbruce Mar 28, 2023
6449d10
Revert "Add databaseId for encoding and decoding keys"
danieljbruce Mar 29, 2023
e9ee522
Try a test with the namespace
danieljbruce Mar 29, 2023
1ba9756
Use the other datastore in the test
danieljbruce Mar 29, 2023
d35cdee
Create a test that looks at specific details
danieljbruce Mar 29, 2023
769417d
Add additional checks to existing test
danieljbruce Mar 29, 2023
e694341
Revert "Revert "Add databaseId for encoding and decoding keys""
danieljbruce Mar 29, 2023
f307f4d
Revert "Revert "Revert "Add databaseId for encoding and decoding keys"""
danieljbruce Mar 29, 2023
0f27ea4
Merge branch 'main' of https://github.com/googleapis/nodejs-datastore…
danieljbruce Mar 31, 2023
76a3dfc
Rename the second database to secondDatabase
danieljbruce May 10, 2023
3fd19fb
Use the database called multidb-test
danieljbruce May 19, 2023
d4e3f91
Merge branch 'main' into multi-db
danieljbruce May 31, 2023
771390c
Eliminate function on index that adds the id
danieljbruce Jun 9, 2023
ae15bc4
Fix the test so that the assertion error bubbles
danieljbruce Jun 9, 2023
6b5c542
Merge branch 'main' into multi-db
danieljbruce Jun 9, 2023
da64dab
Add types so that purpose of function is clear
danieljbruce Aug 8, 2023
8d170fd
Merge branch 'main' into multi-db
danieljbruce Aug 14, 2023
d49e5fc
Add types and names so that the function works
danieljbruce Aug 14, 2023
e7082ab
Merge branch 'multi-db' of https://github.com/danieljbruce/nodejs-dat…
danieljbruce Aug 14, 2023
b50b065
Eliminate repeated code fragments
danieljbruce Aug 15, 2023
f3aadc2
Better names and code organization
danieljbruce Aug 15, 2023
65605e8
Change the variable name of the default key
danieljbruce Aug 15, 2023
6986d4c
Refactor the post key hierarchy out
danieljbruce Aug 15, 2023
0378dea
Add only to the test we are interested in
danieljbruce Aug 18, 2023
a190512
Revert "Add only to the test we are interested in"
danieljbruce Aug 18, 2023
4b62317
Merge branch 'main' into multi-db
danieljbruce Aug 21, 2023
88e20ed
Merge branch 'main' of https://github.com/googleapis/nodejs-datastore…
danieljbruce Sep 20, 2023
739796f
Add second database id
danieljbruce Sep 20, 2023
87450ef
Remove unnecessary mocks
danieljbruce Sep 20, 2023
28b5aaa
Remove unused imports
danieljbruce Sep 20, 2023
46a5627
Add parameterized testing to the test/index file
danieljbruce Sep 20, 2023
2cf3d98
Add parameterized testing to test/transaction.ts
danieljbruce Sep 20, 2023
9505083
This ensures that the tests still run on the deft
danieljbruce Sep 20, 2023
e34735a
Add parameterized testing for the system tests
danieljbruce Sep 20, 2023
eb55764
Use default datastore variable in new tests
danieljbruce Sep 20, 2023
992117f
inline add database id
danieljbruce Sep 21, 2023
7312f49
Modify the namespace in parameterized tests
danieljbruce Sep 21, 2023
aef80a1
Move databaseId to SharedQueryOptions for alignme
danieljbruce Sep 21, 2023
72255ef
Run the linter
danieljbruce Sep 27, 2023
07dc746
Merge branch 'main' into multi-db
danieljbruce Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {Transaction} from './transaction';
import {promisifyAll} from '@google-cloud/promisify';
import {google} from '../protos/protos';
import {AggregateQuery} from './aggregate';
import {addDatabaseIdToRequest} from './util';
kolea2 marked this conversation as resolved.
Show resolved Hide resolved

const {grpc} = new GrpcClient();

Expand Down Expand Up @@ -699,6 +700,16 @@ class Datastore extends DatastoreRequest {
);
}

/**
* Gets the database id that all requests will be run against.
*
* @returns {string} The database id that the current client is set to that
* requests will run against.
*/
getDatabaseId(): string | undefined {
return this.options.databaseId;
}

getProjectId(): Promise<string> {
return this.auth.getProjectId();
}
Expand Down Expand Up @@ -1817,7 +1828,9 @@ promisifyAll(Datastore, {
'double',
'isDouble',
'geoPoint',
'getDatabaseId',
'getProjectId',
'getRequestWithDatabaseId',
'getSharedQueryOptions',
'isGeoPoint',
'index',
Expand Down Expand Up @@ -1898,6 +1911,7 @@ export interface DatastoreOptions extends GoogleAuthOptions {
namespace?: string;
apiEndpoint?: string;
sslCreds?: ChannelCredentials;
databaseId?: string;
}

export interface KeyToLegacyUrlSafeCallback {
Expand Down
4 changes: 4 additions & 0 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
import {Datastore} from '.';
import ITimestamp = google.protobuf.ITimestamp;
import {AggregateQuery} from './aggregate';
import {addDatabaseIdToRequest} from './util';

/**
* A map of read consistency values to proto codes.
Expand Down Expand Up @@ -995,6 +996,8 @@ class DatastoreRequest {
}
}

addDatabaseIdToRequest(datastore, reqOpts);
kolea2 marked this conversation as resolved.
Show resolved Hide resolved

if (method === 'rollback') {
reqOpts.transaction = this.id;
}
Expand Down Expand Up @@ -1155,6 +1158,7 @@ export interface SharedQueryOptions {
};
}
export interface RequestOptions extends SharedQueryOptions {
databaseId?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this here and not along side wherever reqOpts.projectId lives? (looking at request.ts, line 1030)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

databaseId is in RequestOptions because reqOpts is of type RequestOptions and reqOpts is what gets sent here to the backend.

projectId lives in sharedQueryOptions so therefore also lives in RequestOptions by inheritance which is why we can attach projectId on line 1030. Now the reason databaseId should not live in sharedQueryOptions is because then they would be included in RunAggregationQueryRequest, but they do not need to be. I am not sure why projectId lives in sharedQueryOptions and perhaps that is something that needs to be investigated further.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline, will move databaseId to shared query options to match proto: example https://github.com/googleapis/googleapis/blob/master/google/datastore/v1/datastore.proto#L208-L234

mutations?: google.datastore.v1.IMutation[];
keys?: Entity;
transactionOptions?: {
Expand Down
19 changes: 19 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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
//
// https://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.

export function addDatabaseIdToRequest(datastore: any, reqOpts: any) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like it's only used in one place in the (non test) code. Is this necessary, or can we simply inline it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we could inline it, move this to test/util.ts so that multiple test files can use this shared code and then add a comment to say that the same logic is in two places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second thought, I really think we should avoid having duplicate code in two places (this logic in the test code and this logic in request.ts). So I think we should keep this function here and call it directly from both places instead of going through addDatabaseIdToRequest on index.ts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add some more comments in tests specifically

if (datastore.options && datastore.options.databaseId) {
reqOpts.databaseId = datastore.options.databaseId;
}
}
113 changes: 112 additions & 1 deletion system-test/datastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import {Datastore, Index} from '../src';
import {google} from '../protos/protos';
import {Storage} from '@google-cloud/storage';
import {AggregateField} from '../src/aggregate';
import {PropertyFilter, EntityFilter, and, or} from '../src/filter';
import {entity} from '../src/entity';
import KEY_SYMBOL = entity.KEY_SYMBOL;
import {PropertyFilter, and, or} from '../src/filter';

const SECOND_DATABASE_ID = 'multidb-test';

describe('Datastore', () => {
const testKinds: string[] = [];
Expand Down Expand Up @@ -312,6 +314,115 @@ describe('Datastore', () => {
await datastore.delete(postKey);
});

describe('multi-db support for read and write operations', () => {
it('should run a query with another database', async () => {
// First verify that a record gets written to datastore
const postKey = datastore.key(['Post', 'post1']);
await datastore.save({key: postKey, data: post});
const query = datastore.createQuery('Post').hasAncestor(postKey);
const [defaultDatastoreResults] = await datastore.runQuery(query);
assert.strictEqual(defaultDatastoreResults.length, 1);
const [entity] = await datastore.get(postKey);
assert.strictEqual(entity.author, 'Silvano');
// With another database, verify that a query returns no results
const otherDatastore = new Datastore({
namespace: `${Date.now()}`,
databaseId: SECOND_DATABASE_ID,
});
const [secondDatastoreResults] = await otherDatastore.runQuery(query);
assert.strictEqual(secondDatastoreResults.length, 0);
const [otherEntity] = await otherDatastore.get(postKey);
assert(typeof otherEntity === 'undefined');
// Cleanup
await datastore.delete(postKey);
});
it('should ensure save works with another database', async () => {
// First verify that the default database is empty
const postKey = datastore.key(['Post', 'post1']);
const query = datastore.createQuery('Post').hasAncestor(postKey);
const [defaultDatastoreResults] = await datastore.runQuery(query);
assert.strictEqual(defaultDatastoreResults.length, 0);
const [originalSecondaryResults] = await datastore.runQuery(query);
assert.strictEqual(originalSecondaryResults.length, 0);
const [entity] = await datastore.get(postKey);
assert(typeof entity === 'undefined');
// With another database, verify that saving to the database works
const otherDatastore = new Datastore({
namespace: `${Date.now()}`,
databaseId: SECOND_DATABASE_ID,
});
await otherDatastore.save({key: postKey, data: post});
const [secondDatastoreResults] = await otherDatastore.runQuery(query);
assert.strictEqual(secondDatastoreResults.length, 1);
const [originalResults] = await datastore.runQuery(query);
assert.strictEqual(originalResults.length, 0);
const [otherEntity] = await otherDatastore.get(postKey);
assert.strictEqual(otherEntity.author, 'Silvano');
// Cleanup
await otherDatastore.delete(postKey);
});
it('should ensure save respects the databaseId parameter per key', async () => {
// First write entities to the database by specifying the database in the key
const otherDatastore = new Datastore({
namespace: `${Date.now()}`,
databaseId: SECOND_DATABASE_ID,
});
const dataD1 = Object.assign({}, post);
dataD1.author = 'D1';
const dataS1 = Object.assign({}, post);
dataS1.author = 'S1';
const dataS2 = Object.assign({}, post);
dataS2.author = 'S2';
const dataS3 = Object.assign({}, post);
dataS3.author = 'S3';
const postKeyDefault1 = datastore.key(['Post', 'postD1']);
const postKeySecondary1 = otherDatastore.key(['Post', 'postS1']);
const postKeySecondary2 = otherDatastore.key(['Post', 'postS2']);
const postKeySecondary3 = otherDatastore.key(['Post', 'postS3']);
await datastore.save({key: postKeyDefault1, data: dataD1});
await otherDatastore.save({key: postKeySecondary1, data: dataS1});
await otherDatastore.save({key: postKeySecondary2, data: dataS2});
await otherDatastore.save({key: postKeySecondary3, data: dataS3});
// Next, ensure that the default database has the right records
const query = datastore
.createQuery('Post')
.hasAncestor(postKeyDefault1);
const [defaultDatastoreResults] = await datastore.runQuery(query);
assert.strictEqual(defaultDatastoreResults.length, 1);
assert.strictEqual(defaultDatastoreResults[0].author, 'D1');
// Next, ensure that the other database has the right records
const queryS1 = otherDatastore
.createQuery('Post')
.hasAncestor(postKeySecondary1);
const [secondDatastoreResults1] = await otherDatastore.runQuery(
queryS1
);
assert.strictEqual(secondDatastoreResults1.length, 1);
assert.strictEqual(secondDatastoreResults1[0].author, 'S1');
const queryS2 = otherDatastore
.createQuery('Post')
.hasAncestor(postKeySecondary2);
const [secondDatastoreResults2] = await otherDatastore.runQuery(
queryS2
);
assert.strictEqual(secondDatastoreResults2.length, 1);
assert.strictEqual(secondDatastoreResults2[0].author, 'S2');
const queryS3 = otherDatastore
.createQuery('Post')
.hasAncestor(postKeySecondary3);
const [secondDatastoreResults3] = await otherDatastore.runQuery(
queryS3
);
assert.strictEqual(secondDatastoreResults3.length, 1);
assert.strictEqual(secondDatastoreResults3[0].author, 'S3');
// Cleanup
await datastore.delete(postKeyDefault1);
await otherDatastore.delete(postKeySecondary1);
await otherDatastore.delete(postKeySecondary2);
await otherDatastore.delete(postKeySecondary3);
});
});

it('should save/get/delete from a snapshot', async () => {
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
Expand Down
16 changes: 15 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as proxyquire from 'proxyquire';
import {PassThrough, Readable} from 'stream';

import * as ds from '../src';
import {DatastoreOptions} from '../src';
import {Datastore, DatastoreOptions} from '../src';
import {entity, Entity, EntityProto, EntityObject} from '../src/entity';
import {RequestConfig} from '../src/request';
import * as is from 'is';
Expand Down Expand Up @@ -91,6 +91,10 @@ function fakeGoogleAuth(...args: Array<{}>) {

let createInsecureOverride: Function | null;

const SECOND_DATABASE_ID = 'multidb-test';

export {SECOND_DATABASE_ID};

const fakeGoogleGax = {
GoogleAuth: fakeGoogleAuth,
GrpcClient: class extends gax.GrpcClient {
Expand Down Expand Up @@ -2150,4 +2154,14 @@ describe('Datastore', () => {
assert.strictEqual(key.name, 'Test');
});
});

describe('multi-db support', () => {
it('should get the database id from the client', async () => {
const otherDatastore = new Datastore({
namespace: `${Date.now()}`,
databaseId: SECOND_DATABASE_ID,
});
assert.strictEqual(otherDatastore.getDatabaseId(), SECOND_DATABASE_ID);
});
});
});
40 changes: 40 additions & 0 deletions test/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const {Query} = require('../src/query');
import {Datastore} from '../src';
import {AggregateField, AggregateQuery} from '../src/aggregate';
import {PropertyFilter, EntityFilter, or} from '../src/filter';
import {entity} from '../src/entity';
import {SECOND_DATABASE_ID} from './index';

describe('Query', () => {
const SCOPE = {} as Datastore;
Expand Down Expand Up @@ -432,4 +434,42 @@ describe('Query', () => {
assert.strictEqual(results, runQueryReturnValue);
});
});

it('should pass the database id to the generated layer', async () => {
const options = {
namespace: `${Date.now()}`,
databaseId: SECOND_DATABASE_ID,
projectId: 'test-project-id',
};
const clientName = 'DatastoreClient';
const otherDatastore = new Datastore(options);
const postKey = new entity.Key({path: ['Post', 'post1']});
// Initialize the generated client so that we can mock it out
const gapic = Object.freeze({
v1: require('../src/v1'),
});
otherDatastore.clients_.set(clientName, new gapic.v1[clientName](options));
const dataClient = otherDatastore.clients_.get(clientName);
const projectId = await otherDatastore.getProjectId();
if (dataClient) {
dataClient['commit'] = (
request: any,
options: any,
callback: (err?: unknown) => void
) => {
try {
assert.strictEqual(request.databaseId, SECOND_DATABASE_ID);
assert.strictEqual(request.projectId, projectId);
assert.strictEqual(
options.headers['google-cloud-resource-prefix'],
`projects/${projectId}`
);
} catch (e) {
callback(e);
}
callback();
};
}
await otherDatastore.save({key: postKey, data: {title: 'test'}});
});
});
9 changes: 9 additions & 0 deletions test/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
GetResponse,
RequestCallback,
} from '../src/request';
import {addDatabaseIdToRequest} from '../src/util';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Any = any;
Expand Down Expand Up @@ -87,6 +88,11 @@ describe('Request', () => {
});
v1FakeClientOverride = null;
request = new Request();
request.datastore = {
addDatabaseIdToRequest(d: any, r: any) {
addDatabaseIdToRequest(d, r);
},
};
});

afterEach(() => sandbox.restore());
Expand Down Expand Up @@ -1625,6 +1631,9 @@ describe('Request', () => {
callback(null, PROJECT_ID);
},
},
addDatabaseIdToRequest(d: any, r: any) {
addDatabaseIdToRequest(d, r);
},
};
});

Expand Down
4 changes: 4 additions & 0 deletions test/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {Datastore, DatastoreRequest, Query, TransactionOptions} from '../src';
import {Entity} from '../src/entity';
import * as tsTypes from '../src/transaction';
import * as sinon from 'sinon';
import {addDatabaseIdToRequest} from '../src/util';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Any = any;
Expand Down Expand Up @@ -61,6 +62,9 @@ describe('Transaction', () => {
request_() {},
projectId: PROJECT_ID,
namespace: NAMESPACE,
addDatabaseIdToRequest(d: any, r: any) {
kolea2 marked this conversation as resolved.
Show resolved Hide resolved
addDatabaseIdToRequest(d, r);
},
} as {} as Datastore;

function key(path: Path) {
Expand Down