#1253 [WIP] feat!: inline BeginTransaction with first statement

Open olavloite

@@ -254,6 +254,7 @@
Loading
254 254
  private instance: Instance;
255 255
  formattedName_: string;
256 256
  pool_: SessionPoolInterface;
257 +
  inlineBeginTx_?: boolean;
257 258
  queryOptions_?: spannerClient.spanner.v1.ExecuteSqlRequest.IQueryOptions;
258 259
  resourceHeader_: {[k: string]: string};
259 260
  request: DatabaseRequest;
@@ -356,10 +357,13 @@
Loading
356 357
      },
357 358
    } as {}) as ServiceObjectConfig);
358 359
359 -
    this.pool_ =
360 -
      typeof poolOptions === 'function'
361 -
        ? new (poolOptions as SessionPoolConstructor)(this, null)
362 -
        : new SessionPool(this, poolOptions);
360 +
    if (typeof poolOptions === 'function') {
361 +
      this.pool_ = new (poolOptions as SessionPoolConstructor)(this, null);
362 +
      this.inlineBeginTx_ = false;
363 +
    } else {
364 +
      this.pool_ = new SessionPool(this, poolOptions);
365 +
      this.inlineBeginTx_ = poolOptions && poolOptions.inlineBeginTx;
366 +
    }
363 367
    this.formattedName_ = formattedName_;
364 368
    this.instance = instance;
