Severity: high. No relationship ever replicates.
Environment
- PostgreSQL 18.4 (Homebrew), macOS arm64
pg_ladybug main @ e5182153f1d4b588e7fd758d69c1406a6649a79f
liblbug 0.19.1
Summary
The Cypher generated for an edge INSERT has two major issues:
- The
id property is emitted twice.
- The endpoint columns (
from_col / to_col) are copied into the
relationship's property map, where no such properties exist.
The same endpoint leak affects the SET list produced for UPDATE. Every
relationship statement therefore fails at replay with
Binder exception: Cannot find property src_id for r.
Minimal reproduction
CREATE DATABASE bugrepro;
\c bugrepro
CREATE EXTENSION pg_ladybug;
CREATE TABLE node_city (
id bigint PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE rel_route (
id bigint PRIMARY KEY,
src_id bigint NOT NULL REFERENCES node_city(id),
dst_id bigint NOT NULL REFERENCES node_city(id),
distance double precision NOT NULL
);
SELECT ladybug.register_node('City', 'node_city', 'id', NULL, 'demo');
SELECT ladybug.register_edge('Route', 'rel_route', 'src_id', 'dst_id', 'id', 'demo');
SELECT ladybug.enable_replication('demo', 'INSERT,UPDATE,DELETE');
INSERT INTO node_city VALUES (1, 'Toronto'), (2, 'Vancouver');
INSERT INTO rel_route VALUES (1, 1, 2, 3358.0);
UPDATE rel_route SET distance = 3400.0 WHERE id = 1;
SELECT operation, cypher FROM ladybug.replication_log('demo') ORDER BY id;
Actual output
operation | cypher
-----------+---------------------------------------------------------------------------------------------
INSERT | CREATE (n:City {id: 1, name: 'Toronto'})
INSERT | CREATE (n:City {id: 2, name: 'Vancouver'})
INSERT | MATCH (a {id: 1}), (b {id: 2}) CREATE (a)-[r:Route {id: 1, id: 1, src_id: 1, dst_id: 2, distance: 3358}]->(b)
UPDATE | MATCH (a {id: 1}), (b {id: 2}) MERGE (a)-[r:Route {id: 1}]->(b) SET r.src_id = 1, r.dst_id = 2, r.distance = 3400
Note {id: 1, id: 1, src_id: 1, dst_id: 2, ...} on the INSERT and
SET r.src_id = ..., r.dst_id = ... on the UPDATE.
Expected
MATCH (a {id: 1}), (b {id: 2}) CREATE (a)-[r:Route {id: 1, distance: 3358}]->(b)
MATCH (a {id: 1}), (b {id: 2}) MERGE (a)-[r:Route {id: 1}]->(b) SET r.distance = 3400
Downstream failure
Against a native schema:
SELECT * FROM ladybug.cypher(
'CREATE NODE TABLE City(id INT64, name STRING, PRIMARY KEY(id))') AS t(ok text);
SELECT * FROM ladybug.cypher(
'CREATE REL TABLE Route(FROM City TO City, id INT64, distance DOUBLE)') AS t(ok text);
SELECT ladybug.replay_replication('demo');
NOTICE: ladybug: replay skipped: MATCH (a {id: 1}), (b {id: 2}) CREATE (a)-[r:Route {id: 1, id: 1, src_id: 1, dst_id: 2, distance: 3358}]->(b)
DETAIL: ladybug: query failed: Binder exception: Cannot find property src_id for r.
In a larger run, all 830 relationships failed this way while the originating
Postgres writes all succeeded, so Postgres and the ladybug graph silently diverge.
Source
pg_ladybug.c:987-1012. The edge INSERT path writes id explicitly and then
calls append_all_props(), which appends every column including id again and
both endpoint columns. The UPDATE path's append_set_items() skips only the ID
column.
Suggested fix
Give the property appenders an exclusion set. For edges, exclude id_column,
from_col, and to_col, then add id_column exactly once. Cover INSERT,
UPDATE, and DELETE with replay tests against a native relationship schema.
Severity: high. No relationship ever replicates.
Environment
pg_ladybugmain @e5182153f1d4b588e7fd758d69c1406a6649a79fliblbug0.19.1Summary
The Cypher generated for an edge INSERT has two major issues:
idproperty is emitted twice.from_col/to_col) are copied into therelationship's property map, where no such properties exist.
The same endpoint leak affects the
SETlist produced for UPDATE. Everyrelationship statement therefore fails at replay with
Binder exception: Cannot find property src_id for r.Minimal reproduction
Actual output
Note
{id: 1, id: 1, src_id: 1, dst_id: 2, ...}on the INSERT andSET r.src_id = ..., r.dst_id = ...on the UPDATE.Expected
Downstream failure
Against a native schema:
In a larger run, all 830 relationships failed this way while the originating
Postgres writes all succeeded, so Postgres and the ladybug graph silently diverge.
Source
pg_ladybug.c:987-1012. The edge INSERT path writesidexplicitly and thencalls
append_all_props(), which appends every column includingidagain andboth endpoint columns. The UPDATE path's
append_set_items()skips only the IDcolumn.
Suggested fix
Give the property appenders an exclusion set. For edges, exclude
id_column,from_col, andto_col, then addid_columnexactly once. Cover INSERT,UPDATE, and DELETE with replay tests against a native relationship schema.