Skip to content

fix(arcadedb): clone() must switch database, or a tenant switch reads the wrong tenant - #4

Open
g33kroid wants to merge 1 commit into
ArcadeData:feat/arcadedb-backendfrom
g33kroid:fix/arcadedb-clone-database
Open

fix(arcadedb): clone() must switch database, or a tenant switch reads the wrong tenant#4
g33kroid wants to merge 1 commit into
ArcadeData:feat/arcadedb-backendfrom
g33kroid:fix/arcadedb-clone-database

Conversation

@g33kroid

@g33kroid g33kroid commented Sep 8, 2026

Copy link
Copy Markdown

GraphDriver.clone() returns self. Every other driver overrides it; ArcadeDBDriver does not, so clone() is a no-op here.

That default is wrong on ArcadeDB specifically. On this backend a database is the tenant boundary — which is a large part of why it is attractive next to Neo4j CE, where tenancy falls back to a group_id filter over one shared store. graphiti calls clone(group_id) to switch tenant, so with clone() returning self, every subsequent query stays on the previous tenant's database.

Nothing raises. The query is valid and the database simply does not hold that tenant's rows, so the caller sees an empty tenant rather than an error — and any code that reads "no results" as "nothing there yet" will write a second copy into the wrong database.

How it showed up

Running graphiti's search suite across ArcadeDB, Neo4j and FalkorDB on ArcadeDB 26.9.1 (sha256:02a1a74f…), driver at 92ab811. Same data, same checks, three engines:

check                                          arcadedb   neo4j
clone(database) per-tenant switch              FAIL       PASS      <- 0 hits
PHYSICAL per-tenant store (not just a filter)  PASS       FAIL

0 hits where the tenant plainly has matching rows. With this fix, the same suite is 15/15 on ArcadeDB.

The fix

clone() returns a driver bound to the requested database. The Bolt client is shared with the original: it is not bound to a database — session() and execute_query() both take database_ per call — so sharing it matches the other drivers, and closing one clone closes them all, as it does elsewhere.

Tests

Two regression tests in the existing tests/driver/test_arcadedb_driver.py style, no server needed:

  • test_clone_switches_databaseclone() returns a distinct driver on the requested database, leaves the original alone, shares the client
  • test_clone_session_targets_cloned_database — a cloned driver's session() opens on the cloned database

Both fail on the current head (assert cloned is not self.driver) and pass with the fix. Full file: 25 passed, 1 skipped.

Unrelated, but found in the same run

Similarity search is still broken on this branch's head — the group/uuid filter emits WHERE …, then the embedding guard is concatenated as a second WHERE, so node/edge/community similarity all raise CypherSyntaxError: Unexpected input 'WHERE' whenever any filter is present, which in a multi-tenant deployment is always. #2 already fixes exactly this and has been open since July, so I have deliberately not duplicated it here — it may just need merging.

🤖 Generated with Claude Code

… the wrong tenant

GraphDriver.clone() returns self. Every other driver overrides it; the
ArcadeDB driver did not, so clone() was a no-op here.

That default is wrong on ArcadeDB specifically, because a database is the
tenant boundary on this backend rather than a filter over one shared store:
per-tenant databases are the reason to pick it. graphiti calls
clone(group_id) to switch tenant, so with clone() returning self every
subsequent query stayed on the previous tenant's database. Nothing raises —
the query is valid, the database simply does not hold that tenant's rows —
so the caller sees an empty tenant instead of an error, and any code that
treats "no results" as "nothing to do" writes a second copy into the wrong
database.

Found running graphiti's search suite across ArcadeDB, Neo4j and FalkorDB on
ArcadeDB 26.9.1: the per-tenant switch returned 0 rows on ArcadeDB and the
expected rows on the other two.

The Bolt client is shared with the original driver. It is not bound to a
database — session() and execute_query() both take database_ per call — so
sharing it matches the other drivers, and closing one clone closes them all
as it does elsewhere.

Two regression tests: clone() returns a distinct driver on the requested
database without disturbing the original, and a cloned driver's session()
opens on the cloned database.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant