Skip to content

Compile randomUUID() to Oracle's UUID(), a random v4, and need Oracle 23.9 - #273

Merged
jasperblues merged 2 commits into
mainfrom
oracle-uuid
Oct 29, 2026
Merged

jasperblues merged 2 commits into
mainfrom
oracle-uuid

Conversation

@jasperblues

Copy link
Copy Markdown
Member

Fixes #252

Port of liberation-data/drivine4j#401 (PR liberation-data/drivine4j#402).

On Oracle, compiled Cypher's randomUUID() was SYS_GUID() written as a UUID. That value is version 0, and consecutive values share all but a counter's digits. The stamp token that stamps.set_clause and stamps.links_clause make, left(replace(randomUUID(), '-', ''), 16), therefore changed in only about 16 bits, and in order.

The change. OracleCypherCompiler.uuid() is now Oracle's own UUID(), a random version 4 UUID:

LOWER(REGEXP_REPLACE(RAWTOHEX(UUID()), '(.{8})(.{4})(.{4})(.{4})(.{12})', '\1-\2-\3-\4-\5'))

It is 92 characters, under SHARED_ABOVE (200). So it is written where it is read and not bound once for the statement, and each row gets a UUID of its own. The stamp clauses compile to it unchanged in length, so they aren't hoisted either.

Drivine now needs Oracle 23.9 or later, because UUID() arrived in 23.9. OracleConnectionProvider._check_version reads connection.version (python-oracledb gives "23.26.3.0.0"), which needs no grant. It runs in _ensure, on the first connection, once per pool: GraphTables runs it once, and again after end(). On an older Oracle it logs a warning, hands connections out, and plain SQL runs. OracleConnection._compiled, which serves both query and open_cursor, refuses a Cypher statement with:

Drivine needs Oracle 23.9 or later (it uses UUID()); this database is 23.8.0.25.4. Upgrade, or use an Oracle Free image 23.9 or newer. Plain SQL over this provider still runs; Cypher does not.

A version that can't be read refuses nothing. The object manager writes its own SQL, so it is not refused. The sync copy is written by scripts/unasync.py. The README, the introduction, docs/03-cypher-on-a-relational-database.md (with the reason) and docs/design.md now say 23.9 or later.

Tests

  • test_cypher_compiled.py, on Neo4j, Postgres and Oracle:

    • 50 randomUUID() values are distinct lower-case version 4 UUIDs, and at most 2 consecutive pairs share more than 4 leading characters;
    • randomUUID() + '' and 'id-' + randomUUID() over UNWIND range(1, 100) give 100 distinct values;
    • set_clause and then links_clause over 1,000 nodes give 1,000 distinct stamps and 2,000 distinct tokens. Every hex position except the version digit takes at least 12 values, and 20 saves of one node in a row seldom share a long prefix.

    The sync copies run the same tests.

  • tests/unit/test_oracle_version.py:

    • oracle_refusal: 23.8 and 19.3 are refused with the message; 23.9, 23.26 and 26.1 pass; None, "" and an unparseable version refuse nothing.
    • Through the provider with a fake pool: plain SQL runs, Cypher is refused, and the version is read once over two connections. Cypher runs on 23.9 and 23.26, and where reading the version raises.
    • The sync provider refuses in the same way.
  • test_oracle_connection.py: on the test image, the version passes and RETURN randomUUID() gives a version 4 UUID.

Compiled SQL. Every statement of the four spec files at v0.6.19, plus the two stamp clauses (1,033 statements), was compiled with the Oracle and Postgres compilers on main and on this branch.

  • Postgres: all byte-identical.
  • Oracle: only the 3 spec statements that call randomUUID() differ (Functions of numbers, steps 6 and 7; A random value bound by a WITH in a write, step 1), plus the two stamp clauses. With SYS_GUID() replaced by UUID(), the output is byte-identical.

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

… 23.9

On Oracle, compiled Cypher's randomUUID() was SYS_GUID() written as a UUID:
version 0, consecutive values sharing all but a counter's digits, so the
token stamps.set_clause and links_clause make changed in about 16 bits, in
order. It is now Oracle's UUID(), a random version 4 UUID, 92 characters,
so it is written where it is read and each row gets its own.

UUID() arrived in Oracle 23.9. The provider reads connection.version on
its first connection, once per pool, with no grant. On an older Oracle
plain SQL still runs, and a Cypher statement is refused with the version
found. A version that cannot be read refuses nothing. Port of
liberation-data/drivine4j#401 (PR #402).

Fixes #252
@jasperblues
jasperblues merged commit ecba8f9 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

1 participant