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
16 changes: 14 additions & 2 deletions src/arcadedb/_native/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
_FIRST_BATCH = 256
_EPOCH_DAY_ORDINAL = date(1970, 1, 1).toordinal()
_NEXT_BATCH = 2048
_EAGER = 8 # a flag of _exec that never reaches the library: the statement's first rows come back in its own reply
_wrap_record = None
_KIND = {"v": "v", "e": "e", "d": "d"}

Expand Down Expand Up @@ -608,12 +609,14 @@ def _exec(
self, flags: int, prefix: str, language: str, command: str, args
) -> NativeResultSet:
self._check_not_closed()
eager = flags & _EAGER
flags &= ~_EAGER
if args:
refuse_surplus_positionals(
"command" if flags & 1 else "query", language, command, args
)
interrupts.clear()
if flags & 1 and _LAZY_COMMAND_ROWS and lib.CMD_PATH:
if flags & 1 and _LAZY_COMMAND_ROWS and lib.CMD_PATH and not eager:
# phase 8: the cursor id comes back as a long (no reply text to free and decode); the rows are read late or never
text, buf, count = encode_params(args, binary=True)
rid = lib.cmd(
Expand All @@ -630,7 +633,7 @@ def _exec(
raise _error(prefix, json.loads(lib.last_error()))
return NativeResultSet._lazy(self, rid)
# phase 7 (arcadedb_n_exec flags): 2 = a command's rows are pulled but encoded only when read, 4 = float arrays come back as a float32 tail
if flags & 1 and _LAZY_COMMAND_ROWS:
if flags & 1 and _LAZY_COMMAND_ROWS and not eager:
flags |= 2
if _BINARY_REPLY:
flags |= 4
Expand Down Expand Up @@ -680,6 +683,15 @@ def command(self, language: str, command: str, *args) -> NativeResultSet:
"""Run a statement that may write."""
return self._exec(1, "Command failed", language, command, args)

def _command_first_row(self, language: str, command: str, *args) -> NativeResultSet:
""":meth:`command` for a caller that reads the first row right away (a create or an update that returns its row): the statement runs
and its first rows come back in the same crossing (``arcadedb_n_exec`` without the lazy bit) instead of a cursor id that a second
crossing (``arcadedb_n_next_bin``) reads. The rows, the values and the errors are those of :meth:`command`, at the same moment:
an error the engine raises while it runs the statement (an INSERT or an UPDATE) is raised by this call, as by :meth:`command`;
one met while rows are computed lazily comes in the reply (``rerr``) and is raised when the row is read, as on the lazy path.
"""
return self._exec(1 | _EAGER, "Command failed", language, command, args)

# -- transactions

def _tx_text(self, op: int, prefix: str) -> str:
Expand Down
8 changes: 4 additions & 4 deletions src/arcadedb/_native/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def save(self):
from .extras import veto_count

vetoes = veto_count()
row = db.command(
row = db._command_first_row(
"sql", self._CREATE.format(t=_q(self._type)), {"row": self._props}
).first()
if row is None and veto_count() != vetoes:
Expand Down Expand Up @@ -388,11 +388,11 @@ def _save_update(self, db) -> None:
vetoes = veto_count()
params = {"rid": RID(self._rid), "row": self._props}
if exact:
row = db.command("sql", self._UPDATE_SENT, params).first()
row = db._command_first_row("sql", self._UPDATE_SENT, params).first()
if row is not None and row.get("count") and veto_count() == vetoes:
self._base = sent
else:
after = db.command("sql", self._UPDATE, params).first()
after = db._command_first_row("sql", self._UPDATE, params).first()
if (
after is not None
): # a veto or a record that is gone gives no row: nothing was written, the copy stays
Expand Down Expand Up @@ -450,7 +450,7 @@ def new_edge(self, label: str, target: "Vertex", **kwargs) -> "Edge":
if kwargs:
sql += " CONTENT :row"
params["row"] = kwargs
row = self._db.command("sql", sql, params).first()
row = self._db._command_first_row("sql", sql, params).first()
edge = Edge(self._db, label, mutable=False)
edge._adopt(row)
return edge
Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
collect_ignore.append("test_native_async_callbacks.py")
# the short call path of a point read (codec.encode_params, lib.exec_query, first()), observed on the native library
collect_ignore.append("test_native_point_read.py")
# the crossings of save() and new_edge() (lib.cmd, lib.call, lib.call_bin, lib.exec_query are the native library's ctypes doors)
collect_ignore.append("test_native_one_row_crossings.py")
# an RID built from an RID keeps the text, so the fast named-parameter path of codec.encode_params can encode it (#265)
collect_ignore.append("test_native_rid_params.py")
# to_json_list walks the rows with codec.json_native only when a reply restored a tagged value (#219), observed on the native library
Expand Down
Loading
Loading