Skip to content

Fix leading OPTIONAL MATCH dropping the row when the pattern has no match - #2483

Open
Yyunozor wants to merge 1 commit into
apache:masterfrom
Yyunozor:fix/optional-match-standalone-null-row
Open

Fix leading OPTIONAL MATCH dropping the row when the pattern has no match#2483
Yyunozor wants to merge 1 commit into
apache:masterfrom
Yyunozor:fix/optional-match-standalone-null-row

Conversation

@Yyunozor

Copy link
Copy Markdown

A query that begins with OPTIONAL MATCH returns no rows when the pattern matches nothing:

SELECT * FROM cypher('g', $$ OPTIONAL MATCH (n:NoSuchLabel) RETURN n $$) AS (n agtype);
 n
---
(0 rows)

Cypher gives every OPTIONAL MATCH left join semantics. With no clause to its left it joins against the implicit single row that begins a query, so this has to return one row with n = NULL — that is what separates OPTIONAL MATCH from MATCH.

transform_cypher_match_pattern builds the null-preserving LATERAL LEFT JOIN out of the previous clause, and falls through to the plain MATCH path when there is none ("If there is no previous clause, transform to a general MATCH clause"). A leading OPTIONAL MATCH has no previous clause, so it took the plain path. Any preceding clause, however trivial, already produced the right answer:

$$ WITH 1 AS x OPTIONAL MATCH (n:NoSuchLabel) RETURN n $$    -- 1 row, n = NULL
$$ UNWIND [1] AS x OPTIONAL MATCH (n:NoSuchLabel) RETURN n $$ -- 1 row, n = NULL

The join was already right; only its left side was missing. This gives a leading OPTIONAL MATCH a previous clause that yields exactly one row, built the way transform_cypher_with already builds a synthesized cypher_return. Its lone column is named with the hidden variable prefix, so expand_pnsi_attrs keeps it out of RETURN * expansion.

The clause is attached in analyze_cypher rather than in the transform. The transform temporarily clears prev to recurse into the join's right side and relies on prev being NULL there to take the plain MATCH path, so synthesizing a prev inside it re-arms that recursion until the stack runs out.

Tests cover the no-match case for node and relationship patterns, a WHERE that no binding survives, that a matching pattern gains no extra null row, that RETURN * does not expose the internal column, that a leading MATCH still returns no rows, and that MATCH following OPTIONAL MATCH still errors. make installcheck passes 43/43 on this branch and on its parent; with only the two test files applied to the parent, cypher_match fails.

Known limitation: a UNION branch beginning with OPTIONAL MATCH still returns no rows. Branches are assembled through cypher_parse_sub_analyze_union, not analyze_cypher, so they never reach this path. Placing the same unit clause there would be a natural follow-up.

Closes #2473

…atch

A query that begins with OPTIONAL MATCH returned no rows when its pattern
matched nothing:

    OPTIONAL MATCH (n:NoSuchLabel) RETURN n   -- returned 0 rows

Cypher gives every OPTIONAL MATCH left join semantics. With no clause to its
left it joins against the implicit single row that begins a query, so the
pattern above must still emit one row with n = NULL. That is what distinguishes
OPTIONAL MATCH from MATCH.

transform_cypher_match_pattern builds the null-preserving LATERAL LEFT JOIN out
of the previous clause, and falls through to the plain MATCH path when there is
none. A leading OPTIONAL MATCH has no previous clause, so it took the plain
path. Any preceding clause, even a trivial one, already produced the correct
result:

    WITH 1 AS x OPTIONAL MATCH (n:NoSuchLabel) RETURN n   -- 1 row, n = NULL

So the join itself was already right; only its left side was missing. This
gives a leading OPTIONAL MATCH a previous clause that produces exactly one row,
built the way transform_cypher_with already builds a synthesized cypher_return.
Its lone column is named with the hidden variable prefix, so expand_pnsi_attrs
keeps it out of RETURN * expansion.

The clause is attached in analyze_cypher rather than in the transform because
the transform temporarily clears prev to recurse into the join right side, and
relies on prev being NULL there to take the plain MATCH path; synthesizing a
prev inside the transform re-arms that recursion indefinitely.

Tests cover the no-match case for node and relationship patterns, a WHERE that
no binding survives, that a matching pattern gains no extra null row, that
RETURN * does not expose the internal column, that a leading MATCH still
returns no rows, and that MATCH following OPTIONAL MATCH still errors.

Closes apache#2473
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standalone OPTIONAL MATCH drops the initial input row when the pattern has no match

1 participant