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

This PR adds support for .transaction() #814

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

mdierolf
Copy link

New pull request for the functionality in this pull request:
#811

I had to create a new PR and close the old one, as github will not allow me to change the head branch once a PR is filed

This adds support for PromiseConnection.transaction() to MySQL2

@sidorares
Copy link
Owner

thoughts re new api @sushantdhiman @gajus @mscdex @dougwilson ?
In general I try to resist adding new surface api here but it looks quite useful for end user

https://github.com/CloudQuote/node-mysql2/blob/b3073f992512459eb06c053e75d49dfdb42f0f69/README.md#using-managed-transaction

@mscdex
Copy link
Contributor

mscdex commented Jul 26, 2018

I don't have an opinion as I don't use Promises.

@mdierolf
Copy link
Author

One thing to note:

The proposed .transaction() function includes options that the current .beginTransaction() function does not currently accept.

IMO, if .transaction() is going to accept options, then .beginTransaction() should probably do so as well.

Copy link
Collaborator

@sushantdhiman sushantdhiman left a comment

Choose a reason for hiding this comment

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

API looks good, I have a suggestion to add ISOLATION LEVELS as they are useful concept when working with transactions.

promise.js Outdated
return userPromise(self);
});

if (options.autoCommit === false) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You meant autoCommit : true?

Copy link
Author

Choose a reason for hiding this comment

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

Actually I meant !== true, as I don't want truthy objects to enable autocommit, only the exact value autoCommit: true

promise.js Outdated
if (options.autoCommit !== true) {
promiseChain = promiseChain.then(function() {
return self.query(
"START TRANSACTION" +
Copy link
Collaborator

Choose a reason for hiding this comment

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

You might also want to support isolation levels https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

A constant type can be exported directly by require('mysql2').ISOLATION_LEVELS

promise.js Outdated
@@ -214,6 +214,47 @@ PromiseConnection.prototype.changeUser = function(options) {
});
});
};
PromiseConnection.prototype.transaction = function(options,userPromise) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

/nitpick userPromise is not a promise, its a callback that will return a promise, name should reflect that

Copy link
Author

Choose a reason for hiding this comment

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

Nitpick accepted

@gajus
Copy link
Collaborator

gajus commented Jul 26, 2018

I have been using an interchangeable API without any issues for a while now (https://github.com/gajus/slonik#slonik-query-methods-transaction).

…tionLevel: as one of the supported parameters
@gajus
Copy link
Collaborator

gajus commented Jul 26, 2018

I would argue that beginTransaction() should be removed altogether in favour of transaction().

@sidorares
Copy link
Owner

I would argue that beginTransaction() should be removed altogether in favour of transaction().

I don't think it's an option, we have to account for mysql2 users who currently use beginTransaction and mysqljs/mysql users whu might occasionaly switch between libs. If we decide to needs to be coordinated between both and go through long deprecation cycle. Much easier for promise wrapper though

@sushantdhiman
Copy link
Collaborator

Looks good, will need some integration tests and it should be good to go

@dougwilson
Copy link
Collaborator

Looking at the user docs for the API I think it is a really neat use for promises.

As for this vs beginTransaction, I guess the only comment I would have is that the new API does not support callbacks at all while the old one does. If the old is removed that would be the only consideration: should callbacks just be removed from the entire driver surface?

@sidorares
Copy link
Owner

@dougwilson you mean remove .beginTransaction() from both promise wrapper and standard api? This is harder as I think 'standard' .beginTransaction() is used actively

@dougwilson
Copy link
Collaborator

Sorry, I meant that if the idea is after landing this new API that beginTransaction would end up deprecated and removed, then there wouldn't be a transaction callback API any longer.

@mdierolf
Copy link
Author

mdierolf commented Jul 30, 2018

I'd recommend leaving beginTransaction() for compatibility with mysqljs, and adding the following behavior:
-Calling beginTransaction() will mark the Connection as having an open transaction
-Calling commit() or rollback() will remove this mark from the Connection
-When a connection is released to the Pool, if it is marked as having an open transaction, rollback() will be called
-Remove the rollback-on-failure logic from PromisePool.transaction() and rely on the pool to automatically rollback on release

This model will allow you to have managed transactions when using the callback API, and managed transactions + automatic connection release when using the Promise API

@mdierolf
Copy link
Author

mdierolf commented Nov 18, 2018

Any thoughts on this PR? I've been using it in production for a while, and updated it yesterday to be compatible with the new class-style code that is now in promise.js, which seems to work fine

I'd like it if we could make a decision one way or another; if it's not something that should be included in the main tree i'd rather convert it into a monkey patch in a separate repository so I can maintain it without having to merge changes into my fork

Also, any feedback as far as automatically rolling back open transactions when a connection is returned to the pool? If some bad code throws an exception, and then returns a connection to the pool that has an already open transaction, it seems like that would be very bad situation.

)
.catch( err =>
this.rollback()
.catch(() => null)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably emit an error or even override the previous error, it certainly shouldn't be swallowed.


const startTransactionSQL =
"START TRANSACTION" +
( options.consistentSnapshot === true ? " WITH CONSISTENT SNAPSHOT," : "" ) +
Copy link
Contributor

Choose a reason for hiding this comment

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

Inconsistent quotes, apparently lint isn't enforcing this.

con.release();
return res;
})
.catch(err => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit, you probably want to merge the then and catch into one then(ok, err) chain instead, otherwise the catch handler would potentially catch .release() errors.

@henricavalcante
Copy link

I'm really looking forward to be able to use transactions on node-mysql2, if there is anything I can help here I'll be glad to do.

@mdierolf
Copy link
Author

So we've been using this for the past year, it helps a little bit in simplifying our code, but I am thinking of re-designing this, as it doesn't really solve the problem we envisioned.

We have a collection of related problems in the code we have which uses node-mysql2:

  1. We need a way to run one or more queries on the same connection - sometimes we use this API but other times we concatenate the queries and run them with "multipleStatements" option, which is kind of clunky to use since we have to put all our SQL into one long string.
  2. We are typically constructing WHERE strings and replacement values in a rather clunky fashion using two arrays we push into, which gets especially weird when using multipleStatements since the replacements do not correspond to a particular statements, so it is easy to pass values to the wrong statement if you do not construct the placeholders correctly.
  3. We need a more automated way of retrying failed queries (either because we were handed a dead connection from the pool, because the connection died in the middle of the sequence of queries, or because the transaction rolled back due to conflict)
  4. We need a way of routing read vs write queries to different SQL interface for scaling.

So to fix these 4 issues, i'm thinking of making a new module which implements a "managed" query interface that could run on top of the mysql or mysql2 driver - which would allow you to define a chain of queries, possibly with intermediate code run between them to do calculations, which would then run them either in a transaction or raw.

It could also flag the individual queries as read vs write so they can be routed to one or more pools, and would handle failures more intelligently - for example you might choose to retry queries twice then fail hard.

The idea being to add on a couple key convenience features that ORM systems implement while still writing raw SQL queries.

As such, i'm not sure if it makes a ton of sense to merge in one particular method of working with transactions into the MySQL driver - the API in the driver should probably be unopinitated (and allow you to do everything) - or opinionated but optional (like what I am thinking of)

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.

8 participants