### Transactions

DML operations can be wrapped by a transaction for both relational tables and NoSQL collections.

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060/testSchema')
    .then(session => {
        return session.startTransaction()
            .then(() => {
                const collection = session.getDefaultSchema().getCollection('testCollection');

                return collection.modify('name = "foo"')
                    .execute();
            })
            .then(() => {
                return session.commit();
            })
            .catch(err => {
                return session.rollback();
            });
    });
```

### Savepoints

Savepoints can be used for rolling back portions of transactions.

#### Creating a Savepoint

A transaction savepoint can be created using the `Session.setSavepoint()` method. The `Promise` returned when creating a savepoint is resolved to a string which is the name of the savepoint. If a name for the savepoint is not provided, one is generated in the form `connector-nodejs-{string}` where `{string}` is 32 random alphanumeric characters, such as:

```txt
connector-nodejs-097D39590784A43511E7B89CD9FB1810
```

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        return session.startTransaction();
            .then(() => {
                // providing a name for the savepoint
                return session.setSavepoint('sp');
            })
            .then(savepoint => {
                console.log(savepoint); // sp
            });
    });
```

Note: A defined identifier is taken literally. Empty strings are not allowed even though they are allowed by the server.

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        return session.startTransaction();
            .then(() => {
                // using an auto-generated name for the savepoint
                return session.setSavepoint();
            })
            .then(savepoint => {
                console.log(savepoint); // connector-nodejs-097D39590784A43511E7B89CD9FB1810
            });
    });
```

#### Rolling back to an existing savepoint

The savepoint name which is either provided by the user or generated by the connector is used to rollback to an existing savepoint with the `Session.rollbackTo()` method.

If the savepoint name is not valid or the savepoint does not exist, the promise will be rejected.

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        const collection = session.getSchema('testSchema').getCollection('testCollection');

        return session.startTransaction()
            .then(() => {
                return collection.add({ _id: '2', name: 'bar' })
                    .execute();
            })
            .then(() => {
                return session.setSavepoint();
            })
            .then(savepoint => {
                return collection.add({ _id: '3', name: 'baz' })
                    .execute()
                    .then(() => {
                        return session.rollbackTo(savepoint);
                    });
            })
            .then(() => {
                return session.commit();
            })
            .then(() => {
                return collection.find()
                    .execute();
            })
            .then(res => {
                console.log(res.fetchAll()); // [{ _id: '2', name: 'bar' }]
            });
    });
```

#### Releasing an existing savepoint

The savepoint name which is either provided by the user or generated by the connector is used to release to an existing savepoint via the `Session.releaseSavepoint()` method.

If the savepoint name is not valid or the savepoint does not exist, the promise will be rejected.

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        const collection = session.getSchema('testSchema').getCollection('testCollection');

        return session.startTransaction()
            .then(() => {
                return session.setSavepoint('sp');
            })
            .then(() => {
                return collection.add({ _id: '2', name: 'bar' })
                    .execute();
            })
            .then(() => {
                return session.releaseSavepoint('sp');
            });
    });
```

Attemping to release a nonexisting savepoint will result in a rejected `Promise`.

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        const collection = session.getSchema('testSchema').getCollection('testCollection');

        return session.startTransaction()
            .then(() => {
                return collection.add({ _id: '2', name: 'bar' })
                    .execute();
            })
            .then(() => {
                return session.releaseSavepoint('sp');
            });
    })
    .catch(err => {
        console.log(err.message); // SAVEPOINT sp does not exist
    });
```

Similarly, attempting to rollback to a nonexisting savepoint will result in a rejected `Promise`.

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        const collection = session.getSchema('testSchema').getCollection('testCollection');

        return session.startTransaction()
            .then(() => {
                return collection.add({ _id: '2', name: 'bar' })
                    .execute();
            })
            .then(() => {
                return session.releaseSavepoint('sp');
            });
    })
    .catch(err => {
        console.log(err.message); // SAVEPOINT sp does not exist
    });
```

If the savepoint was released and a rollback is attempted to that savepoint, then the Promise will be rejected.

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        const collection = session.getSchema('testSchema').getCollection('testCollection');

        return session.startTransaction()
            .then(() => {
                return session.setSavepoint('sp');
            })
            .then(() => {
                return collection.add({ _id: '2', name: 'bar' })
                    .execute();
            })
            .then(() => {
                return session.releaseSavepoint('sp');
            })
            .then(() => {
                return session.rollbackTo('sp');
            });
    })
    .catch(err => {
        console.log(err.message); // SAVEPOINT sp does not exist
    });
```

#### Savepoints in autocommit mode

The exact behavior of savepoints is defined by the server. This can lead to confusing behavior in combination with autocommit mode, which is enabled by default. Consider a snippet like the following with no explicit `BEGIN`, startTransaction() or similar call:

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        return session.setSavepoint('fun');
    })
    .then(savepoint => {
        return session.releaseSavepoint('fun');
    })
    .catch(err => {
        console.log(err.message); // SAVEPOINT fun does not exist
    });
```

In autocommit mode, the call to `setSavepoint()` will create a transaction, create a savepoint and then commit, which would erase the savepoint. In consequence, the call to `releaseSavepoint()` would throw an error ```SAVEPOINT `fun` does not exist```. For the savepoint to survive, the user needs to start an explicit transaction block first.

#### Savepoints with DDL statements

An open transaction is automatically closed and committed on executing a DDL statement. Consider the following code:

```javascript
const mysqlx = require('@mysql/xdevapi');

mysqlx.getSession('mysqlx://root@localhost:33060')
    .then(session => {
        return session.startTransaction()
            .then(() => {
                return session.setSavepoint('fun');
            })
            .then(savepoint => {
                return session.sql('CREATE TABLE schema.table')
                    .execute()
                    .then(() => {
                        return session.releaseSavepoint(savepoint);
                    });
            });
    })
    .catch(err => {
        console.log(err.message); // SAVEPOINT fun does not exist
    });
```

Since the transaction is closed, the savepoint also ceases to exist.
