From 65d1ae55a6b325ca3e1e9f8d25fa252a6e34427d Mon Sep 17 00:00:00 2001 From: Eric Laquer Date: Sun, 25 Oct 2026 14:31:54 -0400 Subject: [PATCH] Fix MERGE edges on 32-bit builds: keep path_entry ids as graphids path_entry's id, start_id and end_id are graphid fields, but merge_vertex and merge_edge stored Datums in them and read them back as Datums, while prebuild_path stores real graphids for vertices bound by an earlier clause. On a 64-bit build the two are the same bits. With SIZEOF_DATUM=4 (the 32-bit support added in #2286, used by PGlite's wasm32 build) a graphid Datum is a pointer, so a MERGE of an edge onto bound vertices -- for example MERGE (a:N {k:'x'}) MERGE (b:N {k:'y'}) MERGE (a)-[:E]->(b) MATCH (a:N {k:'x'}), (b:N {k:'y'}) MERGE (a)-[:E]->(b) wrote the edge with start_id and end_id read from whatever the graphid, taken as an address, pointed to (0 in practice): the row is stored but no MATCH walks it, and a second MERGE of the same edge stores a duplicate. Convert with GRAPHID_GET_DATUM / DATUM_GET_GRAPHID where path_entry meets a Datum, guarding a NULL id. On 64-bit builds these are identity casts, so behaviour there is unchanged. Found running the same Cypher workload on PostgreSQL 17 (64-bit) and in PGlite 0.4.6 (wasm32): 606 of 617 statements agreed before, 617 after. --- src/backend/executor/cypher_merge.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index cbd866444..c1c4b7650 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -34,6 +34,13 @@ * * Note: This structure is only useful for paths when stored in a dynamic * array. + * + * Note: id, start_id and end_id hold graphid values, never Datums. They are + * converted with GRAPHID_GET_DATUM/DATUM_GET_GRAPHID where they meet a + * Datum. On a 64-bit build the two are the same bits, but with + * SIZEOF_DATUM=4 a graphid Datum is a pointer, and storing it here (or + * reading a graphid stored here as a Datum) writes edges with + * start_id/end_id from whatever that address holds. */ typedef struct path_entry { @@ -1270,8 +1277,9 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, path_array != NULL && path_array[path_index] != NULL) { - id = path_array[path_index]->id; isNull = path_array[path_index]->id_isNull; + id = isNull ? (Datum) 0 : + GRAPHID_GET_DATUM(path_array[path_index]->id); } /* * Otherwise, we need to retrieve the vertex normally and store its @@ -1285,7 +1293,8 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, if (path_array != NULL && path_array[path_index] != NULL) { /* store it */ - path_array[path_index]->id = id; + path_array[path_index]->id = isNull ? 0 : + DATUM_GET_GRAPHID(id); path_array[path_index]->id_isNull = isNull; } } @@ -1422,7 +1431,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, else if (path_array != NULL && path_array[path_index] != NULL) { /* retrieve the id of the vertex */ - id = path_array[path_index]->id; + id = GRAPHID_GET_DATUM(path_array[path_index]->id); /* * Add the Datum to the list of entities for creating the path variable @@ -1606,10 +1615,11 @@ static void merge_edge(cypher_merge_custom_scan_state *css, path_array != NULL && path_array[path_index] != NULL) { - id = path_array[path_index]->id; isNull = path_array[path_index]->id_isNull; - start_id = path_array[path_index]->start_id; - end_id = path_array[path_index]->end_id; + id = isNull ? (Datum) 0 : + GRAPHID_GET_DATUM(path_array[path_index]->id); + start_id = GRAPHID_GET_DATUM(path_array[path_index]->start_id); + end_id = GRAPHID_GET_DATUM(path_array[path_index]->end_id); } /* * Otherwise, we need to get the edge's ID and store its unique values if @@ -1623,10 +1633,10 @@ static void merge_edge(cypher_merge_custom_scan_state *css, if (path_array != NULL && path_array[path_index] != NULL) { /* store it */ - path_array[path_index]->id = id; + path_array[path_index]->id = isNull ? 0 : DATUM_GET_GRAPHID(id); path_array[path_index]->id_isNull = isNull; - path_array[path_index]->start_id = start_id; - path_array[path_index]->end_id = end_id; + path_array[path_index]->start_id = DATUM_GET_GRAPHID(start_id); + path_array[path_index]->end_id = DATUM_GET_GRAPHID(end_id); } } else