Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/arcadedb/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2465,8 +2465,10 @@ def open(self, read_only: bool = False) -> Database:
read_only: Open in ``ComponentFile.MODE.READ_ONLY``. Reads, queries
and scans work; every write and schema change raises
``ArcadeDBError``. ``begin()`` does not raise: the first write
does, and so does the commit. Use it to inspect a database that
must not change, such as a backup restored for audit.
does. From ArcadeDB 26.11.1 a read-only transaction commits or
rolls back without error; engines before that raise the
read-only error on ``commit()``. Use it to inspect a database
that must not change, such as a backup restored for audit.
"""
try:
if read_only:
Expand Down Expand Up @@ -2516,8 +2518,10 @@ def open_database(
path: Database path
jvm_kwargs: Optional JVM args passed to start_jvm()
Example: {"heap_size": "8g"}
read_only: Open the database read-only. Writes, schema changes and
transactions raise ``ArcadeDBError``.
read_only: Open the database read-only. Writes and schema changes raise
``ArcadeDBError``. ``begin()`` does not raise, and from ArcadeDB
26.11.1 a read-only transaction commits or rolls back without
error (writes inside it still raise).

Example:
>>> db = arcadedb.open_database("./mydb", read_only=True)
Expand Down
45 changes: 39 additions & 6 deletions tests/test_open_read_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
statement (see `test_sql_constraints_and_maintenance.py`).
"""

import re

import pytest

import arcadedb
Expand Down Expand Up @@ -43,21 +45,52 @@ def test_read_only_open_reads_and_refuses_writes(populated_path):
assert not db.schema.exists_type("Other")


def test_read_only_begin_is_not_refused_the_write_and_the_commit_are(populated_path):
"""begin() returns on a read-only database; the first write raises, and so does the commit
(the engine starts the transaction then). A write through Document.save() is an
ArcadeDBError like the rest of the API. Reads need no transaction."""
def _end_read_only_transaction(db, end):
"""Call `end` (db.commit or db.rollback) on a read-only transaction.

From 26.11.1 (ArcadeDB PR #11124, issue #11057) it returns and no transaction is
active afterwards. Engines before that refuse with the read-only error ("Cannot begin
a transaction on a read only database") and leave the transaction active, and a
rollback after the refused commit is refused the same way, so there is nothing to
clean up and no state to assert beyond the unchanged data. The 26.11.1-SNAPSHOT line
spans both behaviours, so a version check cannot tell them apart: accept either.
"""
try:
end()
except ArcadeDBError as e:
assert re.search(r"(?i)read.?only", str(e)), str(e)
return False
assert not db.is_transaction_active()
return True


def test_read_only_begin_reads_and_the_write_is_refused_the_commit_is_either(
populated_path,
):
"""begin() returns on a read-only database; a write through Document.save() raises an
ArcadeDBError like the rest of the API, and reads need no transaction. commit() of
that read-only transaction returns on engines from 26.11.1 and raises the read-only
error before (see `_end_read_only_transaction`)."""
with arcadedb.open_database(populated_path, read_only=True) as db:
db.begin() # not refused
assert db.count_type("Note") == 2
with pytest.raises(ArcadeDBError, match="(?i)read.?only"):
db.new_document("Note").set("n", 3).save()
with pytest.raises(ArcadeDBError, match="(?i)read.?only"):
db.commit()
_end_read_only_transaction(db, db.commit)
assert db.count_type("Note") == 2
assert db.query("sql", "SELECT n FROM Note").to_list()


def test_read_only_rollback_of_a_read_transaction(populated_path):
"""rollback() of a read-only transaction returns on engines from 26.11.1 and raises
the read-only error before; the data is unchanged either way."""
with arcadedb.open_database(populated_path, read_only=True) as db:
db.begin()
assert db.count_type("Note") == 2
_end_read_only_transaction(db, db.rollback)
assert db.count_type("Note") == 2


def test_factory_open_read_only_and_default_is_writable(populated_path):
factory = arcadedb.DatabaseFactory(populated_path)

Expand Down
Loading