Skip to content

Commit dec1489

Browse files
committed
Adding support for JavaScript transactions
1 parent f05756c commit dec1489

File tree

3 files changed

+141
-6
lines changed

3 files changed

+141
-6
lines changed

arangoasync/database.py

+103-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
]
66

77

8-
from typing import List, Optional, Sequence, TypeVar, cast
8+
from typing import Any, List, Optional, Sequence, TypeVar, cast
9+
from warnings import warn
910

1011
from arangoasync.collection import StandardCollection
1112
from arangoasync.connection import Connection
@@ -27,6 +28,7 @@
2728
ServerStatusError,
2829
TransactionAbortError,
2930
TransactionCommitError,
31+
TransactionExecuteError,
3032
TransactionInitError,
3133
TransactionListError,
3234
TransactionStatusError,
@@ -1099,6 +1101,85 @@ def response_handler(resp: Response) -> Jsons:
10991101

11001102
return await self._executor.execute(request, response_handler)
11011103

1104+
async def execute_transaction(
1105+
self,
1106+
command: str,
1107+
params: Optional[Json] = None,
1108+
read: Optional[str | Sequence[str]] = None,
1109+
write: Optional[str | Sequence[str]] = None,
1110+
exclusive: Optional[str | Sequence[str]] = None,
1111+
allow_implicit: Optional[bool] = None,
1112+
wait_for_sync: Optional[bool] = None,
1113+
lock_timeout: Optional[int] = None,
1114+
max_transaction_size: Optional[int] = None,
1115+
) -> Result[Any]:
1116+
"""Execute a JavaScript Transaction.
1117+
1118+
Warning:
1119+
JavaScript Transactions are deprecated from ArangoDB v3.12.0 onward and
1120+
will be removed in a future version.
1121+
1122+
Args:
1123+
command (str): The actual transaction operations to be executed, in the
1124+
form of stringified JavaScript code.
1125+
params (dict): Optional parameters passed into the JavaScript command.
1126+
read (str | list | None): Name(s) of collections read during transaction.
1127+
write (str | list | None): Name(s) of collections written to during
1128+
transaction with shared access.
1129+
exclusive (str | list | None): Name(s) of collections written to during
1130+
transaction with exclusive access.
1131+
allow_implicit (bool | None): Allow reading from undeclared collections.
1132+
wait_for_sync (bool | None): If `True`, will force the transaction to write
1133+
all data to disk before returning.
1134+
lock_timeout (int | None): Timeout for waiting on collection locks. Setting
1135+
it to 0 will prevent ArangoDB from timing out while waiting for a lock.
1136+
max_transaction_size (int | None): Transaction size limit in bytes.
1137+
1138+
Returns:
1139+
Any: Result of the transaction.
1140+
1141+
Raises:
1142+
TransactionExecuteError: If the operation fails on the server side.
1143+
1144+
References:
1145+
- `execute-a-javascript-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/javascript-transactions/#execute-a-javascript-transaction>`__
1146+
""" # noqa: 501
1147+
m = "JavaScript Transactions are deprecated from ArangoDB v3.12.0 onward and will be removed in a future version." # noqa: E501
1148+
warn(m, DeprecationWarning, stacklevel=2)
1149+
1150+
collections = dict()
1151+
if read is not None:
1152+
collections["read"] = read
1153+
if write is not None:
1154+
collections["write"] = write
1155+
if exclusive is not None:
1156+
collections["exclusive"] = exclusive
1157+
1158+
data: Json = dict(collections=collections, action=command)
1159+
if params is not None:
1160+
data["params"] = params
1161+
if wait_for_sync is not None:
1162+
data["waitForSync"] = wait_for_sync
1163+
if allow_implicit is not None:
1164+
data["allowImplicit"] = allow_implicit
1165+
if lock_timeout is not None:
1166+
data["lockTimeout"] = lock_timeout
1167+
if max_transaction_size is not None:
1168+
data["maxTransactionSize"] = max_transaction_size
1169+
1170+
request = Request(
1171+
method=Method.POST,
1172+
endpoint="/_api/transaction",
1173+
data=self.serializer.dumps(data),
1174+
)
1175+
1176+
def response_handler(resp: Response) -> Any:
1177+
if not resp.is_success:
1178+
raise TransactionExecuteError(resp, request)
1179+
return self.deserializer.loads(resp.raw_body)["result"]
1180+
1181+
return await self._executor.execute(request, response_handler)
1182+
11021183