365 369
    this.resourceHeader_ = {
@@ -2543,14 +2547,23 @@
Loading
2543 2547
      typeof optionsOrRunFn === 'object'
2544 2548
        ? (optionsOrRunFn as RunTransactionOptions)
2545 2549
        : {};
2550 +
    if (this.inlineBeginTx_) {
2551 +
      options.inlineBeginTx = true;
2552 +
    }
2546 2553
2547 2554
    const getWriteSession = this.pool_.getWriteSession.bind(this.pool_);
2555 +
    const getReadSession = this.pool_.getReadSession.bind(this.pool_);
2548 2556
    // Loop to retry 'Session not found' errors.
2549 2557
    // (and yes, we like while (true) more than for (;;) here)
2550 2558
    // eslint-disable-next-line no-constant-condition
2551 2559
    while (true) {
2552 2560
      try {
2553 -
        const [session, transaction] = await promisify(getWriteSession)();
2561 +
        let session, transaction;
2562 +
        if (options.inlineBeginTx) {
2563 +
          [session] = await promisify(getReadSession)();
2564 +
        } else {
2565 +
          [session, transaction] = await promisify(getWriteSession)();
2566 +
        }
2554 2567
        const runner = new AsyncTransactionRunner<T>(
2555 2568
          session,
2556 2569
          transaction,

@@ -41,6 +41,7 @@
Loading
41 41
 */
42 42
export interface RunTransactionOptions {
43 43
  timeout?: number;
44 +
  inlineBeginTx?: boolean;
44 45
}
45 46
46 47
/**
@@ -191,7 +192,9 @@
Loading
191 192
    const transaction = this.session.transaction(
192 193
      (this.session.parent as Database).queryOptions_
193 194
    );
194 -
    await transaction.begin();
195 +
    if (!this.options.inlineBeginTx) {
196 +
      await transaction.begin();
197 +
    }
195 198
    return transaction;
196 199
  }
197 200
  /**

@@ -132,6 +132,8 @@
Loading
132 132
 *     write sessions represented as a float.
133 133
 * @property {number} [incStep=25] The number of new sessions to create when at
134 134
 *     least one more session is needed.
135 +
 * @property {boolean} [inlineBeginTx=false] Indicates whether a BeginTransaction
136 +
 *     option should be included with the first statement of a transaction.
135 137
 */
136 138
export interface SessionPoolOptions {
137 139
  acquireTimeout?: number;
@@ -145,6 +147,7 @@
Loading
145 147
  min?: number;
146 148
  writes?: number;
147 149
  incStep?: number;
150 +
  inlineBeginTx?: boolean;
148 151
}
149 152
150 153
const DEFAULTS: SessionPoolOptions = {
@@ -159,6 +162,7 @@
Loading
159 162
  min: 25,
160 163
  writes: 0,
161 164
  incStep: 25,
165 +
  inlineBeginTx: false,
162 166
};
163 167
164 168
/**

@@ -195,6 +195,7 @@
Loading
195 195
  protected _options!: spannerClient.spanner.v1.ITransactionOptions;
196 196
  protected _seqno = 1;
197 197
  id?: Uint8Array | string;
198 +
  inlineBegin?: boolean;
198 199
  ended: boolean;
199 200
  metadata?: spannerClient.spanner.v1.ITransaction;
200 201
  readTimestamp?: PreciseDate;
@@ -509,6 +510,8 @@
Loading
509 510
510 511
    if (this.id) {
511 512
      transaction.id = this.id as Uint8Array;
513 +
    } else if (this.inlineBegin) {
514 +
      transaction.begin = this._options;
512 515
    } else {
513 516
      transaction.singleUse = this._options;
514 517
    }
@@ -539,11 +542,27 @@
Loading
539 542
      });
540 543
    };
541 544
542 -
    return partialResultStream(makeRequest, {
545 +
    const prs = partialResultStream(makeRequest, {
543 546
      json,
544 547
      jsonOptions,
545 548
      maxResumeRetries,
546 549
    });
550 +
    this.addTransactionListener(prs);
551 +
    return prs;
552 +
  }
553 +
554 +
  private addTransactionListener(prs: PartialResultStream) {
555 +
    if (prs) {
556 +
      prs.once('response', (prs: google.spanner.v1.PartialResultSet) => {
557 +
        if (
558 +
          prs.metadata &&
559 +
          prs.metadata.transaction &&
560 +
          prs.metadata.transaction.id
561 +
        ) {
562 +
          this.id = prs.metadata.transaction.id;
563 +
        }
564 +
      });
565 +
    }
547 566
  }
548 567
549 568
  /**
@@ -913,6 +932,8 @@
Loading
913 932
      const transaction: spannerClient.spanner.v1.ITransactionSelector = {};
914 933
      if (this.id) {
915 934
        transaction.id = this.id as Uint8Array;
935 +
      } else if (this.inlineBegin) {
936 +
        transaction.begin = this._options;
916 937
      } else {
917 938
        transaction.singleUse = this._options;
918 939
      }
@@ -950,11 +971,13 @@
Loading
950 971
      });
951 972
    };
952 973
953 -
    return partialResultStream(makeRequest, {
974 +
    const prs = partialResultStream(makeRequest, {
954 975
      json,
955 976
      jsonOptions,
956 977
      maxResumeRetries,
957 978
    });
979 +
    this.addTransactionListener(prs);
980 +
    return prs;
958 981
  }
959 982
960 983
  /**
@@ -1253,6 +1276,7 @@
Loading
1253 1276
1254 1277
    this._queuedMutations = [];
1255 1278
    this._options = {readWrite: options};
1279 +
    this.inlineBegin = true;
1256 1280
  }
1257 1281
1258 1282
  batchUpdate(
@@ -1355,9 +1379,17 @@
Loading
1355 1379
      }
1356 1380
    );
1357 1381
1382 +
    const transaction: spannerClient.spanner.v1.ITransactionSelector = {};
1383 +
    if (this.id) {
1384 +
      transaction.id = this.id;
1385 +
    } else if (this.inlineBegin) {
1386 +
      transaction.begin = this._options;
1387 +
    } else {
1388 +
      throw new Error('BatchDml cannot be used with a singleUse transaction');
1389 +
    }
1358 1390
    const reqOpts: spannerClient.spanner.v1.ExecuteBatchDmlRequest = {
1359 1391
      session: this.session.formattedName_!,
1360 -
      transaction: {id: this.id!},
1392 +
      transaction: transaction,
1361 1393
      seqno: this._seqno++,
1362 1394
      statements,
1363 1395
    } as spannerClient.spanner.v1.ExecuteBatchDmlRequest;
@@ -1384,6 +1416,14 @@
Loading
1384 1416
        }
1385 1417
1386 1418
        const {resultSets, status} = resp;
1419 +
        if (
1420 +
          resultSets[0] &&
1421 +
          resultSets[0].metadata &&
1422 +
          resultSets[0].metadata.transaction &&
1423 +
          resultSets[0].metadata.transaction.id
1424 +
        ) {
1425 +
          this.id = resultSets[0].metadata.transaction.id;
1426 +
        }
1387 1427
        const rowCounts: number[] = resultSets.map(({stats}) => {
1388 1428
          return (
1389 1429
            (stats &&

@@ -189,7 +189,7 @@
Loading
189 189
      this.emit('stats', chunk.stats);
190 190
    }
191 191
192 -
    if (!this._fields && chunk.metadata) {
192 +
    if (!this._fields && chunk.metadata && chunk.metadata.rowType) {
193 193
      this._fields = chunk.metadata.rowType!
194 194
        .fields as google.spanner.v1.StructType.Field[];
195 195
    }

Everything is accounted for!

No changes detected that need to be reviewed.
What changes does Codecov check for?
Lines, not adjusted in diff, that have changed coverage data.
Files that introduced coverage data that had none before.
Files that have missing coverage data that once were tracked.
Files Coverage
bin 0.00%
src -<.01% 99.74%
.mocharc.js 79.31%
.prettierrc.js 0.00%
Project Totals (23 files) 98.60%
Loading