Skip to content

Turn Oracle's JSON table transformation off in a query of two tables or more, so a node keeps its label test - #275

Merged
jasperblues merged 2 commits into
mainfrom
oracle-json-table-transform
Oct 29, 2026
Merged

jasperblues merged 2 commits into
mainfrom
oracle-json-table-transform

Conversation

@jasperblues

Copy link
Copy Markdown
Member

Fixes #258. Port of liberation-data/drivine4j#432.

Cause

Oracle's JSON table transformation (FORCE_JSON_TABLE_TRANSFORM) rewrites several JSON reads of one node's props into one JSON_TABLE view. Beside another node's table, the view drops that node's label and property tests. On Oracle 23 (gvenzl/oracle-free:23-faststart), over N1, N2 and N3 seeded, MATCH (a:N1), (b:N2) RETURN a.n + b.n AS sum, a.n - b.n AS diff gave 9 rows here before this change ([4, 0], [4, -1], [4, -2], [5, 1], …), where Neo4j and Postgres give [[3, -1]].

Fix

  • A new planned(query) hook in SqlCypherCompiler, which does nothing (Postgres).
  • In OracleCypherCompiler, a query that names the graph's node or edge table twice or more (\b(?:<graph>_node|<graph>_edge) \w, with the graph's name escaped) opens with /*+ NO_JSON_TABLE_TRANSFORM */. If the query already opens with a hint comment (/*+ NO_MERGE */), the hint joins it.
  • The hook is applied to the compiled query, to a UNION, and to each query of a write's procedure: SELECT … INTO (_select_into), FOR … IN loops (_loop, and MERGE's match), and the rows it returns.
  • A bullet in the Oracle notes of docs/04-statements.md.

SQL

Every statement of the Cypher spec (v0.6.19, 1,030 statements) was compiled before and after the change.

  • Postgres: byte-identical for all of them.
  • Oracle: 248 change: 177 in queries, 70 in writes and 1 in temporal. The hint is the only change in each. It's added once in 235 statements, and twice or three times in 13 procedures. These are the same counts as drivine4j.

Tests

  • test_properties_of_a_node_beside_another_nodes_table_give_the_rows_of_the_pattern_and_that_nodes_values (async and sync): drivine4j's 13 statements, plus review pass 1's two (an unlabelled neighbour with filters, and two nodes of one label with a relationship property), on Neo4j, Postgres and Oracle. On Oracle it failed before the fix and passes after.
  • tests/unit/test_oracle_json_table_transform.py: where the hint goes and where it doesn't (a query of two tables, one table, a UNION, a declared function, a write's SELECT … INTO, a MERGE loop, the graph name escaped, a hint comment joined, Postgres unchanged).

Gates: check_cypher_spec.py, ruff check and format, pyright, unasync.py --check, and ORACLE_TESTS=true uv run pytest -q (5167 passed, 298 skipped).

…or more, so a node keeps its label test

Port of liberation-data/drivine4j#432. Oracle's JSON table transformation rewrites
several JSON reads of one node's props into one JSON_TABLE view, and beside another
node's table that view lost the node's label and property tests:
MATCH (a:N1), (b:N2) RETURN a.n + b.n, a.n - b.n gave 9 rows on Oracle, one for each
two nodes, where Neo4j and Postgres give 1.

A compiled query, a UNION, and each query of a write's procedure (SELECT ... INTO,
FOR ... IN loops, the rows it returns) go through a new planned() hook. It does
nothing on Postgres. On Oracle, a query that names the graph's node or edge table twice
or more opens with /*+ NO_JSON_TABLE_TRANSFORM */, joined to a hint comment it
already opens with.

Spec v0.6.19: Postgres SQL is byte-identical for all 1,030 statements. On Oracle
248 change (177 queries, 70 writes, 1 temporal), each only by the hint, as in drivine4j.

Fixes #258
@jasperblues
jasperblues merged commit ac0c979 into main Oct 29, 2026
7 checks passed
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.

Cypher on Oracle: a.n + b.n over two matched nodes gives rows the MATCH doesn't have

1 participant