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

Add transactor.parentTransaction #5567

Merged
merged 2 commits into from Nov 29, 2023

Conversation

caseywebdev
Copy link
Contributor

@caseywebdev caseywebdev commented May 10, 2023

Recently our app has had the need to know when the base transaction has committed, regardless of nested transaction levels. This works with executionPromise when you're one transaction deep, but if you have nested transactions the executionPromise only refers to the inner-most transaction. This change adds a baseExecutionPromise property on the transactor that will always reference the outer-most executionPromise.

Our specific use case is that we have functions that receive transactions and may open up nested transactions which can open up more nested transactions. Sometimes in those transactions, offline work records are inserted into a queue table to be processed out-of-band of the current transaction. We want to start that work as soon as the transaction is committed, but if we attempt to process the work when executionPromise is resolved in a nested transaction, the outer transaction is still not committed and the records aren't visible to the work processors yet, so no work is consumed.

I've monkey-patched this for our purposes for now, but I think it's a valuable addition to the core. For anyone interested in the patch:

import Transaction from 'knex/lib/execution/transaction.js';
const { _evaluateContainer } = Transaction.prototype;
Object.assign(Transaction.prototype, {
  async _evaluateContainer(config, container) {
    return await _evaluateContainer.call(this, config, tx => {
      this.baseExecutionPromise = tx.baseExecutionPromise =
        this.outerTx?.baseExecutionPromise ?? tx.executionPromise;
      return container(tx);
    });
  }
});

@coveralls
Copy link

coveralls commented May 10, 2023

Coverage Status

coverage: 92.792% (+0.006%) from 92.786%
when pulling 7c318f7 on caseywebdev:base-execution-promise
into 770b2f2 on knex:master.

@caseywebdev
Copy link
Contributor Author

Any feedback here?

@caseywebdev
Copy link
Contributor Author

@OlivierCavadenti would you mind taking a look?

@caseywebdev
Copy link
Contributor Author

@OlivierCavadenti friendly ping

@rluvaton
Copy link
Member

rluvaton commented Nov 28, 2023

@kibertoad what if instead of having another property for the top transaction promise

we would have one of those to allow full traversal of the transactions:

  1. an array of transactions promises
  2. property that point to the parent transaction

I prefer option 2 as it's:

  1. simpler to implement IMO
  2. cleaner
  3. Less prone to leaks

@kibertoad
Copy link
Collaborator

@rluvaton yeah, I agree!
@caseywebdev does that sound reasonable to you?

@caseywebdev caseywebdev changed the title Add transactor.baseExecutionPromise Add transactor.parentTransaction Nov 29, 2023
@caseywebdev
Copy link
Contributor Author

Fine with me. I've updated the implementation.

Copy link
Member

@rluvaton rluvaton left a comment

Choose a reason for hiding this comment

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

Great job!

@@ -219,6 +219,10 @@ class Transaction extends EventEmitter {
return makeTransactor(this, connection, trxClient);
})
.then((transactor) => {
this.transactor = transactor;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

executionPromise is not available on outerTx directly. Need a reference to it somewhere.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks, yeah after I wrote I saw

@@ -219,6 +219,10 @@ class Transaction extends EventEmitter {
return makeTransactor(this, connection, trxClient);
})
.then((transactor) => {
this.transactor = transactor;
if (this.outerTx) {
transactor.parentTransaction = this.outerTx.transactor;
Copy link
Member

@rluvaton rluvaton Nov 29, 2023

Choose a reason for hiding this comment

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

Please define this and the transactor property in the constructor (with default undefined value) to take advantage of v8 hidden classes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems unprecedented? For example context, isTransaction, userParams, savepoint, commit, rollback, isCompleted, and executionPromise are all not defined on the QueryBuilder constructor.

@@ -219,6 +219,10 @@ class Transaction extends EventEmitter {
return makeTransactor(this, connection, trxClient);
})
.then((transactor) => {
this.transactor = transactor;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

executionPromise is not available on outerTx directly. Need a reference to it somewhere.

@rluvaton
Copy link
Member

@kibertoad LGTM

@kibertoad
Copy link
Collaborator

@caseywebdev I assume TS types need to be updated as well?

@caseywebdev
Copy link
Contributor Author

@caseywebdev I assume TS types need to be updated as well?

Just pushed

@kibertoad kibertoad merged commit d908f09 into knex:master Nov 29, 2023
29 of 30 checks passed
@kibertoad
Copy link
Collaborator

Thank you! Can you also add a note about this feature to https://github.com/knex/documentation?

@kibertoad
Copy link
Collaborator

Released in 3.1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants