gregfelice commented on issue #2494:
URL: https://github.com/apache/age/issues/2494#issuecomment-5171231812

   I have a working fix for this, but it carries a semantics change that I 
would rather have agreement on before opening a PR — particularly with 1.8.0 
mid-release.
   
   **Environment:** `master` @ 80141740, PostgreSQL 18.4, built from source.
   
   ## Proposed fix
   
   PostgreSQL already solves exactly this problem for data-modifying CTEs. 
`ExecPostprocessPlan()` runs everything on `EState.es_auxmodifytables` to 
completion once the main plan is done, specifically so that writes do not 
depend on the main query pulling from them. It iterates that list as plain 
`PlanState *` and calls `ExecProcNode`, so AGE's DML CustomScan can join it 
directly:
   
   ```c
   /* in begin_cypher_create(), after Increment_Estate_CommandId() */
   if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
   {
       estate->es_auxmodifytables = lappend(estate->es_auxmodifytables, node);
   }
   ```
   
   Re-running an already-exhausted node is a no-op — its subtree returns NULL 
immediately. `EXPLAIN` without `ANALYZE` never reaches `ExecutorFinish`, hence 
the guard.
   
   Result:
   
   | Query | Writes before | Writes after |
   |---|---|---|
   | Minimal repro in this issue | 0 nodes, 0 edges | **4 nodes, 2 edges** |
   | #2491 as filed | 0 nodes, 0 edges | **8 nodes, 56 edges** |
   
   Full regression suite: **42/42**.
   
   ## What it does not fix
   
   The writes now happen, but they happen at `ExecutorFinish`, after the 
reading clause has already run. So the *query results* are unchanged: this 
issue's repro still returns `0`, and #2491 still returns `"FAIL"`. Only the 
silent data loss is fixed.
   
   Making the results correct means getting the planner to order the write 
before the read — the DML CustomScan would have to be somewhere the executor 
cannot skip *and* cannot schedule after the reading scan. That is a much larger 
change and I have not attempted it.
   
   So this is a fix for the title of the issue, not for the full openCypher 
semantics behind it.
   
   ## The semantics change to agree on
   
   Draining to completion also changes what `LIMIT` does over a write clause. 
Against 5 `:src` vertices:
   
   ```cypher
   MATCH (n:src) CREATE (:made {from: n.id}) RETURN n LIMIT 1
   ```
   
   | | `:made` vertices created |
   |---|---|
   | master today | 1 |
   | with this change | **5** |
   
   Measured both ways on the same build, not inferred.
   
   I think 5 is the correct answer — it is what Neo4j does (the write clause 
processes every incoming row; `LIMIT` bounds the returned rows, not the 
writes), and it matches PostgreSQL's rule that data-modifying CTEs always 
execute to completion regardless of what the primary query reads. By that 
reading, today's behaviour is the same defect as this issue wearing a different 
hat: a write silently skipped because the executor stopped pulling.
   
   But it is a user-visible change, no test in the suite asserts either 
behaviour, and it would land during a release. Hence asking rather than filing.
   
   ## Scope
   
   The same hazard applies to `SET`, `DELETE`, and `MERGE` — each is a 
CustomScan the planner can place on a skippable side of a join. The fix is the 
same one-liner in each `begin_*` function. I held off pending direction on the 
above.
   
   @jrgemignani @MuhammadTahaNaveed — three questions:
   
   1. Is the `LIMIT`-over-write change acceptable, or should writes stay 
bounded by what the plan pulls?
   2. Land this for 1.8.0, or hold for after the release?
   3. All four DML clauses in one PR, or `CREATE` first?
   
   Happy to open the PR immediately once there is a direction.
   


-- 
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]

Reply via email to