Oracle: create <graph>_nulls_equal with the graph, so a statement that writes or a reduce compares lists holding a null item by item (port of drivine4j#270)
Los mantenedores suelen responder en 1 día
Ya se ha fusionado un pull request relacionado.
- #203 de @jasperblues — fusionado
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 35/100
Línea de trabajo
Start with drivine/backend/oracle/oracle_graph.py:57 and drivine/cypher/sql/oracle_cypher_compiler.py:502-510, then review the listed drivine4j#323 contract tests and spec cases. The work spans Oracle function creation, compiler behavior, permission fallback, and tests. Done means the stored function and fallback match the described behavior, with the listed Oracle tests and spec updates covered.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Port of liberation-data/drivine4j#270.
What happens here now: on Oracle, a Cypher statement that writes compiles to a PL/SQL block, and so does the step of a reduce. Neither can call the cy_nulls_equal function a query declares with WITH FUNCTION (PLS-00231). So two lists or maps that hold a null and differ as JSON compare as false there, where Cypher may give null: CREATE (n:CyWrite) WITH n, [null] AS a, [1] AS b SET n.eq = coalesce(a = b, 'null') RETURN n.eq gives false where Neo4j gives "null". See drivine/cypher/sql/oracle_cypher_compiler.py:502-510 (nulls_equal, if self.procedural or self._folding).
What drivine4j does now (liberation-data/drivine4j#323):
- Store layout (shared contract). The Oracle graph is made with a function,
<graph>_nulls_equal, created where it is missing or invalid, so a store made by an earlier version gains it. It is not recorded in_DrivineSchema, as the Postgres<graph>_find/_owns/_writefunctions aren't. The ensure block (here drivine/backend/oracle/oracle_graph.py:57 (ensuring)) gains, after the existingIF v_made = 0 THEN … END IF;:
e_denied EXCEPTION;
PRAGMA EXCEPTION_INIT(e_denied, -1031);
...
SELECT COUNT(*) INTO v_made FROM user_objects WHERE object_type = 'FUNCTION' AND object_name = '<GRAPH>_NULLS_EQUAL' AND status = 'VALID';
IF v_made = 0 THEN
BEGIN EXECUTE IMMEDIATE '<the DDL below, quoted>'; EXCEPTION WHEN e_denied THEN NULL; END;
END IF;
The DDL must match byte for byte (lines joined with \n, no trailing newline), with <graph> the graph's name. It is CREATE OR REPLACE followed by the same text the query declares as cy_nulls_equal, renamed:
CREATE OR REPLACE FUNCTION <graph>_nulls_equal(cy_x JSON, cy_y JSON) RETURN NUMBER IS
FUNCTION eq(a JSON_ELEMENT_T, b JSON_ELEMENT_T) RETURN NUMBER IS
r NUMBER := 1;
e NUMBER;
la JSON_ARRAY_T;
lb JSON_ARRAY_T;
oa JSON_OBJECT_T;
ob JSON_OBJECT_T;
ks JSON_KEY_LIST;
too_long EXCEPTION;
PRAGMA EXCEPTION_INIT(too_long, -40478);
BEGIN
IF a.is_Null OR b.is_Null THEN RETURN NULL; END IF;
IF a.is_Array AND b.is_Array THEN
la := TREAT(a AS JSON_ARRAY_T);
lb := TREAT(b AS JSON_ARRAY_T);
IF la.get_Size <> lb.get_Size THEN RETURN 0; END IF;
FOR i IN 0 .. la.get_Size - 1 LOOP
e := eq(la.get(i), lb.get(i));
IF e = 0 THEN RETURN 0; END IF;
IF e IS NULL THEN r := NULL; END IF;
END LOOP;
RETURN r;
END IF;
IF a.is_Object AND b.is_Object THEN
oa := TREAT(a AS JSON_OBJECT_T);
ob := TREAT(b AS JSON_OBJECT_T);
IF oa.get_Size <> ob.get_Size THEN RETURN 0; END IF;
ks := oa.get_Keys;
FOR i IN 1 .. ks.COUNT LOOP
IF NOT ob.has(ks(i)) THEN RETURN 0; END IF;
e := eq(oa.get(ks(i)), ob.get(ks(i)));
IF e = 0 THEN RETURN 0; END IF;
IF e IS NULL THEN r := NULL; END IF;
END LOOP;
RETURN r;
END IF;
IF a.is_Number AND b.is_Number THEN RETURN CASE WHEN a.to_Number = b.to_Number THEN 1 ELSE 0 END; END IF;
IF a.is_String AND b.is_String THEN
BEGIN
RETURN CASE WHEN a.to_String = b.to_String THEN 1 ELSE 0 END;
EXCEPTION WHEN too_long THEN
RETURN CASE WHEN DBMS_LOB.COMPARE(a.to_Clob, b.to_Clob) = 0 THEN 1 ELSE 0 END;
END;
END IF;
IF a.is_Boolean AND b.is_Boolean THEN RETURN CASE WHEN a.to_Boolean = b.to_Boolean THEN 1 ELSE 0 END; END IF;
RETURN 0;
END;
BEGIN
RETURN eq(JSON_ELEMENT_T.parse(JSON_SERIALIZE(cy_x RETURNING CLOB)), JSON_ELEMENT_T.parse(JSON_SERIALIZE(cy_y RETURNING CLOB)));
END;
- Compiler.
nullsEqualalways walks with the store's function, in reads too: it is as fast as the declared one, and the query needs noWITH FUNCTIONblock. Before the walk there is a fast path: two values with the same short JSON text are null, since one holds a null, so they need no walk:
CASE WHEN JSON_SERIALIZE(x RETURNING VARCHAR2(4000) NULL ON ERROR) = JSON_SERIALIZE(y RETURNING VARCHAR2(4000) NULL ON ERROR) THEN NULL ELSE (<graph>_nulls_equal(x, y) = 1) END
(JSON = and JSON_EQUAL can't be the fast path: they fail past about 32 KB, ORA-40180.) The compiler takes a flag, nullsEqualStored (default true), and reports whether the compiled statement calls the stored function.
- Fallback. Making the function takes
CREATE PROCEDURE, which theRESOURCErole grants. Without it, ensure still makes the graph (ORA-01031 is swallowed for the function only). When a compiled statement calls the function, the connection that compiles Cypher for Oracle first checks that the session can call it, withSELECT <graph>_nulls_equal(JSON('[1]'), JSON('[1]')) FROM DUAL. A yes is remembered per database, user and graph; a no is asked again next time. If the session can't call it, the statement is compiled again withnullsEqualStored = false, which is today's behaviour (a read declarescy_nulls_equal; a write or reduce step usesCASE WHEN <equals> THEN NULL ELSE FALSE END), and one warning is logged per graph.
Tests to port (drivine4j#323):
CypherWriteContractlists and maps that hold a null and differ as JSON compare item by item in a statement that writes, and the Oracle-onlya store without the function … until the graph is ensured again.CypherQueryContractthe step of a reduce compares lists and maps that hold a null and differ as JSON item by item. The write case[null] = [1]no longer skips Oracle.CompiledSizeTestvalues that may hold a null are walked on Oracle by the store's function ….OracleFreshGraphTestthe graph is made with the function ….- Spec v0.6.14 (once tagged): writes.yaml drops the step's
except: [oracle]and adds steps; queries.yaml adds "Values that hold a null, compared in the step of a reduce".
- Lenguaje dominante
- Python
- Estrellas
- 1
- Forks
- 0
- Merge medio
- 1 h 13 min
- PR fusionados (30 d)
- 109
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de liberation-data/drivine-python
-
bug
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
liberation-data/drivine-python#209 ·
Los mantenedores suelen responder en 1 día
-
CI: three Oracle-only tests in test_cypher_compiled.py never run, as -k oracle doesn't select themAbierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
liberation-data/drivine-python#178 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
liberation-data/drivine-python#219 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 57/100
liberation-data/drivine-python#218 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 52/100
liberation-data/drivine-python#215 ·
Los mantenedores suelen responder en 1 día
Todos los issues de liberation-data/drivine-python
Issues similares
-
component:benchmark
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
agentic-os-org/ANOLISA#9177 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
distrochooser/distrochooser#520 ·
Los mantenedores suelen responder en 2 días
-
Status: Needs confirmation Status: Needs triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 64/100
Los mantenedores suelen responder en 1 día
-
[BUG] Published Docker images lack the redis extra: HEADROOM_COMPRESS_SESSION_STORE stops startupPosiblemente ocupada @AlessandroSpallina la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
headroomlabs-ai/headroom#4513 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 65/100
codespell-project/codespell#4060 ·
Los mantenedores suelen responder en 1 día