gregfelice opened a new pull request, #2495:
URL: https://github.com/apache/age/pull/2495
Fixes #2493. Also fixes the visibility half of #2491, and the underlying
defect behind #2490.
## Problem
In a multi-part query, a clause that reads sees only the rows written by the
**first input row** of a preceding `CREATE` or `SET`. Everything written by the
remaining input rows is invisible for the rest of the statement.
```sql
SELECT * FROM cypher('g', $cypher$
UNWIND [1, 2, 3] AS i
CREATE (:v {id: i})
WITH count(*) AS ignored
MATCH (n:v)
RETURN count(n)
$cypher$) AS (visible agtype);
```
Returns `1`. All three vertices are persisted.
The visible unit is the command id, which covers one input row — so the
symptom depends on how the write is driven, not how much it writes:
| Setup | Written | Visible to the later `MATCH` | Persisted |
|---|---|---|---|
| `UNWIND range(1,2) AS i CREATE (:N {id:i})` | 2 | **1** | 2 |
| `UNWIND range(1,8) AS i CREATE (:N {id:i})` | 8 | **1** | 8 |
| 5 pre-existing, then create 3 in-statement | 3 | **6** (5 + 1) | 8 |
| `CREATE (:N),(:N),(:N)` — one input row | 3 | 3 ✅ | 3 |
`SET` behaves the same way: `MATCH (n:x) SET n.marked = true WITH count(*)
AS ig MATCH (m:x) WHERE m.marked = true RETURN count(m)` sees 1 of 3, though
all 3 updates persist.
## Root cause
Entities are written with the **global** command id — `insert_entity_tuple`
for `CREATE`, the `cid` in `update_entity_tuple` for `SET` — and
`CommandCounterIncrement()` advances that id once per input row.
The executor's snapshot does not follow it. `CommandCounterIncrement()`
updates the current and secondary snapshots, not the pushed one `es_snapshot`
points at, and `Increment_Estate_CommandId` bumps `curcid` only once, when the
clause begins. So `curcid` sits one step past the command id used by the first
input row, and only that row's tuples satisfy `cmin < curcid`.
This is already documented in-tree at
`src/backend/executor/cypher_utils.c:248-260`, where `entity_exists()` works
around it locally with `Max(saved_curcid, GetCurrentCommandId(false))`. Nothing
applied the same correction to ordinary `MATCH` scans.
## Fix
When a `CREATE` or `SET` clause reaches the end of its input, raise
`es_snapshot->curcid` to the global command id:
```c
estate->es_snapshot->curcid = Max(estate->es_snapshot->curcid,
GetCurrentCommandId(false));
```
Doing this at end of input rather than after each row is what preserves the
existing protection against a clause seeing its own writes: by that point the
subtree is exhausted, so raising `curcid` cannot feed a written row back into
the pattern that wrote it. `Max()` because `Increment_Estate_CommandId` can
push `curcid` above the global command id, and lowering it would hide tuples
that are already visible.
`REMOVE` is covered by the `SET` path it shares. `DELETE` already
synchronizes `curcid` explicitly (`cypher_delete.c:356`, `:482`) and was
unaffected; `MERGE` behaved correctly in the same probes.
## Testing
Full suite green — **42/42**, PostgreSQL 18.4.
Behaviour changes, all verified:
| Case | Before | After |
|---|---|---|
| #2493 as filed | `rows=1, bound=1` | **`3, 3`** |
| #2490 driven by two input rows | 2 | **4** |
| Create k vertices, count visible (k=1,2,3,8) | 1, 1, 1, 1 | **1, 2, 3, 8**
|
| 5 pre-existing + 3 new | 6 | **8** |
| #2491 middle stage (8 vertices → edges) | 0 edges | **56 edges** |
| `SET` 3 vertices, later `MATCH` on the new property | 1 | **3** |
| `REMOVE` from 3, later `MATCH` counts still-marked | 2 | **0** |
| `MATCH (n:N) CREATE (:N)` — must not self-feed | 6 | **6** (unchanged) |
New regression coverage in `cypher_create` and `cypher_set` for visibility
of every written row, for writes from an earlier clause driving a later one,
and for a write clause still not seeing its own writes. Each new assertion was
confirmed to fail without this change — `visible` reports 1 instead of 3,
`edges` 0 instead of 6, `still_marked` 2 instead of 0.
I also ran the suite on unmodified `master` and with this change through an
identical harness: the outputs are byte-identical apart from the new
assertions, so nothing else moved.
## Out of scope
#2491 additionally hits a separate defect, filed as #2494: the planner can
place the DML `CustomScan` on a side of a join the executor never pulls, so the
writes are skipped entirely and nothing persists. That is untouched here, and
#2491 will still fail until it is fixed.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]