apache / apache/age

A `WHERE EXISTS { ... }` subquery may crash Apache AGE when it contains both:

Open
#2,396 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
C
Stars
4.8k
Forks
523
Avg merge
1d 2h
Merged PRs (30d)
9

Description

**Describe the bug**
A `WHERE EXISTS { ... }` subquery may crash Apache AGE when it contains both:

1. a variable-length relationship match
2. a `UNION` branch

In the minimized repro below, the server connection is terminated while evaluating the query.

**How are you accessing AGE (Command line, driver, etc.)?**
- PostgreSQL `cypher(...)` wrapper through the local Python differential-testing harness
- Reproducible directly in `psql` inside the Docker container

**What data setup do we need to do?**
```pgsql
SELECT * FROM cypher('fuzz_graph', $$
CREATE (:Country {name: 'Germany'}),
(:Country {name: 'United Kingdom'}),
(:Person {name: 'Anna'})
$$) AS (v agtype);

SELECT * FROM cypher('fuzz_graph', $$
MATCH (a:Person {name:'Anna'}), (g:Country {name:'Germany'})
CREATE (a)-[:LIVING_IN]->(g)
$$) AS (v agtype);
```

**What is the necessary configuration info needed?**
- Plain Apache AGE Docker image was enough
- Docker image in local repro: `apache/age`
- AGE extension version: `1.7.0`
- PostgreSQL version: `18.1`
- Graph name used in repro: `fuzz_graph`
- No extra extensions or special configuration were required

**What is the command that caused the error?**
```pgsql
SELECT * FROM cypher('fuzz_graph', $$
MATCH (person:Person)
WHERE EXISTS {
MATCH (person)-[:LIVING_IN*1..2]->(:Country)
UNION
MATCH (person)-[:WORKING_IN]->(:Country)
}
RETURN person.name AS name
$$) AS (name agtype);
```

Returned result on AGE:
```text
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
```

**Expected behavior**
The query should succeed and return:

```text
Anna
```

Neo4j returns exactly that result for the equivalent Cypher query.

**Environment (please complete the following information):**
- Version: Apache AGE `1.7.0`
- PostgreSQL: `18.1`
- Host OS: Windows
- Architecture: x86_64
- Deployment: Docker

**Additional context**
Two nearby control cases behave correctly on the same AGE instance:

1. The variable-length `EXISTS` branch alone works:

```pgsql
SELECT * FROM cypher('fuzz_graph', $$
MATCH (person:Person)
WHERE EXISTS { MATCH (person)-[:LIVING_IN*1..2]->(:Country) }
RETURN person.name AS name
$$) AS (name agtype);
```

Observed result:
```text
Anna
```

2. Splitting the logic into two separate `EXISTS` predicates joined by `OR` also works:

```pgsql
SELECT * FROM cypher('fuzz_graph', $$
MATCH (person:Person)
WHERE EXISTS { MATCH (person)-[:LIVING_IN*1..2]->(:Country) }
OR EXISTS { MATCH (person)-[:WORKING_IN]->(:Country) }
RETURN person.name AS name
$$) AS (name agtype);
```

Observed result:
```text
Anna
```

So the crash appears to be specifically tied to combining a variable-length pattern and `UNION` inside the same `EXISTS` subquery.

This issue was first found during automated Neo4j-vs-AGE differential testing in a larger query:

```cypher
MATCH (person:Person)
WHERE EXISTS {
MATCH (person)-[:LIVING_IN*1..2]->(:Country {name: 'Germany'})
UNION
MATCH (person)-[:WORKING_IN]->(:Country {name: 'United Kingdom'})
}
OPTIONAL MATCH (person)-[r:LIVING_IN]->(c:Country)
WITH person, collect({rel: r, country: c}) AS connections
RETURN person.name AS name, size(connections) AS connection_count
ORDER BY name DESC
LIMIT 10
```

After minimization, the extra `OPTIONAL MATCH`, `collect(...)`, and `ORDER BY/LIMIT` clauses were no longer needed to reproduce the server crash.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.