ysys143 opened a new issue, #2500:
URL: https://github.com/apache/age/issues/2500

   ### Summary
   
   On `master` (`8014174`) and on the `PG18` branch (= `release/PG18/1.8.0`, 
both at
   `e43dc1a`), a Cypher query that
   compares `id()` against a list **held in a variable** dereferences a 
`graphid` as if it
   were a varlena pointer and terminates the backend with `SIGSEGV`. PostgreSQL 
then
   recycles every other backend and enters crash recovery, so one ordinary 
query takes
   down the instance.
   
   `release/PG18/1.7.0` built from source on the identical base image is 
unaffected.
   
   ### Minimal reproduction
   
   ```sql
   CREATE EXTENSION IF NOT EXISTS age CASCADE;
   LOAD 'age';
   SET search_path = ag_catalog, "$user", public;
   
   SELECT create_graph('gtest');
   SELECT * FROM cypher('gtest', $$ CREATE (:P {name: 'a'}) $$) AS (v agtype);
   
   -- crashes on 1.8.0; returns 0 on 1.7.0
   SELECT count(*) FROM cypher('gtest',
     $$ WITH [1] AS ids MATCH (n) WHERE id(n) IN ids RETURN n $$) AS (n agtype);
   ```
   
   The `CREATE` is required: the crash only happens once `MATCH` actually 
produces a row
   (see the scope table). A single vertex is enough, and the list element does 
not have to
   be a real id — `[1]` matches nothing and still crashes.
   
   Server log from running exactly the snippet above against a freshly created 
graph:
   
   ```
   LOG:  client backend (PID 141) was terminated by signal 11: Segmentation 
fault
   DETAIL:  Failed process was running: SELECT count(*) FROM cypher('gtest',
                   $$ WITH [1] AS ids MATCH (n) WHERE id(n) IN ids RETURN n $$) 
AS (n agtype);
   LOG:  terminating any other active server processes
   LOG:  all server processes terminated; reinitializing
   ```
   
   ### Backtrace
   
   Core dump from a `-O0 -g3` build:
   
   ```
   Program terminated with signal SIGSEGV, Segmentation fault.
   #0  pg_detoast_datum ()
   #1  agtype_in_operator (fcinfo=0x...) at src/backend/utils/adt/agtype.c:5052
   #2-#6 [ExecQual / ExecScan]
   #7  standard_ExecutorRun ()
   #9  PortalRun ()
   #11 PostgresMain ()
   ```
   
   `agtype.c:5052` is the fetch of the **left-hand** operand of `IN`:
   
   ```c
   5051        /* get the item to search for */
   5052        agt_item = AG_GET_ARG_AGTYPE_P(1);
   ```
   
   ### Root cause
   
   `agtype_in_operator` is **byte-for-byte identical** between 1.7.0 and 1.8.0 
(I diffed the
   function body). What changed is the type of what reaches it.
   
   On 1.8.0 `id()` produces `graphid`, not `agtype`:
   
   ```
   -- 1.8.0
   SELECT i FROM cypher('gtest', $$ MATCH (n:P) RETURN id(n) $$) AS (i bigint);
   ERROR:  cannot cast type graphid to bigint for column "i"
   -- 1.7.0: returns 844424930131969
   ```
   
   (`agtype` → `bigint` still works on 1.8.0 — e.g. `count()` declared `bigint` 
— so this is
   specific to `id()`'s new output type. This is consistent with 1.8.0 
introducing native
   `vertex` / `edge` types: `pg_cast` gains `vertex→agtype` and `edge→agtype`, 
and `pg_proc`
   gains `vertex_to_agtype` / `edge_to_agtype`, none of which exist on 1.7.0.)
   
   `graphid` is a **pass-by-value int64**. When the planner selects 
`agtype_in_operator`
   without inserting the `graphid→agtype` cast, line 5052 hands that raw 
integer to
   `pg_detoast_datum`, which dereferences it as a pointer.
   
   The variable-bound list is what lets this through. With a *multi-element 
literal* list,
   type resolution instead fails cleanly before execution:
   
   ```
   -- 1.8.0
   MATCH (n) WHERE id(n) IN [1,2] RETURN n
   ERROR:  not a common type: 16461
   ```
   
   (The OID is assigned at `CREATE EXTENSION` time, so it differs per install. 
Here
   `SELECT 16461::regtype` → `ag_catalog.agtype`; a second install of the same 
version
   reported `16462` for the same error, which likewise resolves to 
`ag_catalog.agtype`.)
   
   So the same underlying type change produces a clean error on one path and a 
segfault on
   the other.
   
   ### Scope
   
   Each probe run in its own session against the same seeded graph, on both 
versions built
   identically. `v` denotes a variable bound by `WITH`.
   
   | probe | query | 1.7.0 | 1.8.0 |
   |---|---|---|---|
   | id + IN + var-list | `WITH [1] AS v MATCH (n) WHERE id(n) IN v` | ok (0) | 
**SEGV** |
   | id + IN + var-list, real ids | `MATCH (x:P) WITH collect(id(x)) AS v MATCH 
(n) WHERE id(n) IN v` | ok (2) | **SEGV** |
   | id + IN + 1-elem literal | `MATCH (n) WHERE id(n) IN [1]` | ok (0) | ok 
(0) |
   | id + IN + n-elem literal | `MATCH (n) WHERE id(n) IN [1,2]` | ok (0) | 
`ERROR: not a common type: 16461` |
   | **prop** + IN + var-list | `WITH ['a'] AS v MATCH (n) WHERE n.name IN v` | 
ok (1) | ok (1) |
   | id + **=** + var index | `WITH [1] AS v MATCH (n) WHERE id(n) = v[0]` | ok 
(0) | ok (0) |
   | id + = + literal | `MATCH (n) WHERE id(n) = 1` | ok (0) | ok (0) |
   | var-list alone | `WITH [1] AS v MATCH (n) RETURN n` | ok (2) | ok (2) |
   | id + IN + var, **0 rows matched** | same, on a graph with no vertices | ok 
(0) | ok (0) |
   | id + IN + var, **0 rows matched** | same, `MATCH (n:Absent)` | ok (0) | ok 
(0) |
   
   Dropping any one ingredient avoids the crash: replacing `id()` with a 
property, replacing
   `IN` with `=`, or replacing the variable with a literal list all run clean. 
The last two
   rows show the operator is only reached once a row survives `MATCH`, which is 
why the
   repro needs at least one vertex.
   
   ### Environment
   
   Both legs built from the same `Dockerfile` — same base image, same 
toolchain, same flags
   — so AGE version is the only variable:
   
   - base: `postgres:18` (PostgreSQL 18.4, Debian), `aarch64`
   - crashing: `release/PG18/1.8.0` @ `e43dc1a12b78fba4acef9835b2b10379b8d243b4`
   - working: `release/PG18/1.7.0` @ `806fa2ebdb300b3e76ef30cdba61803babbf2683`
   - built with `make COPT="-O0 -g3 -fno-omit-frame-pointer"` for the backtrace
   
   `release/PG18/1.8.0` and the `PG18` branch head are currently the **same 
commit**, so this
   is the state of the PG18 development tip, not just a tagged RC.
   
   **`master` is affected too.** Built from the same Dockerfile (`--build-arg 
AGE_BRANCH=master`,
   commit `801417404978823bd8732452c3f7959017584785`) on the same `postgres:18` 
base, the
   snippet above crashes identically:
   
   ```
   LOG:  client backend (PID 96) was terminated by signal 11: Segmentation fault
   DETAIL:  Failed process was running: SELECT count(*) FROM cypher('gtest', ...
   LOG:  all server processes terminated; reinitializing
   ```
   
   So this is not confined to the PG18 release lineage — a fix needs to land on 
`master` as well.
   
   ### Impact
   
   `WITH [<ids>] AS node_ids MATCH (a) WHERE id(a) IN node_ids` is the natural 
way to fetch a
   subgraph for a known vertex set, and it is what LightRAG's PostgreSQL graph 
backend
   issues, so a single ordinary API call crashes the server. Docker Hub 
currently has no
   `release_PG18_1.8.0` image, so this is not yet in users' hands.
   


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