diff --git a/README.md b/README.md index abb13841a..ed4c06756 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre | Creates an incremental backup schedule | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/create-incremental-backup-schedule.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/create-incremental-backup-schedule.js,samples/README.md) | | Create-instance-without-default-backup-schedules | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/create-instance-without-default-backup-schedules.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/create-instance-without-default-backup-schedules.js,samples/README.md) | | CRUD | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/crud.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/crud.js,samples/README.md) | +| Adds split points to a database. | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-add-split-points.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-add-split-points.js,samples/README.md) | | Creates a new database with a specific default leader | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-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-create-with-default-leader.js,samples/README.md) | | Database-create-with-encryption-key | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-encryption-key.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-create-with-encryption-key.js,samples/README.md) | | Database-create-with-multiple-kms-keys | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-multiple-kms-keys.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-create-with-multiple-kms-keys.js,samples/README.md) | diff --git a/observability-test/database.ts b/observability-test/database.ts index a4fbb5561..9ae9ccd85 100644 --- a/observability-test/database.ts +++ b/observability-test/database.ts @@ -938,54 +938,60 @@ describe('Database', () => { getSessionStub.callsFake(callback => callback(fakeError)); - database.getTransaction(async err => { - assert.strictEqual(err, fakeError); + database.getTransaction( + {requestOptions: {transactionTag: 'transaction-tag'}}, + async err => { + assert.strictEqual(err, fakeError); - await provider.forceFlush(); - traceExporter.forceFlush(); - const spans = traceExporter.getFinishedSpans(); - assert.strictEqual(spans.length, 1, 'Exactly 1 span expected'); - withAllSpansHaveDBName(spans); + await provider.forceFlush(); + traceExporter.forceFlush(); + const spans = traceExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 1, 'Exactly 1 span expected'); + withAllSpansHaveDBName(spans); - const actualEventNames: string[] = []; - const actualSpanNames: string[] = []; - spans.forEach(span => { - actualSpanNames.push(span.name); - span.events.forEach(event => { - actualEventNames.push(event.name); + const actualEventNames: string[] = []; + const actualSpanNames: string[] = []; + spans.forEach(span => { + actualSpanNames.push(span.name); + span.events.forEach(event => { + actualEventNames.push(event.name); + }); }); - }); - - const expectedSpanNames = ['CloudSpanner.Database.getTransaction']; - assert.deepStrictEqual( - actualSpanNames, - expectedSpanNames, - `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` - ); - // In the event of a sessionPool error, we should not have events. - const expectedEventNames = []; - assert.deepStrictEqual( - actualEventNames, - expectedEventNames, - `event names mismatch:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}` - ); + const expectedSpanNames = ['CloudSpanner.Database.getTransaction']; + assert.deepStrictEqual( + actualSpanNames, + expectedSpanNames, + `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` + ); - // Ensure that the span actually produced an error that was recorded. - const firstSpan = spans[0]; - assert.strictEqual( - SpanStatusCode.ERROR, - firstSpan.status.code, - 'Expected an ERROR span status' - ); - assert.strictEqual( - 'pool error', - firstSpan.status.message, - 'Mismatched span status message' - ); + // In the event of a sessionPool error, we should not have events. + const expectedEventNames = []; + assert.deepStrictEqual( + actualEventNames, + expectedEventNames, + `event names mismatch:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}` + ); - done(); - }); + // Ensure that the span actually produced an error that was recorded. + const firstSpan = spans[0]; + assert.strictEqual( + SpanStatusCode.ERROR, + firstSpan.status.code, + 'Expected an ERROR span status' + ); + assert.strictEqual( + 'pool error', + firstSpan.status.message, + 'Mismatched span status message' + ); + assert.strictEqual( + spans[0].attributes['transaction.tag'], + 'transaction-tag' + ); + done(); + } + ); }); it('with no errors', done => { @@ -1342,7 +1348,10 @@ describe('Database', () => { 'Using Session', ]; assert.deepStrictEqual(actualEventNames, expectedEventNames); - + assert.strictEqual( + spans[0].attributes['transaction.tag'], + 'batch-write-tag' + ); done(); }); @@ -1475,52 +1484,59 @@ describe('Database', () => { callback(fakeErr) ); - database.runTransaction(err => { - assert.strictEqual(err, fakeErr); + database.runTransaction( + {requestOptions: {transactionTag: 'transaction-tag'}}, + err => { + assert.strictEqual(err, fakeErr); - const spans = traceExporter.getFinishedSpans(); - assert.strictEqual(spans.length, 1, 'Exactly 1 span expected'); - withAllSpansHaveDBName(spans); + const spans = traceExporter.getFinishedSpans(); + assert.strictEqual(spans.length, 1, 'Exactly 1 span expected'); + withAllSpansHaveDBName(spans); - const actualSpanNames: string[] = []; - const actualEventNames: string[] = []; - spans.forEach(span => { - actualSpanNames.push(span.name); - span.events.forEach(event => { - actualEventNames.push(event.name); + const actualSpanNames: string[] = []; + const actualEventNames: string[] = []; + spans.forEach(span => { + actualSpanNames.push(span.name); + span.events.forEach(event => { + actualEventNames.push(event.name); + }); }); - }); - const expectedSpanNames = ['CloudSpanner.Database.runTransaction']; - assert.deepStrictEqual( - actualSpanNames, - expectedSpanNames, - `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` - ); + const expectedSpanNames = ['CloudSpanner.Database.runTransaction']; + assert.deepStrictEqual( + actualSpanNames, + expectedSpanNames, + `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` + ); - // Ensure that the span actually produced an error that was recorded. - const firstSpan = spans[0]; - assert.strictEqual( - SpanStatusCode.ERROR, - firstSpan.status.code, - 'Expected an ERROR span status' - ); - assert.strictEqual( - 'getting a session', - firstSpan.status.message, - 'Mismatched span status message' - ); + // Ensure that the span actually produced an error that was recorded. + const firstSpan = spans[0]; + assert.strictEqual( + SpanStatusCode.ERROR, + firstSpan.status.code, + 'Expected an ERROR span status' + ); + assert.strictEqual( + 'getting a session', + firstSpan.status.message, + 'Mismatched span status message' + ); - // We don't expect events. - const expectedEventNames = []; - assert.deepStrictEqual( - actualEventNames, - expectedEventNames, - `Unexpected events:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}` - ); + // We don't expect events. + const expectedEventNames = []; + assert.deepStrictEqual( + actualEventNames, + expectedEventNames, + `Unexpected events:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}` + ); - done(); - }); + assert.strictEqual( + spans[0].attributes['transaction.tag'], + 'transaction-tag' + ); + done(); + } + ); }); it('with other errors when running the transaction', done => { @@ -1601,11 +1617,14 @@ describe('Database', () => { .stub(FakeAsyncTransactionRunner.prototype, 'run') .resolves(fakeValue); - const value = await database.runTransactionAsync(async txn => { - const result = await txn.run('SELECT 1'); - await txn.commit(); - return result; - }); + const value = await database.runTransactionAsync( + {requestOptions: {transactionTag: 'transaction-tag'}}, + async txn => { + const result = await txn.run('SELECT 1'); + await txn.commit(); + return result; + } + ); assert.strictEqual(value, fakeValue); @@ -1649,6 +1668,10 @@ describe('Database', () => { expectedEventNames, `Unexpected events:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}` ); + assert.strictEqual( + spans[0].attributes['transaction.tag'], + 'transaction-tag' + ); }); it('with error', async () => { @@ -1713,6 +1736,7 @@ describe('Database', () => { sql: 'SELECT * FROM table', a: 'b', c: 'd', + requestOptions: {requestTag: 'request-tag'}, }; let fakeSessionFactory: FakeSessionFactory; let fakeSession: FakeSession; @@ -1805,6 +1829,7 @@ describe('Database', () => { `Unexpected events:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}` ); + assert.strictEqual(spans[0].attributes['request.tag'], 'request-tag'); done(); }); }); @@ -1962,6 +1987,7 @@ describe('Database', () => { key: 'k999', thing: 'abc', }, + requestOptions: {requestTag: 'request-tag'}, }; let fakeSessionFactory: FakeSessionFactory; @@ -2070,6 +2096,7 @@ describe('Database', () => { `Unexpected events:\n\tGot: ${actualEventNames}\n\tWant: ${expectedEventNames}` ); + assert.strictEqual(spans[0].attributes['request.tag'], 'request-tag'); done(); }); }); diff --git a/observability-test/observability.ts b/observability-test/observability.ts index e759ceb1a..2a9e87bd8 100644 --- a/observability-test/observability.ts +++ b/observability-test/observability.ts @@ -160,12 +160,6 @@ describe('startTrace', () => { 'Missing gcp.client.repo attribute' ); - assert.equal( - span.attributes[SEMATTRS_DB_SYSTEM], - 'spanner', - 'Missing DB_SYSTEM attribute' - ); - assert.equal( span.attributes[SEMATTRS_DB_SQL_TABLE], 'table', diff --git a/observability-test/table.ts b/observability-test/table.ts index 558312c6c..09e47f52f 100644 --- a/observability-test/table.ts +++ b/observability-test/table.ts @@ -31,7 +31,6 @@ import {SpanStatusCode} from '@opentelemetry/api'; // eslint-disable-next-line n/no-extraneous-require const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base'); -const {generateWithAllSpansHaveDBName} = require('./helper'); const fakePfy = extend({}, pfy, { promisifyAll(klass, options) { @@ -83,6 +82,12 @@ describe('Table', () => { const NAME = 'table-name'; + const ROW = {}; + + const mutateRowsOptions = { + requestOptions: {transactionTag: 'transaction-tag'}, + }; + before(() => { Table = proxyquire('../src/table.js', { '@google-cloud/promisify': fakePfy, @@ -102,10 +107,6 @@ describe('Table', () => { traceExporter.reset(); }); - const withAllSpansHaveDBName = generateWithAllSpansHaveDBName( - DATABASE.formattedName_ - ); - function getExportedSpans(minCount: number) { traceExporter.forceFlush(); const spans = traceExporter.getFinishedSpans(); @@ -131,6 +132,13 @@ describe('Table', () => { return actualSpanNames; } + function verifySpanAttributes(span) { + const attributes = span.attributes; + assert.strictEqual(attributes['transaction.tag'], 'transaction-tag'); + assert.strictEqual(attributes['db.sql.table'], 'table-name'); + assert.strictEqual(attributes['db.name'], 'formatted-db-name'); + } + it('deleteRows', done => { const KEYS = ['key']; const stub = ( @@ -141,39 +149,39 @@ describe('Table', () => { callback(); }); - table.deleteRows(KEYS, err => { + table.deleteRows(KEYS, mutateRowsOptions, err => { assert.ifError(err); assert.strictEqual(stub.callCount, 1); - const actualSpanNames = spanNames(getExportedSpans(1)); + const spans = getExportedSpans(1); + const actualSpanNames = spanNames(spans); const expectedSpanNames = ['CloudSpanner.Table.deleteRows']; assert.deepStrictEqual( actualSpanNames, expectedSpanNames, `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` ); - + verifySpanAttributes(spans[0]); done(); }); }); - const ROW = {}; - it('insert', done => { const stub = ( sandbox.stub(transaction, 'insert') as sinon.SinonStub ).withArgs(table.name, ROW); - table.insert(ROW, err => { + table.insert(ROW, mutateRowsOptions, err => { assert.ifError(err); assert.strictEqual(stub.callCount, 1); - const actualSpanNames = spanNames(getExportedSpans(1)); + const spans = getExportedSpans(1); + const actualSpanNames = spanNames(spans); const expectedSpanNames = ['CloudSpanner.Table.insert']; assert.deepStrictEqual( actualSpanNames, expectedSpanNames, `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` ); - + verifySpanAttributes(spans[0]); done(); }); }); @@ -184,7 +192,7 @@ describe('Table', () => { .stub(DATABASE, 'runTransaction') .callsFake((opts, callback) => callback(fakeError)); - table.insert(ROW, err => { + table.insert(ROW, mutateRowsOptions, err => { assert.strictEqual(err, fakeError); const gotSpans = getExportedSpans(1); @@ -207,7 +215,7 @@ describe('Table', () => { expectedSpanNames, `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` ); - + verifySpanAttributes(gotSpans[0]); done(); }); }); @@ -217,12 +225,11 @@ describe('Table', () => { sandbox.stub(transaction, 'upsert') as sinon.SinonStub ).withArgs(table.name, ROW); - table.upsert(ROW, err => { + table.upsert(ROW, mutateRowsOptions, err => { assert.ifError(err); assert.strictEqual(stub.callCount, 1); const gotSpans = getExportedSpans(1); - withAllSpansHaveDBName(gotSpans); const actualSpanNames = spanNames(gotSpans); const expectedSpanNames = ['CloudSpanner.Table.upsert']; @@ -232,7 +239,7 @@ describe('Table', () => { expectedSpanNames, `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` ); - + verifySpanAttributes(gotSpans[0]); done(); }); }); @@ -243,11 +250,10 @@ describe('Table', () => { .stub(DATABASE, 'runTransaction') .callsFake((opts, callback) => callback(fakeError)); - table.upsert(ROW, err => { + table.upsert(ROW, mutateRowsOptions, err => { assert.strictEqual(err, fakeError); const gotSpans = getExportedSpans(1); - withAllSpansHaveDBName(gotSpans); const gotSpanStatus = gotSpans[0].status; const wantSpanStatus = { @@ -268,6 +274,7 @@ describe('Table', () => { `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` ); + verifySpanAttributes[gotSpans[0]]; done(); }); }); @@ -277,12 +284,11 @@ describe('Table', () => { sandbox.stub(transaction, 'replace') as sinon.SinonStub ).withArgs(table.name, ROW); - table.replace(ROW, err => { + table.replace(ROW, mutateRowsOptions, err => { assert.ifError(err); assert.strictEqual(stub.callCount, 1); const gotSpans = getExportedSpans(1); - withAllSpansHaveDBName(gotSpans); const actualSpanNames = spanNames(gotSpans); const expectedSpanNames = ['CloudSpanner.Table.replace']; @@ -292,6 +298,7 @@ describe('Table', () => { `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` ); + verifySpanAttributes(gotSpans[0]); done(); }); }); @@ -302,7 +309,7 @@ describe('Table', () => { .stub(DATABASE, 'runTransaction') .callsFake((opts, callback) => callback(fakeError)); - table.replace(ROW, err => { + table.replace(ROW, mutateRowsOptions, err => { assert.strictEqual(err, fakeError); const gotSpans = getExportedSpans(1); const gotSpanStatus = gotSpans[0].status; @@ -316,8 +323,6 @@ describe('Table', () => { `mismatch in span status:\n\tGot: ${JSON.stringify(gotSpanStatus)}\n\tWant: ${JSON.stringify(wantSpanStatus)}` ); - withAllSpansHaveDBName(gotSpans); - const actualSpanNames = spanNames(gotSpans); const expectedSpanNames = ['CloudSpanner.Table.replace']; assert.deepStrictEqual( @@ -325,6 +330,7 @@ describe('Table', () => { expectedSpanNames, `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` ); + verifySpanAttributes(gotSpans[0]); done(); }); diff --git a/observability-test/transaction.ts b/observability-test/transaction.ts index 176111f4b..0780e5464 100644 --- a/observability-test/transaction.ts +++ b/observability-test/transaction.ts @@ -33,6 +33,7 @@ const { SimpleSpanProcessor, } = require('@opentelemetry/sdk-trace-base'); const {generateWithAllSpansHaveDBName} = require('./helper'); +import {ExecuteSqlRequest, ReadRequest} from '../src/transaction'; describe('Transaction', () => { const sandbox = sinon.createSandbox(); @@ -64,6 +65,7 @@ describe('Transaction', () => { formattedName_: SESSION_NAME, request: REQUEST, requestStream: REQUEST_STREAM, + _observabilityOptions: {}, }; const PARTIAL_RESULT_STREAM = sandbox.stub(); @@ -100,11 +102,11 @@ describe('Transaction', () => { const SNAPSHOT_OPTIONS = {a: 'b', c: 'd'}; sandbox.stub(Snapshot, 'encodeTimestampBounds').returns(SNAPSHOT_OPTIONS); + SESSION._observabilityOptions = {tracerProvider: tracerProvider}; snapshot = new Snapshot(SESSION); snapshot._observabilityOptions = {tracerProvider: tracerProvider}; transaction = new Transaction(SESSION); - transaction._observabilityOptions = {tracerProvider: tracerProvider}; }); afterEach(async () => { @@ -444,10 +446,12 @@ describe('Transaction', () => { it('with error', done => { REQUEST_STREAM.resetHistory(); - const fakeQuery = Object.assign({}, QUERY, { + const fakeQuery: ExecuteSqlRequest = Object.assign({}, QUERY, { params: {a: undefined}, + requestOptions: {requestTag: 'request-tag'}, }); + snapshot.requestOptions = {transactionTag: 'transaction-tag'}; const stream = snapshot.runStream(fakeQuery); stream.on('error', error => { assert.strictEqual( @@ -487,11 +491,52 @@ describe('Transaction', () => { 'Unexpected span status message' ); + const attributes = exportResults.spans[0].attributes; + assert.strictEqual(attributes['transaction.tag'], 'transaction-tag'); + assert.strictEqual(attributes['db.name'], 'formatted-database-name'); + assert.strictEqual(attributes['request.tag'], 'request-tag'); done(); }); assert.ok(!REQUEST_STREAM.called, 'No request should be made'); }); }); + + describe('createReadStream', () => { + const TABLE = 'my-table-123'; + + beforeEach(() => { + PARTIAL_RESULT_STREAM.callsFake(makeRequest => makeRequest()); + }); + + it('without error', done => { + const fakeStream = new EventEmitter(); + REQUEST_STREAM.returns(fakeStream); + const request: ReadRequest = { + requestOptions: {requestTag: 'request-tag'}, + }; + snapshot.requestOptions = {transactionTag: 'transaction-tag'}; + const stream = snapshot.createReadStream(TABLE, request); + stream.on('end', () => { + const exportResults = extractExportedSpans(); + const actualSpanNames = exportResults.spanNames; + + const expectedSpanNames = ['CloudSpanner.Snapshot.createReadStream']; + assert.deepStrictEqual( + actualSpanNames, + expectedSpanNames, + `span names mismatch:\n\tGot: ${actualSpanNames}\n\tWant: ${expectedSpanNames}` + ); + + const attributes = exportResults.spans[0].attributes; + assert.strictEqual(attributes['transaction.tag'], 'transaction-tag'); + assert.strictEqual(attributes['db.sql.table'], TABLE); + assert.strictEqual(attributes['db.name'], 'formatted-database-name'); + assert.strictEqual(attributes['request.tag'], 'request-tag'); + done(); + }); + fakeStream.emit('end'); + }); + }); }); describe('rollback', () => { diff --git a/samples/README.md b/samples/README.md index 8cf41639d..bf09c546a 100644 --- a/samples/README.md +++ b/samples/README.md @@ -36,6 +36,7 @@ and automatic, synchronous replication for high availability. * [Creates an incremental backup schedule](#creates-an-incremental-backup-schedule) * [Create-instance-without-default-backup-schedules](#create-instance-without-default-backup-schedules) * [CRUD](#crud) + * [Adds split points to a database.](#adds-split-points-to-a-database.) * [Creates a new database with a specific default leader](#creates-a-new-database-with-a-specific-default-leader) * [Database-create-with-encryption-key](#database-create-with-encryption-key) * [Database-create-with-multiple-kms-keys](#database-create-with-multiple-kms-keys) @@ -135,7 +136,6 @@ and automatic, synchronous replication for high availability. * [Transaction](#transaction) * [Updates a backup schedule](#updates-a-backup-schedule) * [Updates an instance.](#updates-an-instance.) - * [Adds split points](#add-split-points) ## Before you begin @@ -168,22 +168,6 @@ __Usage:__ -### Add split points - -View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-add-split-points.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-add-split-points.js,samples/README.md) - -__Usage:__ - - -`node database-add-split-points.js ` - - ------ - - - ### Backups-cancel @@ -542,6 +526,23 @@ __Usage:__ +### Adds split points to a database. + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-add-split-points.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-add-split-points.js,samples/README.md) + +__Usage:__ + + +`node database-add-split-points.js ` + + +----- + + + + ### Creates a new database with a specific default leader View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/database-create-with-default-leader.js). diff --git a/src/database.ts b/src/database.ts index b1a8d37b3..94554e1d4 100644 --- a/src/database.ts +++ b/src/database.ts @@ -2274,29 +2274,35 @@ class Database extends common.GrpcServiceObject { ? (optionsOrCallback as GetTransactionOptions) : {}; - return startTrace('Database.getTransaction', this._traceConfig, span => { - this.pool_.getSession((err, session, transaction) => { - if (options.requestOptions) { - transaction!.requestOptions = Object.assign( - transaction!.requestOptions || {}, - options.requestOptions - ); - } - transaction?.setReadWriteTransactionOptions( - options as RunTransactionOptions - ); - - if (!err) { - span.addEvent('Using Session', {'session.id': session?.id}); - transaction!._observabilityOptions = this._observabilityOptions; - this._releaseOnEnd(session!, transaction!, span); - } else { - setSpanError(span, err); - } - span.end(); - cb!(err as grpc.ServiceError | null, transaction); - }); - }); + return startTrace( + 'Database.getTransaction', + { + ...this._traceConfig, + transactionTag: options.requestOptions?.transactionTag, + }, + span => { + this.pool_.getSession((err, session, transaction) => { + if (!err) { + if (options.requestOptions) { + transaction!.requestOptions = Object.assign( + transaction!.requestOptions || {}, + options.requestOptions + ); + } + transaction?.setReadWriteTransactionOptions( + options as RunTransactionOptions + ); + span.addEvent('Using Session', {'session.id': session?.id}); + transaction!._observabilityOptions = this._observabilityOptions; + this._releaseOnEnd(session!, transaction!, span); + } else { + setSpanError(span, err); + } + span.end(); + cb!(err as grpc.ServiceError | null, transaction); + }); + } + ); } /** @@ -2889,31 +2895,34 @@ class Database extends common.GrpcServiceObject { ? (optionsOrCallback as TimestampBounds) : {}; - const traceConfig = { - sql: query, - ...this._traceConfig, - }; - return startTrace('Database.run', traceConfig, span => { - this.runStream(query, options) - .on('error', err => { - setSpanError(span, err); - span.end(); - callback!(err as grpc.ServiceError, rows, stats, metadata); - }) - .on('response', response => { - if (response.metadata) { - metadata = response.metadata; - } - }) - .on('stats', _stats => (stats = _stats)) - .on('data', row => { - rows.push(row); - }) - .on('end', () => { - span.end(); - callback!(null, rows, stats, metadata); - }); - }); + return startTrace( + 'Database.run', + { + ...(query as ExecuteSqlRequest), + ...this._traceConfig, + }, + span => { + this.runStream(query, options) + .on('error', err => { + setSpanError(span, err); + span.end(); + callback!(err as grpc.ServiceError, rows, stats, metadata); + }) + .on('response', response => { + if (response.metadata) { + metadata = response.metadata; + } + }) + .on('stats', _stats => (stats = _stats)) + .on('data', row => { + rows.push(row); + }) + .on('end', () => { + span.end(); + callback!(null, rows, stats, metadata); + }); + } + ); } /** * Partitioned DML transactions are used to execute DML statements with a @@ -2940,28 +2949,33 @@ class Database extends common.GrpcServiceObject { query: string | RunPartitionedUpdateOptions, callback?: RunUpdateCallback ): void | Promise<[number]> { - const traceConfig = { - sql: query, - ...this._traceConfig, - }; - return startTrace('Database.runPartitionedUpdate', traceConfig, span => { - this.sessionFactory_.getSessionForPartitionedOps((err, session) => { - if (err) { - setSpanError(span, err); - span.end(); - callback!(err as ServiceError, 0); - return; - } - - this._runPartitionedUpdate(session!, query, (err, count) => { + return startTrace( + 'Database.runPartitionedUpdate', + { + ...(query as RunPartitionedUpdateOptions), + ...this._traceConfig, + requestTag: (query as RunPartitionedUpdateOptions)?.requestOptions + ?.requestTag, + }, + span => { + this.sessionFactory_.getSessionForPartitionedOps((err, session) => { if (err) { setSpanError(span, err); + span.end(); + callback!(err as ServiceError, 0); + return; } - span.end(); - callback!(err, count); + + this._runPartitionedUpdate(session!, query, (err, count) => { + if (err) { + setSpanError(span, err); + } + span.end(); + callback!(err, count); + }); }); - }); - }); + } + ); } _runPartitionedUpdate( @@ -3128,79 +3142,83 @@ class Database extends common.GrpcServiceObject { options?: TimestampBounds ): PartialResultStream { const proxyStream: Transform = through.obj(); - const traceConfig = { - sql: query, - ...this._traceConfig, - }; - return startTrace('Database.runStream', traceConfig, span => { - this.sessionFactory_.getSession((err, session) => { - if (err) { - setSpanError(span, err); - proxyStream.destroy(err); - span.end(); - return; - } + return startTrace( + 'Database.runStream', + { + ...(query as ExecuteSqlRequest), + ...this._traceConfig, + requestTag: (query as ExecuteSqlRequest)?.requestOptions?.requestTag, + }, + span => { + this.sessionFactory_.getSession((err, session) => { + if (err) { + setSpanError(span, err); + proxyStream.destroy(err); + span.end(); + return; + } - span.addEvent('Using Session', {'session.id': session?.id}); + span.addEvent('Using Session', {'session.id': session?.id}); - const snapshot = session!.snapshot(options, this.queryOptions_); + const snapshot = session!.snapshot(options, this.queryOptions_); - this._releaseOnEnd(session!, snapshot, span); + this._releaseOnEnd(session!, snapshot, span); - let dataReceived = false; - let dataStream = snapshot.runStream(query); + let dataReceived = false; + let dataStream = snapshot.runStream(query); - const endListener = () => { - snapshot.end(); - }; - dataStream - .once('data', () => (dataReceived = true)) - .once('error', err => { - setSpanError(span, err); + const endListener = () => { + snapshot.end(); + }; + dataStream + .once('data', () => (dataReceived = true)) + .once('error', err => { + setSpanError(span, err); - if ( - !dataReceived && - isSessionNotFoundError(err as grpc.ServiceError) && - !this.sessionFactory_.isMultiplexedEnabled() - ) { - // If it is a 'Session not found' error and we have not yet received - // any data, we can safely retry the query on a new session. - // Register the error on the session so the pool can discard it. - if (session) { - session.lastError = err as grpc.ServiceError; + if ( + !dataReceived && + isSessionNotFoundError(err as grpc.ServiceError) && + !this.sessionFactory_.isMultiplexedEnabled() + ) { + // If it is a 'Session not found' error and we have not yet received + // any data, we can safely retry the query on a new session. + // Register the error on the session so the pool can discard it. + if (session) { + session.lastError = err as grpc.ServiceError; + } + span.addEvent('No session available', { + 'session.id': session?.id, + }); + // Remove the current data stream from the end user stream. + dataStream.unpipe(proxyStream); + dataStream.removeListener('end', endListener); + dataStream.end(); + snapshot.end(); + span.end(); + // Create a new data stream and add it to the end user stream. + dataStream = this.runStream(query, options); + dataStream.pipe(proxyStream); + } else { + proxyStream.destroy(err); + snapshot.end(); } - span.addEvent('No session available', { - 'session.id': session?.id, - }); - // Remove the current data stream from the end user stream. - dataStream.unpipe(proxyStream); - dataStream.removeListener('end', endListener); - dataStream.end(); - snapshot.end(); - span.end(); - // Create a new data stream and add it to the end user stream. - dataStream = this.runStream(query, options); - dataStream.pipe(proxyStream); - } else { - proxyStream.destroy(err); - snapshot.end(); - } - }) - .on('stats', stats => proxyStream.emit('stats', stats)) - .on('response', response => proxyStream.emit('response', response)) - .once('end', endListener) - .pipe(proxyStream); - }); + }) + .on('stats', stats => proxyStream.emit('stats', stats)) + .on('response', response => proxyStream.emit('response', response)) + .once('end', endListener) + .pipe(proxyStream); + }); - finished(proxyStream, err => { - if (err) { - setSpanError(span, err); - } - span.end(); - }); + finished(proxyStream, err => { + if (err) { + setSpanError(span, err); + } + span.end(); + }); - return proxyStream as PartialResultStream; - }); + return proxyStream as PartialResultStream; + } + ); } /** @@ -3310,61 +3328,73 @@ class Database extends common.GrpcServiceObject { ? (optionsOrRunFn as RunTransactionOptions) : {}; - startTrace('Database.runTransaction', this._traceConfig, span => { - this.pool_.getSession((err, session?, transaction?) => { - if (err) { - setSpanError(span, err); - } + startTrace( + 'Database.runTransaction', + { + ...this._traceConfig, + transactionTag: options.requestOptions?.transactionTag, + }, + span => { + this.pool_.getSession((err, session?, transaction?) => { + if (err) { + setSpanError(span, err); + } - if (err && isSessionNotFoundError(err as grpc.ServiceError)) { - span.addEvent('No session available', { - 'session.id': session?.id, - }); - span.end(); - this.runTransaction(options, runFn!); - return; - } + if (err && isSessionNotFoundError(err as grpc.ServiceError)) { + span.addEvent('No session available', { + 'session.id': session?.id, + }); + span.end(); + this.runTransaction(options, runFn!); + return; + } - if (err) { - span.end(); - runFn!(err as grpc.ServiceError); - return; - } + if (err) { + span.end(); + runFn!(err as grpc.ServiceError); + return; + } - transaction!._observabilityOptions = this._observabilityOptions; + transaction!._observabilityOptions = this._observabilityOptions; - transaction!.setReadWriteTransactionOptions( - options as RunTransactionOptions - ); + transaction!.requestOptions = Object.assign( + transaction!.requestOptions || {}, + options.requestOptions + ); - const release = () => { - this.pool_.release(session!); - span.end(); - }; + transaction!.setReadWriteTransactionOptions( + options as RunTransactionOptions + ); + + const release = () => { + this.pool_.release(session!); + span.end(); + }; - const runner = new TransactionRunner( - session!, - transaction!, - runFn!, - options - ); + const runner = new TransactionRunner( + session!, + transaction!, + runFn!, + options + ); - runner.run().then(release, err => { - setSpanError(span, err!); + runner.run().then(release, err => { + setSpanError(span, err!); - if (isSessionNotFoundError(err)) { - span.addEvent('No session available', { - 'session.id': session?.id, - }); - release(); - this.runTransaction(options, runFn!); - } else { - setImmediate(runFn!, err); - release(); - } + if (isSessionNotFoundError(err)) { + span.addEvent('No session available', { + 'session.id': session?.id, + }); + release(); + this.runTransaction(options, runFn!); + } else { + setImmediate(runFn!, err); + release(); + } + }); }); - }); - }); + } + ); } runTransactionAsync( @@ -3448,9 +3478,13 @@ class Database extends common.GrpcServiceObject { let sessionId = ''; const getSession = this.pool_.getSession.bind(this.pool_); + return startTrace( 'Database.runTransactionAsync', - this._traceConfig, + { + ...this._traceConfig, + transactionTag: options?.requestOptions?.transactionTag, + }, async span => { // Loop to retry 'Session not found' errors. // (and yes, we like while (true) more than for (;;) here) @@ -3564,7 +3598,10 @@ class Database extends common.GrpcServiceObject { return startTrace( 'Database.batchWriteAtLeastOnce', - this._traceConfig, + { + ...this._traceConfig, + transactionTag: options?.requestOptions?.transactionTag, + }, span => { this.pool_.getSession((err, session) => { if (err) { diff --git a/src/instrument.ts b/src/instrument.ts index ba4628933..203aa85e1 100644 --- a/src/instrument.ts +++ b/src/instrument.ts @@ -17,10 +17,6 @@ import { ATTR_OTEL_SCOPE_NAME, ATTR_OTEL_SCOPE_VERSION, - SEMATTRS_DB_NAME, - SEMATTRS_DB_STATEMENT, - SEMATTRS_DB_SYSTEM, - SEMATTRS_DB_SQL_TABLE, } from '@opentelemetry/semantic-conventions'; import { @@ -89,6 +85,8 @@ interface traceConfig { sql?: string | SQLStatement; tableName?: string; dbName?: string; + transactionTag?: string | null; + requestTag?: string | null; opts?: ObservabilityOptions; } @@ -141,7 +139,6 @@ export function startTrace( SPAN_NAMESPACE_PREFIX + '.' + spanNameSuffix, {kind: SpanKind.CLIENT}, span => { - span.setAttribute(SEMATTRS_DB_SYSTEM, 'spanner'); span.setAttribute(ATTR_OTEL_SCOPE_NAME, TRACER_NAME); span.setAttribute(ATTR_OTEL_SCOPE_VERSION, TRACER_VERSION); span.setAttribute('gcp.client.service', 'spanner'); @@ -149,10 +146,16 @@ export function startTrace( span.setAttribute('gcp.client.repo', 'googleapis/nodejs-spanner'); if (config.tableName) { - span.setAttribute(SEMATTRS_DB_SQL_TABLE, config.tableName); + span.setAttribute('db.sql.table', config.tableName); } if (config.dbName) { - span.setAttribute(SEMATTRS_DB_NAME, config.dbName); + span.setAttribute('db.name', config.dbName); + } + if (config.requestTag) { + span.setAttribute('request.tag', config.requestTag); + } + if (config.transactionTag) { + span.setAttribute('transaction.tag', config.transactionTag); } const allowExtendedTracing = @@ -160,10 +163,10 @@ export function startTrace( if (config.sql && allowExtendedTracing) { const sql = config.sql; if (typeof sql === 'string') { - span.setAttribute(SEMATTRS_DB_STATEMENT, sql as string); + span.setAttribute('db.statement', sql as string); } else { const stmt = sql as SQLStatement; - span.setAttribute(SEMATTRS_DB_STATEMENT, stmt.sql); + span.setAttribute('db.statement', stmt.sql); } } diff --git a/src/session.ts b/src/session.ts index 040e9b2a8..374958fdc 100644 --- a/src/session.ts +++ b/src/session.ts @@ -41,6 +41,7 @@ import { addLeaderAwareRoutingHeader, getCommonHeaders, } from './common'; +import {ObservabilityOptions} from './instrument'; import {grpc, CallOptions} from 'google-gax'; import IRequestOptions = google.spanner.v1.IRequestOptions; import {Spanner} from '.'; @@ -118,6 +119,7 @@ export class Session extends common.GrpcServiceObject { lastUsed?: number; lastError?: grpc.ServiceError; commonHeaders_: {[k: string]: string}; + _observabilityOptions?: ObservabilityOptions; constructor(database: Database, name?: string) { const methods = { /** @@ -259,9 +261,10 @@ export class Session extends common.GrpcServiceObject { }, } as {} as ServiceObjectConfig); + this._observabilityOptions = database._observabilityOptions; this.commonHeaders_ = getCommonHeaders( (this.parent as Database).formattedName_, - database._observabilityOptions?.enableEndToEndTracing + this._observabilityOptions?.enableEndToEndTracing ); this.request = database.request; this.requestStream = database.requestStream; diff --git a/src/table.ts b/src/table.ts index dd557aa03..1649aa66c 100644 --- a/src/table.ts +++ b/src/table.ts @@ -1092,6 +1092,8 @@ class Table { opts: this._observabilityOptions, tableName: this.name, dbName: this.getDBName(), + transactionTag: (options as MutateRowsOptions)?.requestOptions + ?.transactionTag, }; startTrace('Table.' + method, traceConfig, span => { diff --git a/src/transaction.ts b/src/transaction.ts index 412ea61a1..4e89fba36 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -52,6 +52,7 @@ import { startTrace, setSpanError, setSpanErrorAndException, + traceConfig, } from './instrument'; import {RunTransactionOptions} from './transaction-runner'; import {injectRequestIDIntoHeaders, nextNthRequest} from './request_id_header'; @@ -297,6 +298,7 @@ export class Snapshot extends EventEmitter { commonHeaders_: {[k: string]: string}; requestOptions?: Pick; _observabilityOptions?: ObservabilityOptions; + _traceConfig: traceConfig; protected _dbName?: string; /** @@ -365,6 +367,10 @@ export class Snapshot extends EventEmitter { this._dbName, this._observabilityOptions?.enableEndToEndTracing ); + this._traceConfig = { + opts: this._observabilityOptions, + dbName: this._dbName, + }; } /** @@ -447,35 +453,38 @@ export class Snapshot extends EventEmitter { addLeaderAwareRoutingHeader(headers); } - const traceConfig = { - opts: this._observabilityOptions, - dbName: this._dbName!, - }; - return startTrace('Snapshot.begin', traceConfig, span => { - span.addEvent('Begin Transaction'); - - this.request( - { - client: 'SpannerClient', - method: 'beginTransaction', - reqOpts, - gaxOpts, - headers: injectRequestIDIntoHeaders(headers, this.session), - }, - ( - err: null | grpc.ServiceError, - resp: spannerClient.spanner.v1.ITransaction - ) => { - if (err) { - setSpanError(span, err); - } else { - this._update(resp); + return startTrace( + 'Snapshot.begin', + { + transactionTag: this.requestOptions?.transactionTag, + ...this._traceConfig, + }, + span => { + span.addEvent('Begin Transaction'); + + this.request( + { + client: 'SpannerClient', + method: 'beginTransaction', + reqOpts, + gaxOpts, + headers: injectRequestIDIntoHeaders(headers, this.session), + }, + ( + err: null | grpc.ServiceError, + resp: spannerClient.spanner.v1.ITransaction + ) => { + if (err) { + setSpanError(span, err); + } else { + this._update(resp); + } + span.end(); + callback!(err, resp); } - span.end(); - callback!(err, resp); - } - ); - }); + ); + } + ); } /** @@ -712,10 +721,11 @@ export class Snapshot extends EventEmitter { addLeaderAwareRoutingHeader(headers); } - const traceConfig = { + const traceConfig: traceConfig = { + ...this._traceConfig, tableName: table, - opts: this._observabilityOptions, - dbName: this._dbName!, + transactionTag: this.requestOptions?.transactionTag, + requestTag: requestOptions?.requestTag, }; return startTrace('Snapshot.createReadStream', traceConfig, span => { @@ -999,25 +1009,27 @@ export class Snapshot extends EventEmitter { callback = cb as ReadCallback; } - const traceConfig = { - tableName: table, - opts: this._observabilityOptions, - dbName: this._dbName!, - }; - return startTrace('Snapshot.read', traceConfig, span => { - this.createReadStream(table, request) - .on('error', err => { - const e = err as grpc.ServiceError; - setSpanError(span, e); - span.end(); - callback!(e, null); - }) - .on('data', row => rows.push(row)) - .on('end', () => { - span.end(); - callback!(null, rows); - }); - }); + return startTrace( + 'Snapshot.read', + { + tableName: table, + ...this._traceConfig, + }, + span => { + this.createReadStream(table, request) + .on('error', err => { + const e = err as grpc.ServiceError; + setSpanError(span, e); + span.end(); + callback!(e, null); + }) + .on('data', row => rows.push(row)) + .on('end', () => { + span.end(); + callback!(null, rows); + }); + } + ); } /** @@ -1107,33 +1119,35 @@ export class Snapshot extends EventEmitter { let stats: google.spanner.v1.ResultSetStats; let metadata: google.spanner.v1.ResultSetMetadata; - const traceConfig = { - sql: query, - opts: this._observabilityOptions, - dbName: this._dbName!, - }; - startTrace('Snapshot.run', traceConfig, span => { - return this.runStream(query) - .on('error', (err, rows, stats, metadata) => { - setSpanError(span, err); - span.end(); - callback!(err, rows, stats, metadata); - }) - .on('response', response => { - if (response.metadata) { - metadata = response.metadata; - if (metadata.transaction && !this.id) { - this._update(metadata.transaction); + startTrace( + 'Snapshot.run', + { + ...(query as ExecuteSqlRequest), + ...this._traceConfig, + }, + span => { + return this.runStream(query) + .on('error', (err, rows, stats, metadata) => { + setSpanError(span, err); + span.end(); + callback!(err, rows, stats, metadata); + }) + .on('response', response => { + if (response.metadata) { + metadata = response.metadata; + if (metadata.transaction && !this.id) { + this._update(metadata.transaction); + } } - } - }) - .on('data', row => rows.push(row)) - .on('stats', _stats => (stats = _stats)) - .on('end', () => { - span.end(); - callback!(null, rows, stats, metadata); - }); - }); + }) + .on('data', row => rows.push(row)) + .on('stats', _stats => (stats = _stats)) + .on('end', () => { + span.end(); + callback!(null, rows, stats, metadata); + }); + } + ); } /** @@ -1304,10 +1318,11 @@ export class Snapshot extends EventEmitter { addLeaderAwareRoutingHeader(headers); } - const traceConfig = { - opts: this._observabilityOptions, - dbName: this._dbName!, + const traceConfig: traceConfig = { + transactionTag: this.requestOptions?.transactionTag, + requestTag: requestOptions?.requestTag, ...query, + ...this._traceConfig, }; return startTrace('Snapshot.runStream', traceConfig, span => { let attempt = 0; @@ -1702,34 +1717,38 @@ export class Dml extends Snapshot { query = {sql: query} as ExecuteSqlRequest; } - const traceConfig = { - opts: this._observabilityOptions, - dbName: this._dbName!, - ...query, - }; - return startTrace('Dml.runUpdate', traceConfig, span => { - this.run( - query, - ( - err: null | grpc.ServiceError, - rows: Rows, - stats: spannerClient.spanner.v1.ResultSetStats - ) => { - let rowCount = 0; + return startTrace( + 'Dml.runUpdate', + { + ...query, + ...this._traceConfig, + transactionTag: this.requestOptions?.transactionTag, + requestTag: query.requestOptions?.requestTag, + }, + span => { + this.run( + query, + ( + err: null | grpc.ServiceError, + rows: Rows, + stats: spannerClient.spanner.v1.ResultSetStats + ) => { + let rowCount = 0; + + if (stats && stats.rowCount) { + rowCount = Math.floor(stats[stats.rowCount] as number); + } - if (stats && stats.rowCount) { - rowCount = Math.floor(stats[stats.rowCount] as number); - } + if (err) { + setSpanError(span, err); + } - if (err) { - setSpanError(span, err); + span.end(); + callback!(err, rowCount); } - - span.end(); - callback!(err, rowCount); - } - ); - }); + ); + } + ); } } @@ -1967,13 +1986,15 @@ export class Transaction extends Dml { } else { transaction.begin = this._options; } + + const requestOptionsWithTag = this.configureTagOptions( + false, + this.requestOptions?.transactionTag ?? undefined, + (options as BatchUpdateOptions).requestOptions + ); const reqOpts: spannerClient.spanner.v1.ExecuteBatchDmlRequest = { session: this.session.formattedName_!, - requestOptions: this.configureTagOptions( - false, - this.requestOptions?.transactionTag ?? undefined, - (options as BatchUpdateOptions).requestOptions - ), + requestOptions: requestOptionsWithTag, transaction, seqno: this._seqno++, statements, @@ -1990,9 +2011,10 @@ export class Transaction extends Dml { addLeaderAwareRoutingHeader(headers); } - const traceConfig = { - opts: this._observabilityOptions, - dbName: this._dbName!, + const traceConfig: traceConfig = { + ...this._traceConfig, + transactionTag: requestOptionsWithTag?.transactionTag, + requestTag: (options as BatchUpdateOptions)?.requestOptions?.requestTag, }; return startTrace('Transaction.batchUpdate', traceConfig, span => { this.request( @@ -2163,98 +2185,107 @@ export class Transaction extends Dml { const requestOptions = (options as CommitOptions).requestOptions; const reqOpts: CommitRequest = {mutations, session, requestOptions}; - const traceConfig = { - opts: this._observabilityOptions, - dbName: this._dbName!, - }; - return startTrace('Transaction.commit', traceConfig, span => { - if (this.id) { - reqOpts.transactionId = this.id as Uint8Array; - } else if (!this._useInRunner) { - reqOpts.singleUseTransaction = this._options; - } else { - this.begin().then( - () => { - this.commit(options, (err, resp) => { - if (err) { - setSpanError(span, err); - } + return startTrace( + 'Transaction.commit', + { + transactionTag: this.requestOptions?.transactionTag, + ...this._traceConfig, + }, + span => { + if (this.id) { + reqOpts.transactionId = this.id as Uint8Array; + } else if (!this._useInRunner) { + reqOpts.singleUseTransaction = this._options; + } else { + this.begin().then( + () => { + this.commit(options, (err, resp) => { + if (err) { + setSpanError(span, err); + } + span.end(); + callback(err, resp); + }); + }, + err => { + setSpanError(span, err); span.end(); - callback(err, resp); - }); - }, - err => { - setSpanError(span, err); - span.end(); - callback(err, null); - } - ); - return; - } - - if ( - 'returnCommitStats' in options && - (options as CommitOptions).returnCommitStats - ) { - reqOpts.returnCommitStats = ( - options as CommitOptions - ).returnCommitStats; - } - if ( - 'maxCommitDelay' in options && - (options as CommitOptions).maxCommitDelay - ) { - reqOpts.maxCommitDelay = (options as CommitOptions).maxCommitDelay; - } - reqOpts.requestOptions = Object.assign( - requestOptions || {}, - this.requestOptions - ); - - const headers = this.commonHeaders_; - if (this._getSpanner().routeToLeaderEnabled) { - addLeaderAwareRoutingHeader(headers); - } + callback(err, null); + } + ); + return; + } - span.addEvent('Starting Commit'); + if ( + 'returnCommitStats' in options && + (options as CommitOptions).returnCommitStats + ) { + reqOpts.returnCommitStats = ( + options as CommitOptions + ).returnCommitStats; + } + if ( + 'maxCommitDelay' in options && + (options as CommitOptions).maxCommitDelay + ) { + reqOpts.maxCommitDelay = (options as CommitOptions).maxCommitDelay; + } + reqOpts.requestOptions = Object.assign( + requestOptions || {}, + this.requestOptions + ); - const database = this.session.parent as Database; - this.request( - { - client: 'SpannerClient', - method: 'commit', - reqOpts, - gaxOpts: gaxOpts, - headers: injectRequestIDIntoHeaders( - headers, - this.session, - nextNthRequest(database), - 1 - ), - }, - (err: null | Error, resp: spannerClient.spanner.v1.ICommitResponse) => { - this.end(); + const headers = this.commonHeaders_; + if (this._getSpanner().routeToLeaderEnabled) { + addLeaderAwareRoutingHeader(headers); + } - if (err) { - span.addEvent('Commit failed'); - setSpanError(span, err); - } else { - span.addEvent('Commit Done'); - } + span.addEvent('Starting Commit'); + + const database = this.session.parent as Database; + this.request( + { + client: 'SpannerClient', + method: 'commit', + reqOpts, + gaxOpts: gaxOpts, + headers: injectRequestIDIntoHeaders( + headers, + this.session, + nextNthRequest(database), + 1 + ), + }, + ( + err: null | Error, + resp: spannerClient.spanner.v1.ICommitResponse + ) => { + this.end(); + + if (err) { + span.addEvent('Commit failed'); + setSpanError(span, err); + } else { + span.addEvent('Commit Done'); + } - if (resp && resp.commitTimestamp) { - this.commitTimestampProto = resp.commitTimestamp; - this.commitTimestamp = new PreciseDate( - resp.commitTimestamp as DateStruct + if (resp && resp.commitTimestamp) { + this.commitTimestampProto = resp.commitTimestamp; + this.commitTimestamp = new PreciseDate( + resp.commitTimestamp as DateStruct + ); + } + err = Transaction.decorateCommitError( + err as ServiceError, + mutations ); - } - err = Transaction.decorateCommitError(err as ServiceError, mutations); - span.end(); - callback!(err as ServiceError | null, resp); - } - ); - }); + span.end(); + callback!(err as ServiceError | null, resp); + } + ); + } + ); } /** @@ -2545,11 +2576,7 @@ export class Transaction extends Dml { const callback = typeof gaxOptionsOrCallback === 'function' ? gaxOptionsOrCallback : cb!; - const traceConfig = { - opts: this._observabilityOptions, - dbName: this._dbName!, - }; - return startTrace('Transaction.rollback', traceConfig, span => { + return startTrace('Transaction.rollback', this._traceConfig, span => { if (!this.id) { span.addEvent('Transaction ID is unknown, nothing to rollback.'); span.end(); @@ -3060,21 +3087,23 @@ export class PartitionedDml extends Dml { query: string | ExecuteSqlRequest, callback?: RunUpdateCallback ): void | Promise { - const traceConfig = { - sql: query, - opts: this._observabilityOptions, - dbName: this._dbName!, - }; - return startTrace('PartitionedDml.runUpdate', traceConfig, span => { - super.runUpdate(query, (err, count) => { - if (err) { - setSpanError(span, err); - } - this.end(); - span.end(); - callback!(err, count); - }); - }); + return startTrace( + 'PartitionedDml.runUpdate', + { + ...(query as ExecuteSqlRequest), + ...this._traceConfig, + }, + span => { + super.runUpdate(query, (err, count) => { + if (err) { + setSpanError(span, err); + } + this.end(); + span.end(); + callback!(err, count); + }); + } + ); } }