!CHAPTER Transaction invocation ArangoDB transactions are different from transactions in SQL. In SQL, transactions are started with explicit *BEGIN* or *START TRANSACTION* command. Following any series of data retrieval or modification operations, an SQL transaction is finished with a *COMMIT* command, or rolled back with a *ROLLBACK* command. There may be client/server communication between the start and the commit/rollback of an SQL transaction. In ArangoDB, a transaction is always a server-side operation, and is executed on the server in one go, without any client interaction. All operations to be executed inside a transaction need to be known by the server when the transaction is started. There are no individual *BEGIN*, *COMMIT* or *ROLLBACK* transaction commands in ArangoDB. Instead, a transaction in ArangoDB is started by providing a description of the transaction to the *db._executeTransaction* Javascript function: ```js db._executeTransaction(description); ``` This function will then automatically start a transaction, execute all required data retrieval and/or modification operations, and at the end automatically commit the transaction. If an error occurs during transaction execution, the transaction is automatically aborted, and all changes are rolled back. !SUBSECTION Execute transaction @startDocuBlock executeTransaction !SUBSECTION Declaration of collections All collections which are to participate in a transaction need to be declared beforehand. This is a necessity to ensure proper locking and isolation. Collections can be used in a transaction in write mode or in read-only mode. If any data modification operations are to be executed, the collection must be declared for use in write mode. The write mode allows modifying and reading data from the collection during the transaction (i.e. the write mode includes the read mode). Contrary, using a collection in read-only mode will only allow performing read operations on a collection. Any attempt to write into a collection used in read-only mode will make the transaction fail. Collections for a transaction are declared by providing them in the *collections* attribute of the object passed to the *_executeTransaction* function. The *collections* attribute has the sub-attributes *read* and *write*: ```js db._executeTransaction({ collections: { write: [ "users", "logins" ], read: [ "recommendations" ] } }); ``` *read* and *write* are optional attributes, and only need to be specified if the operations inside the transactions demand for it. The contents of *read* or *write* can each be lists with collection names or a single collection name (as a string): ```js db._executeTransaction({ collections: { write: "users", read: "recommendations" } }); ``` **Note**: It is currently optional to specify collections for read-only access. Even without specifying them, it is still possible to read from such collections from within a transaction, but with relaxed isolation. Please refer to [Transactions Locking](../Transactions/LockingAndIsolation.md) for more details. !SUBSECTION Declaration of data modification and retrieval operations All data modification and retrieval operations that are to be executed inside the transaction need to be specified in a Javascript function, using the *action* attribute: ```js db._executeTransaction({ collections: { write: "users" }, action: function () { // all operations go here } }); ``` Any valid Javascript code is allowed inside *action* but the code may only access the collections declared in *collections*. *action* may be a Javascript function as shown above, or a string representation of a Javascript function: ``` db._executeTransaction({ collections: { write: "users" }, action: "function () { doSomething(); }" }); ``` Please note that any operations specified in *action* will be executed on the server, in a separate scope. Variables will be bound late. Accessing any Javascript variables defined on the client-side or in some other server context from inside a transaction may not work. Instead, any variables used inside *action* should be defined inside *action* itself: ``` db._executeTransaction({ collections: { write: "users" }, action: function () { var db = require(...).db; db.users.save({ ... }); } }); ``` When the code inside the *action* attribute is executed, the transaction is already started and all required locks have been acquired. When the code inside the *action* attribute finishes, the transaction will automatically commit. There is no explicit commit command. To make a transaction abort and roll back all changes, an exception needs to be thrown and not caught inside the transaction: ```js db._executeTransaction({ collections: { write: "users" }, action: function () { var db = require("internal").db; db.users.save({ _key: "hello" }); // will abort and roll back the transaction throw "doh!"; } }); ``` There is no explicit abort or roll back command. As mentioned earlier, a transaction will commit automatically when the end of the *action* function is reached and no exception has been thrown. In this case, the user can return any legal Javascript value from the function: ```js db._executeTransaction({ collections: { write: "users" }, action: function () { var db = require("internal").db; db.users.save({ _key: "hello" }); // will commit the transaction and return the value "hello" return "hello"; } }); ``` !SUBSECTION Examples The first example will write 3 documents into a collection named *c1*. The *c1* collection needs to be declared in the *write* attribute of the *collections* attribute passed to the *executeTransaction* function. The *action* attribute contains the actual transaction code to be executed. This code contains all data modification operations (3 in this example). ```js // setup db._create("c1"); db._executeTransaction({ collections: { write: [ "c1" ] }, action: function () { var db = require("internal").db; db.c1.save({ _key: "key1" }); db.c1.save({ _key: "key2" }); db.c1.save({ _key: "key3" }); } }); db.c1.count(); // 3 ``` Aborting the transaction by throwing an exception in the *action* function will revert all changes, so as if the transaction never happened: ``` js // setup db._create("c1"); db._executeTransaction({ collections: { write: [ "c1" ] }, action: function () { var db = require("internal").db; db.c1.save({ _key: "key1" }); db.c1.count(); // 1 db.c1.save({ _key: "key2" }); db.c1.count(); // 2 throw "doh!"; } }); db.c1.count(); // 0 ``` The automatic rollback is also executed when an internal exception is thrown at some point during transaction execution: ```js // setup db._create("c1"); db._executeTransaction({ collections: { write: [ "c1" ] }, action: function () { var db = require("internal").db; db.c1.save({ _key: "key1" }); // will throw duplicate a key error, not explicitly requested by the user db.c1.save({ _key: "key1" }); // we'll never get here... } }); db.c1.count(); // 0 ``` As required by the *consistency* principle, aborting or rolling back a transaction will also restore secondary indexes to the state at transaction start. The following example using a cap constraint should illustrate that: ```js // setup db._create("c1"); // limit the number of documents to 3 db.c1.ensureCapConstraint(3); // insert 3 documents db.c1.save({ _key: "key1" }); db.c1.save({ _key: "key2" }); db.c1.save({ _key: "key3" }); // this will push out key1 // we now have these keys: [ "key2", "key3", "key4" ] db.c1.save({ _key: "key4" }); db._executeTransaction({ collections: { write: [ "c1" ] }, action: function () { var db = require("internal").db; // this will push out key2. we now have keys [ "key3", "key4", "key5" ] db.c1.save({ _key: "key5" }); // will abort the transaction throw "doh!" } }); // we now have these keys back: [ "key2", "key3", "key4" ] ``` !SUBSECTION Cross-collection transactions There's also the possibility to run a transaction across multiple collections. In this case, multiple collections need to be declared in the *collections* attribute, e.g.: ```js // setup db._create("c1"); db._create("c2"); db._executeTransaction({ collections: { write: [ "c1", "c2" ] }, action: function () { var db = require("internal").db; db.c1.save({ _key: "key1" }); db.c2.save({ _key: "key2" }); } }); db.c1.count(); // 1 db.c2.count(); // 1 ``` Again, throwing an exception from inside the *action* function will make the transaction abort and roll back all changes in all collections: ```js // setup db._create("c1"); db._create("c2"); db._executeTransaction({ collections: { write: [ "c1", "c2" ] }, action: function () { var db = require("internal").db; for (var i = 0; i < 100; ++i) { db.c1.save({ _key: "key" + i }); db.c2.save({ _key: "key" + i }); } db.c1.count(); // 100 db.c2.count(); // 100 // abort throw "doh!" } }); db.c1.count(); // 0 db.c2.count(); // 0 ```