11031184
class StandardDatabase(Database):
11041185
"""Standard database API wrapper.
@@ -1139,7 +1220,7 @@ async def begin_transaction(
11391220
all data to disk before returning
11401221
allow_implicit (bool | None): Allow reading from undeclared collections.
11411222
lock_timeout (int | None): Timeout for waiting on collection locks. Setting
1142-
it to 0 will make ArangoDB not time out waiting for a lock.
1223+
it to 0 will prevent ArangoDB from timing out while waiting for a lock.
11431224
max_transaction_size (int | None): Transaction size limit in bytes.
11441225
allow_dirty_read (bool | None): If `True`, allows the Coordinator to ask any
11451226
shard replica for the data, not only the shard leader. This may result
@@ -1155,7 +1236,10 @@ async def begin_transaction(
11551236
11561237
Raises:
11571238
TransactionInitError: If the operation fails on the server side.
1158-
"""
1239+
1240+
References:
1241+
- `begin-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#begin-a-stream-transaction>`__
1242+
""" # noqa: E501
11591243
collections = dict()
11601244
if read is not None:
11611245
collections["read"] = read
@@ -1244,7 +1328,10 @@ async def transaction_status(self) -> str:
12441328
12451329
Raises:
12461330
TransactionStatusError: If the transaction is not found.
1247-
"""
1331+
1332+
References:
1333+
- `get-the-status-of-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#get-the-status-of-a-stream-transaction>`__
1334+
""" # noqa: E501
12481335
request = Request(
12491336
method=Method.GET,
12501337
endpoint=f"/_api/transaction/{self.transaction_id}",
@@ -1263,7 +1350,10 @@ async def commit_transaction(self) -> None:
12631350
12641351
Raises:
12651352
TransactionCommitError: If the operation fails on the server side.
1266-
"""
1353+
1354+
References:
1355+
- `commit-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#commit-a-stream-transaction>`__
1356+
""" # noqa: E501
12671357
request = Request(
12681358
method=Method.PUT,
12691359
endpoint=f"/_api/transaction/{self.transaction_id}",
@@ -1276,7 +1366,14 @@ def response_handler(resp: Response) -> None:
12761366
await self._executor.execute(request, response_handler)
12771367

12781368
async def abort_transaction(self) -> None:
1279-
"""Abort the transaction."""
1369+
"""Abort the transaction.
1370+
1371+
Raises:
1372+
TransactionAbortError: If the operation fails on the server side.
1373+
1374+
References:
1375+
- `abort-a-stream-transaction <https://docs.arangodb.com/stable/develop/http-api/transactions/stream-transactions/#abort-a-stream-transaction>`__
1376+
""" # noqa: E501
12801377
request = Request(
12811378
method=Method.DELETE,
12821379
endpoint=f"/_api/transaction/{self.transaction_id}",

arangoasync/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ class TransactionCommitError(ArangoServerError):
203203
"""Failed to commit transaction."""
204204

205205

206+
class TransactionExecuteError(ArangoServerError):
207+
"""Failed to execute JavaScript transaction."""
208+
209+
206210
class TransactionInitError(ArangoServerError):
207211
"""Failed to initialize transaction."""
208212

tests/test_transaction.py

+34
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,45 @@
77
from arangoasync.exceptions import (
88
TransactionAbortError,
99
TransactionCommitError,
10+
TransactionExecuteError,
1011
TransactionInitError,
1112
TransactionStatusError,
1213
)
1314

1415

16+
@pytest.mark.asyncio
17+
async def test_transaction_execute_raw(db, doc_col, docs):
18+
# Test a valid JS transaction
19+
doc = docs[0]
20+
key = doc["_key"]
21+
command = f"""
22+
function (params) {{
23+
var db = require('internal').db;
24+
db.{doc_col.name}.save({{'_key': params.key, 'val': 1}});
25+
return true;
26+
}}
27+
""" # noqa: E702 E231 E272 E202
28+
result = await db.execute_transaction(
29+
command=command,
30+
params={"key": key},
31+
write=[doc_col.name],
32+
read=[doc_col.name],
33+
exclusive=[doc_col.name],
34+
wait_for_sync=False,
35+
lock_timeout=1000,
36+
max_transaction_size=100000,
37+
allow_implicit=True,
38+
)
39+
assert result is True
40+
doc = await doc_col.get(key)
41+
assert doc is not None and doc["val"] == 1
42+
43+
# Test an invalid transaction
44+
with pytest.raises(TransactionExecuteError) as err:
45+
await db.execute_transaction(command="INVALID COMMAND")
46+
assert err.value.error_code == BAD_PARAMETER
47+
48+
1549
@pytest.mark.asyncio
1650
async def test_transaction_document_insert(db, bad_db, doc_col, docs):
1751
# Start a basic transaction

0 commit comments

Comments
 (0)