diff --git a/README.md b/README.md index 2336401e3..f62dea368 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,13 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre | Datatypes | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/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) | | DML | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/dml.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.js,samples/README.md) | | Get-commit-stats | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/get-commit-stats.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/get-commit-stats.js,samples/README.md) | +| Creates a new value-storing index | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-create-storing.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/index-create-storing.js,samples/README.md) | +| Creates a new index | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-create.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/index-create.js,samples/README.md) | +| Executes a read-only SQL query using an existing index. | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-query-data.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/index-query-data.js,samples/README.md) | +| Reads data using an existing storing index. | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-read-data-with-storing.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/index-read-data-with-storing.js,samples/README.md) | +| Read data using an existing index. | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-read-data.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/index-read-data.js,samples/README.md) | | Indexing | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/indexing.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/indexing.js,samples/README.md) | +| Instance-with-processing-units | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/instance-with-processing-units.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/instance-with-processing-units.js,samples/README.md) | | Instance | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/instance.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/instance.js,samples/README.md) | | Numeric-add-column | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/numeric-add-column.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/numeric-add-column.js,samples/README.md) | | Numeric-query-parameter | [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/numeric-query-parameter.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/numeric-query-parameter.js,samples/README.md) | diff --git a/samples/README.md b/samples/README.md index 7ddd6ffa2..f13b9f6ab 100644 --- a/samples/README.md +++ b/samples/README.md @@ -32,7 +32,13 @@ and automatic, synchronous replication for high availability. * [Datatypes](#datatypes) * [DML](#dml) * [Get-commit-stats](#get-commit-stats) + * [Creates a new value-storing index](#creates-a-new-value-storing-index) + * [Creates a new index](#creates-a-new-index) + * [Executes a read-only SQL query using an existing index.](#executes-a-read-only-sql-query-using-an-existing-index.) + * [Reads data using an existing storing index.](#reads-data-using-an-existing-storing-index.) + * [Read data using an existing index.](#read-data-using-an-existing-index.) * [Indexing](#indexing) + * [Instance-with-processing-units](#instance-with-processing-units) * [Instance](#instance) * [Numeric-add-column](#numeric-add-column) * [Numeric-query-parameter](#numeric-query-parameter) @@ -366,6 +372,91 @@ __Usage:__ +### Creates a new value-storing index + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-create-storing.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/index-create-storing.js,samples/README.md) + +__Usage:__ + + +`node createStoringIndex ` + + +----- + + + + +### Creates a new index + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-create.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/index-create.js,samples/README.md) + +__Usage:__ + + +`node createIndex ` + + +----- + + + + +### Executes a read-only SQL query using an existing index. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-query-data.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/index-query-data.js,samples/README.md) + +__Usage:__ + + +`node queryDataWithIndex ` + + +----- + + + + +### Reads data using an existing storing index. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-read-data-with-storing.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/index-read-data-with-storing.js,samples/README.md) + +__Usage:__ + + +`node readDataWithStoringIndex ` + + +----- + + + + +### Read data using an existing index. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/index-read-data.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/index-read-data.js,samples/README.md) + +__Usage:__ + + +`node readDataWithIndex ` + + +----- + + + + ### Indexing View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/indexing.js). @@ -383,6 +474,23 @@ __Usage:__ +### Instance-with-processing-units + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/instance-with-processing-units.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/instance-with-processing-units.js,samples/README.md) + +__Usage:__ + + +`node samples/instance-with-processing-units.js` + + +----- + + + + ### Instance View the [source code](https://github.com/googleapis/nodejs-spanner/blob/master/samples/instance.js). diff --git a/src/index.ts b/src/index.ts index 861793b84..22eb051c5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -158,6 +158,7 @@ class Spanner extends GrpcService { auth: GoogleAuth; clients_: Map; instances_: Map; + projectIdReplaced_: boolean; projectFormattedName_: string; resourceHeader_: {[k: string]: string}; @@ -260,6 +261,7 @@ class Spanner extends GrpcService { this.auth = new GoogleAuth(this.options); this.clients_ = new Map(); this.instances_ = new Map(); + this.projectIdReplaced_ = false; this.projectFormattedName_ = 'projects/' + this.projectId; this.resourceHeader_ = { [CLOUD_RESOURCE_HEADER]: this.projectFormattedName_, @@ -889,6 +891,34 @@ class Spanner extends GrpcService { const gaxClient = this.clients_.get(clientName)!; let reqOpts = extend(true, {}, config.reqOpts); reqOpts = replaceProjectIdToken(reqOpts, projectId!); + // It would have been preferable to replace the projectId already in the + // constructor of Spanner, but that is not possible as auth.getProjectId + // is an async method. This is therefore the first place where we have + // access to the value that should be used instead of the placeholder. + if (!this.projectIdReplaced_) { + this.projectId = replaceProjectIdToken(this.projectId, projectId!); + this.projectFormattedName_ = replaceProjectIdToken( + this.projectFormattedName_, + projectId! + ); + this.instances_.forEach(instance => { + instance.formattedName_ = replaceProjectIdToken( + instance.formattedName_, + projectId! + ); + instance.databases_.forEach(database => { + database.formattedName_ = replaceProjectIdToken( + database.formattedName_, + projectId! + ); + }); + }); + this.projectIdReplaced_ = true; + } + config.headers[CLOUD_RESOURCE_HEADER] = replaceProjectIdToken( + config.headers[CLOUD_RESOURCE_HEADER], + projectId! + ); const requestFn = gaxClient[config.method].bind( gaxClient, reqOpts, diff --git a/test/index.ts b/test/index.ts index 021dc0bcd..2a3aad6a0 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1400,8 +1400,10 @@ describe('Spanner', () => { const replacedReqOpts = {}; replaceProjectIdTokenOverride = (reqOpts, projectId) => { - assert.deepStrictEqual(reqOpts, CONFIG.reqOpts); - assert.notStrictEqual(reqOpts, CONFIG.reqOpts); + if (typeof reqOpts === 'object') { + assert.deepStrictEqual(reqOpts, CONFIG.reqOpts); + assert.notStrictEqual(reqOpts, CONFIG.reqOpts); + } assert.strictEqual(projectId, PROJECT_ID); return replacedReqOpts; }; diff --git a/test/mockserver/mockspanner.ts b/test/mockserver/mockspanner.ts index 5ff7fe0f4..aab527ec7 100644 --- a/test/mockserver/mockspanner.ts +++ b/test/mockserver/mockspanner.ts @@ -18,6 +18,8 @@ import * as path from 'path'; import {google} from '../../protos/protos'; import {grpc} from 'google-gax'; import * as protoLoader from '@grpc/proto-loader'; +// eslint-disable-next-line node/no-extraneous-import +import {Metadata} from '@grpc/grpc-js'; import {Transaction} from '../../src'; import protobuf = google.spanner.v1; import Timestamp = google.protobuf.Timestamp; @@ -221,6 +223,7 @@ interface Request {} */ export class MockSpanner { private requests: Request[] = []; + private metadata: Metadata[] = []; private frozen = 0; private sessionCounter = 0; private sessions: Map = new Map< @@ -274,6 +277,7 @@ export class MockSpanner { resetRequests(): void { this.requests = []; + this.metadata = []; } /** @@ -283,6 +287,13 @@ export class MockSpanner { return this.requests; } + /** + * @return the metadata that have been received by this mock server. + */ + getMetadata(): Metadata[] { + return this.metadata; + } + /** * Registers a result for an SQL statement on the server. * @param sql The SQL statement that should return the result. @@ -417,6 +428,11 @@ export class MockSpanner { return undefined; } + private pushRequest(request: Request, metadata: Metadata): void { + this.requests.push(request); + this.metadata.push(metadata); + } + batchCreateSessions( call: grpc.ServerUnaryCall< protobuf.BatchCreateSessionsRequest, @@ -424,7 +440,7 @@ export class MockSpanner { >, callback: protobuf.Spanner.BatchCreateSessionsCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.batchCreateSessions.name) .then(() => { const sessions = new Array(); @@ -445,7 +461,7 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.CreateSessionCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.createSession.name).then(() => { callback(null, this.newSession(call.request!.database)); }); @@ -455,7 +471,7 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.GetSessionCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.getSession.name).then(() => { const session = this.sessions[call.request!.name]; if (session) { @@ -473,7 +489,7 @@ export class MockSpanner { >, callback: protobuf.Spanner.ListSessionsCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.listSessions.name).then(() => { callback( null, @@ -493,7 +509,7 @@ export class MockSpanner { >, callback: protobuf.Spanner.DeleteSessionCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); if (this.sessions.delete(call.request!.name)) { callback(null, google.protobuf.Empty.create()); } else { @@ -505,7 +521,7 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.ExecuteSqlCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); callback(createUnimplementedError('ExecuteSql is not yet implemented')); } @@ -515,7 +531,7 @@ export class MockSpanner { protobuf.PartialResultSet > ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.executeStreamingSql.name) .then(() => { if (call.request!.transaction && call.request!.transaction.id) { @@ -687,7 +703,7 @@ export class MockSpanner { >, callback: protobuf.Spanner.ExecuteBatchDmlCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.executeBatchDml.name) .then(() => { if (call.request!.transaction && call.request!.transaction.id) { @@ -778,12 +794,12 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.ReadCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); callback(createUnimplementedError('Read is not yet implemented')); } streamingRead(call: grpc.ServerWritableStream) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); call.emit( 'error', createUnimplementedError('StreamingRead is not yet implemented') @@ -798,7 +814,7 @@ export class MockSpanner { >, callback: protobuf.Spanner.BeginTransactionCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.beginTransaction.name) .then(() => { const session = this.sessions.get(call.request!.session); @@ -838,7 +854,7 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.CommitCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); this.simulateExecutionTime(this.commit.name) .then(() => { if (call.request!.transactionId) { @@ -888,7 +904,7 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.RollbackCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); const session = this.sessions.get(call.request!.session); if (session) { const buffer = Buffer.from(call.request!.transactionId as string); @@ -911,7 +927,7 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.PartitionQueryCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); callback(createUnimplementedError('PartitionQuery is not yet implemented')); } @@ -919,7 +935,7 @@ export class MockSpanner { call: grpc.ServerUnaryCall, callback: protobuf.Spanner.PartitionReadCallback ) { - this.requests.push(call.request!); + this.pushRequest(call.request!, call.metadata); callback(createUnimplementedError('PartitionQuery is not yet implemented')); } } diff --git a/test/spanner.ts b/test/spanner.ts index 45941c06d..044db7da0 100644 --- a/test/spanner.ts +++ b/test/spanner.ts @@ -53,6 +53,7 @@ import Priority = google.spanner.v1.RequestOptions.Priority; import {PreciseDate} from '@google-cloud/precise-date'; import PartialResultSet = google.spanner.v1.PartialResultSet; import protobuf = google.spanner.v1; +import {CLOUD_RESOURCE_HEADER} from '../src/common'; function numberToEnglishWord(num: number): string { switch (num) { @@ -135,8 +136,8 @@ describe('Spanner with mock server', () => { // TODO(loite): Enable when SPANNER_EMULATOR_HOST is supported. // Set environment variable for SPANNER_EMULATOR_HOST to the mock server. // process.env.SPANNER_EMULATOR_HOST = `localhost:${port}`; + process.env.GOOGLE_CLOUD_PROJECT = 'test-project'; spanner = new Spanner({ - projectId: 'fake-project-id', servicePath: 'localhost', port, sslCreds: grpc.credentials.createInsecure(), @@ -188,6 +189,24 @@ describe('Spanner with mock server', () => { } }); + it('should replace {{projectId}} in resource header', async () => { + const query = { + sql: selectSql, + }; + const database = newTestDatabase(); + try { + await database.run(query); + spannerMock.getMetadata().forEach(metadata => { + assert.strictEqual( + metadata.get(CLOUD_RESOURCE_HEADER)[0], + `projects/test-project/instances/instance/databases/${database.id}` + ); + }); + } finally { + await database.close(); + } + }); + it('should execute query with requestOptions', async () => { const priority = RequestOptions.Priority.PRIORITY_HIGH; const database = newTestDatabase();