Skip to content

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

Description

@Silence6666668

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?

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?

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:

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:

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

Anna
  1. Splitting the logic into two separate EXISTS predicates joined by OR also works:
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:

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions