Preserve null-valued keys in map literals (#2391)#2412
Conversation
|
Hi @jrgemignani , @MuhammadTahaNaveed could you pls review this.. |
f53fddc to
df92b80
Compare
Sorry for the delay |
There was a problem hiding this comment.
Pull request overview
This PR fixes Cypher map literal semantics so that keys with null values are preserved (e.g., RETURN {a: null} returns {"a": null} instead of {}), aligning AGE behavior with openCypher/Neo4j.
Changes:
- Set the grammar-created
cypher_mapnodes to preservenullvalues by default (keep_null = true). - Add regression tests covering
null-valued map entries (including nested maps andkeys()/coalesce()behavior), plus controls for CREATE property stripping. - Update expected regression outputs impacted by the corrected semantics (including an orderability test containing a nested map with a
nullentry).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/backend/parser/cypher_gram.y |
Changes map literal default to preserve null values via keep_null = true. |
regress/sql/expr.sql |
Adds dedicated regression coverage for Issue #2391 and controls for CREATE behavior. |
regress/expected/expr.out |
Updates expected outputs for preserved null keys and new Issue #2391 test block. |
regress/expected/agtype.out |
Updates orderability expected output affected by preserved nested null map entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -- End of tests | ||
| -- | ||
|
|
||
| -- | ||
| -- Issue 2391 - map literals must preserve keys whose values are null |
There was a problem hiding this comment.
Good catch - I've moved the -- End of tests marker to the actual end of the file, after the Issue 2391 regression block, so it no longer implies the script terminates early. Fixed in c1a3cba.
| SELECT * FROM cypher('issue_2391', $$ | ||
| RETURN {outer: {inner: null, kept: 1}} AS m | ||
| $$) AS (m agtype); | ||
| -- mixed non-null and null values are all preserved in order |
There was a problem hiding this comment.
Agreed - map key order isn't guaranteed and isn't what this test asserts. I've dropped the misleading in order wording from the comment (it now reads mixed non-null and null values are all preserved). Fixed in c1a3cba.
Map literals such as RETURN {a: null} previously dropped keys whose
values were null, producing {} instead of {"a": null}. This
diverged from the openCypher / Neo4j semantics where map literals
preserve every key the user wrote, including those bound to null.
Root cause: cypher_map.keep_null defaulted to false (zero-initialised),
so the grammar-produced node fed agtype_build_map_nonull, which strips
null entries. Call sites that legitimately need strip-null semantics
(CREATE node/edge property maps and SET = assignments) already set
keep_null=false explicitly, and the MATCH pattern path sets it to true
explicitly. Flipping the grammar default to true therefore only affects
the cases that were buggy (bare map expressions and nested map values),
and leaves CREATE/SET behaviour unchanged.
Two preexisting tests encoded the old buggy output and are updated:
expr.out (bare RETURN maps now keep the null value) and agtype.out
(a nested map inside an orderability test no longer drops its null
entry, shifting one row in the ORDER BY result). Dedicated regression
coverage for apache#2391 is added to regress/sql/expr.sql.
- Move 'End of tests' to after the Issue 2391 test block so it marks the actual end of the file. - Remove 'in order' from the mixed-values comment since map key ordering is not guaranteed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
df92b80 to
c1a3cba
Compare
Map literals now build with agtype_build_map (keep_null) instead of agtype_build_map_nonull, so the accessor-optimization EXPLAIN plan in expr.out must reflect the new function name. Also strip a stray trailing CRLF on the last line of expr.sql/expr.out that leaked a carriage return into the regression output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Fixes #2391.
Map literals such as
RETURN {a: null}previously dropped keys whose values were null, producing{}instead of{"a": null}. This diverged from the openCypher / Neo4j semantics where map literals preserve every key the user wrote, including those bound to null.Root cause
cypher_map.keep_nulldefaulted tofalse(zero-initialised), so the grammar-produced node fedagtype_build_map_nonull, which strips null entries. Call sites that legitimately need strip-null semantics (CREATE node/edge property maps and SET=assignments) already setkeep_null = falseexplicitly, and the MATCH pattern path sets it totrueexplicitly. Flipping the grammar default totruetherefore only affects the cases that were buggy (bare map expressions and nested map values), and leaves CREATE / SET behaviour unchanged.Changes
src/backend/parser/cypher_gram.y— themap:rule now setsn->keep_null = truewith an explanatory comment.regress/sql/expr.sql— dedicated regression coverage for Map literals may drop keys whose values arenull. #2391 (single-key null, multiple nulls,keys(),coalesce, nested map values, mixed null / non-null, empty map, CREATE control, MATCH verify).regress/expected/expr.out— two preexisting tests that encoded the old buggy output now show the preserved"z": null; plus expected output for the new Map literals may drop keys whose values arenull. #2391 block.regress/expected/agtype.out— nested{bool: true, i: null}inside an orderability test no longer drops its null entry, shifting one row in theORDER BYresult.Behaviour before / after
CREATE and SET
=still strip null-valued keys (unchanged), matching openCypher semantics for property writes.Test results
make installcheckagainst PostgreSQL 18: 32 / 33 pass. The only failure isage_upgrade, which is a preexisting failure unrelated to this change.