1
0
Fork 0

Documented custom exception behavior (addresses issue #2561). (#2595)

This commit is contained in:
Dan Larkin 2017-06-18 16:50:01 -04:00 committed by Frank Celler
parent 5cd9ad7ee1
commit 9b53d3cd45
2 changed files with 76 additions and 51 deletions

View File

@ -1,6 +1,8 @@
devel
-----
* removed `exception` field from transaction error result; users should throw
explicit `Error` instances to return custom exceptions (addresses issue #2561)
v3.2.beta1 (2017-06-12)
-----------------------

View File

@ -10,13 +10,13 @@ SQL transaction is finished with a *COMMIT* command, or rolled back with a
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
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
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
@ -24,7 +24,7 @@ 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
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.
@ -61,7 +61,7 @@ Additionally, *object* can have the following optional attributes:
*action*.
The following attributes can be used for transactions in the RocksDB storage engine:
- *maxTransactionSize*: transaction size limit in bytes
- *intermediateCommitSize*: maximum total size of operations after which an intermediate
commit is performed automatically
@ -71,22 +71,22 @@ The following attributes can be used for transactions in the RocksDB storage eng
### 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.
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
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
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 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
@ -98,10 +98,10 @@ db._executeTransaction({
});
```
*read* and *write* are optional attributes, and only need to be specified if
*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 arrays collection names or a
The contents of *read* or *write* can each be lists arrays collection names or a
single collection name (as a string):
```js
@ -115,7 +115,7 @@ db._executeTransaction({
**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
from within a transaction, but with relaxed isolation. Please refer to
[Transactions Locking](../Transactions/LockingAndIsolation.md) for more details.
In order to make a transaction fail when a non-declared collection is used inside
@ -153,14 +153,14 @@ db._executeTransaction({
write: "users"
},
action: function () {
// all operations go here
// 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
*action* may be a Javascript function as shown above, or a string representation
of a Javascript function:
```
@ -171,10 +171,10 @@ db._executeTransaction({
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
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.
a transaction may not work.
Instead, any variables used inside *action* should be defined inside *action* itself:
```
@ -190,9 +190,9 @@ db._executeTransaction({
```
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
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.
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:
@ -205,7 +205,7 @@ db._executeTransaction({
action: function () {
var db = require("@arangodb").db;
db.users.save({ _key: "hello" });
// will abort and roll back the transaction
// will abort and roll back the transaction
throw "doh!";
}
});
@ -214,7 +214,7 @@ db._executeTransaction({
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
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
@ -225,16 +225,39 @@ db._executeTransaction({
action: function () {
var db = require("@arangodb").db;
db.users.save({ _key: "hello" });
// will commit the transaction and return the value "hello"
return "hello";
// will commit the transaction and return the value "hello"
return "hello";
}
});
```
### Custom exceptions
One may wish to define custom exceptions inside of a transaction. To have the
exception propagate upwards properly, please throw an an instance of base
JavaScript `Error` class or a derivative. To specify an error number, include it
as the `errorNumber` field. As an example:
```js
db._executeTransaction({
collections: {},
action: function () {
var err = new Error('My error context');
err.errorNumber = 1234;
throw err;
}
});
```
**Note**: In previous versions, custom exceptions which did not have an
`Error`-like form were simply converted to strings and exposed in the
`exception` field of the returned error. This is no longer the case, as it had
the potential to leak unwanted information if improperly used.
### 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
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.
@ -255,7 +278,7 @@ db._executeTransaction({
db.c1.save({ _key: "key3" });
}
});
db.c1.count(); // 3
db.c1.count(); // 3
```
Aborting the transaction by throwing an exception in the *action* function
@ -272,21 +295,21 @@ db._executeTransaction({
action: function () {
var db = require("@arangodb").db;
db.c1.save({ _key: "key1" });
db.c1.count(); // 1
db.c1.count(); // 1
db.c1.save({ _key: "key2" });
db.c1.count(); // 2
db.c1.count(); // 2
throw "doh!";
}
});
db.c1.count(); // 0
db.c1.count(); // 0
```
The automatic rollback is also executed when an internal exception is thrown
The automatic rollback is also executed when an internal exception is thrown
at some point during transaction execution:
```js
// setup
// setup
db._create("c1");
db._executeTransaction({
@ -296,27 +319,27 @@ db._executeTransaction({
action: function () {
var db = require("@arangodb").db;
db.c1.save({ _key: "key1" });
// will throw duplicate a key error, not explicitly requested by the user
// will throw duplicate a key error, not explicitly requested by the user
db.c1.save({ _key: "key1" });
// we'll never get here...
// we'll never get here...
}
});
db.c1.count(); // 0
db.c1.count(); // 0
```
As required by the *consistency* principle, aborting or rolling back a
As required by the *consistency* principle, aborting or rolling back a
transaction will also restore secondary indexes to the state at transaction
start.
### Cross-collection transactions
There's also the possibility to run a transaction across multiple collections.
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
// setup
db._create("c1");
db._create("c2");
@ -331,15 +354,15 @@ db._executeTransaction({
}
});
db.c1.count(); // 1
db.c1.count(); // 1
db.c2.count(); // 1
```
Again, throwing an exception from inside the *action* function will make the
Again, throwing an exception from inside the *action* function will make the
transaction abort and roll back all changes in all collections:
```js
// setup
// setup
db._create("c1");
db._create("c2");
@ -353,13 +376,13 @@ db._executeTransaction({
db.c1.save({ _key: "key" + i });
db.c2.save({ _key: "key" + i });
}
db.c1.count(); // 100
db.c2.count(); // 100
// abort
db.c1.count(); // 100
db.c2.count(); // 100
// abort
throw "doh!"
}
});
db.c1.count(); // 0
db.c2.count(); // 0
db.c1.count(); // 0
db.c2.count(); // 0
```