Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions regress/expected/agtype.out
Original file line number Diff line number Diff line change
Expand Up @@ -2167,8 +2167,8 @@ SELECT * FROM cypher('orderability_graph', $$ MATCH (n) RETURN n ORDER BY n.prop
{"id": 844424930131981, "label": "vertex", "properties": {"prop": [{"id": 0, "label": "v", "properties": {"i": 0}}::vertex, {"id": 2, "label": "e", "end_id": 1, "start_id": 0, "properties": {"i": 0}}::edge, {"id": 1, "label": "v", "properties": {"i": 0}}::vertex]::path}}::vertex
{"id": 844424930131980, "label": "vertex", "properties": {"prop": {"id": 2, "label": "e", "end_id": 1, "start_id": 0, "properties": {"i": 0}}::edge}}::vertex
{"id": 844424930131979, "label": "vertex", "properties": {"prop": {"id": 0, "label": "v", "properties": {"i": 0}}::vertex}}::vertex
{"id": 844424930131978, "label": "vertex", "properties": {"prop": {"bool": true}}}::vertex
{"id": 844424930131977, "label": "vertex", "properties": {"prop": {"i": 0, "bool": true}}}::vertex
{"id": 844424930131978, "label": "vertex", "properties": {"prop": {"i": null, "bool": true}}}::vertex
{"id": 844424930131975, "label": "vertex", "properties": {"prop": [1, 2, 3]}}::vertex
{"id": 844424930131976, "label": "vertex", "properties": {"prop": [1, 2, 3, 4, 5]}}::vertex
{"id": 844424930131973, "label": "vertex", "properties": {"prop": "string"}}::vertex
Expand All @@ -2190,8 +2190,8 @@ SELECT * FROM cypher('orderability_graph', $$ MATCH (n) RETURN n ORDER BY n.prop
{"id": 844424930131973, "label": "vertex", "properties": {"prop": "string"}}::vertex
{"id": 844424930131976, "label": "vertex", "properties": {"prop": [1, 2, 3, 4, 5]}}::vertex
{"id": 844424930131975, "label": "vertex", "properties": {"prop": [1, 2, 3]}}::vertex
{"id": 844424930131978, "label": "vertex", "properties": {"prop": {"i": null, "bool": true}}}::vertex
{"id": 844424930131977, "label": "vertex", "properties": {"prop": {"i": 0, "bool": true}}}::vertex
{"id": 844424930131978, "label": "vertex", "properties": {"prop": {"bool": true}}}::vertex
{"id": 844424930131979, "label": "vertex", "properties": {"prop": {"id": 0, "label": "v", "properties": {"i": 0}}::vertex}}::vertex
{"id": 844424930131980, "label": "vertex", "properties": {"prop": {"id": 2, "label": "e", "end_id": 1, "start_id": 0, "properties": {"i": 0}}::edge}}::vertex
{"id": 844424930131981, "label": "vertex", "properties": {"prop": [{"id": 0, "label": "v", "properties": {"i": 0}}::vertex, {"id": 2, "label": "e", "end_id": 1, "start_id": 0, "properties": {"i": 0}}::edge, {"id": 1, "label": "v", "properties": {"i": 0}}::vertex]::path}}::vertex
Expand Down
114 changes: 108 additions & 6 deletions regress/expected/expr.out
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ SELECT * FROM cypher('expr', $$RETURN {}$$) AS r(c agtype);
SELECT * FROM cypher('expr', $$
RETURN {s: 's', i: 1, f: 1.0, b: true, z: null}
$$) AS r(c agtype);
c
-----------------------------------------
{"b": true, "f": 1.0, "i": 1, "s": "s"}
c
----------------------------------------------------
{"b": true, "f": 1.0, "i": 1, "s": "s", "z": null}
(1 row)

-- nested maps
SELECT * FROM cypher('expr', $$
RETURN {s: {s: 's'}, t: {i: 1, e: {f: 1.0}, s: {a: {b: true}}}, z: null}
$$) AS r(c agtype);
c
----------------------------------------------------------------------------
{"s": {"s": "s"}, "t": {"e": {"f": 1.0}, "i": 1, "s": {"a": {"b": true}}}}
c
---------------------------------------------------------------------------------------
{"s": {"s": "s"}, "t": {"e": {"f": 1.0}, "i": 1, "s": {"a": {"b": true}}}, "z": null}
(1 row)

--
Expand Down Expand Up @@ -9457,3 +9457,105 @@ NOTICE: graph "list" has been dropped
--
-- End of tests
--
--
-- Issue 2391 - map literals must preserve keys whose values are null
--
SELECT create_graph('issue_2391');
NOTICE: graph "issue_2391" has been created
create_graph
--------------

(1 row)

-- single-key null
SELECT * FROM cypher('issue_2391', $$
RETURN {a: null} AS m
$$) AS (m agtype);
m
-------------
{"a": null}
(1 row)

-- multiple null values
SELECT * FROM cypher('issue_2391', $$
RETURN {companyName: null, sinceYear: null} AS m
$$) AS (m agtype);
m
------------------------------------------
{"sinceYear": null, "companyName": null}
(1 row)

-- keys() must see the null-valued key
SELECT * FROM cypher('issue_2391', $$
RETURN keys({a: null}) AS ks
$$) AS (ks agtype);
ks
-------
["a"]
(1 row)

-- coalesce passes a non-null map (map itself is not null) through
SELECT * FROM cypher('issue_2391', $$
RETURN coalesce({a: null}, null) AS m
$$) AS (m agtype);
m
-------------
{"a": null}
(1 row)

-- nested map values inside an expression also preserve nulls
SELECT * FROM cypher('issue_2391', $$
RETURN {outer: {inner: null, kept: 1}} AS m
$$) AS (m agtype);
m
---------------------------------------
{"outer": {"kept": 1, "inner": null}}
(1 row)

-- mixed non-null and null values are all preserved in order
SELECT * FROM cypher('issue_2391', $$
RETURN {a: 1, b: null, c: 'x'} AS m
$$) AS (m agtype);
m
-------------------------------
{"a": 1, "b": null, "c": "x"}
(1 row)

-- control: empty map is still empty
SELECT * FROM cypher('issue_2391', $$
RETURN {} AS m
$$) AS (m agtype);
m
----
{}
(1 row)

-- control: CREATE must still strip top-level null properties so
-- setting a property to null removes it from storage
SELECT * FROM cypher('issue_2391', $$
CREATE (n:Item {keep: 1, drop: null}) RETURN n
$$) AS (n agtype);
n
-----------------------------------------------------------------------------
{"id": 844424930131969, "label": "Item", "properties": {"keep": 1}}::vertex
(1 row)

SELECT * FROM cypher('issue_2391', $$
MATCH (n:Item) RETURN n
$$) AS (n agtype);
n
-----------------------------------------------------------------------------
{"id": 844424930131969, "label": "Item", "properties": {"keep": 1}}::vertex
(1 row)

SELECT * FROM drop_graph('issue_2391', true);
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table issue_2391._ag_label_vertex
drop cascades to table issue_2391._ag_label_edge
drop cascades to table issue_2391."Item"
NOTICE: graph "issue_2391" has been dropped
drop_graph
------------

(1 row)

42 changes: 42 additions & 0 deletions regress/sql/expr.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3739,3 +3739,45 @@ SELECT * FROM drop_graph('list', true);
--
-- End of tests
--

--
-- Issue 2391 - map literals must preserve keys whose values are null
--
SELECT create_graph('issue_2391');
-- single-key null
SELECT * FROM cypher('issue_2391', $$
RETURN {a: null} AS m
$$) AS (m agtype);
-- multiple null values
SELECT * FROM cypher('issue_2391', $$
RETURN {companyName: null, sinceYear: null} AS m
$$) AS (m agtype);
-- keys() must see the null-valued key
SELECT * FROM cypher('issue_2391', $$
RETURN keys({a: null}) AS ks
$$) AS (ks agtype);
-- coalesce passes a non-null map (map itself is not null) through
SELECT * FROM cypher('issue_2391', $$
RETURN coalesce({a: null}, null) AS m
$$) AS (m agtype);
-- nested map values inside an expression also preserve nulls
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
SELECT * FROM cypher('issue_2391', $$
RETURN {a: 1, b: null, c: 'x'} AS m
$$) AS (m agtype);
-- control: empty map is still empty
SELECT * FROM cypher('issue_2391', $$
RETURN {} AS m
$$) AS (m agtype);
-- control: CREATE must still strip top-level null properties so
-- setting a property to null removes it from storage
SELECT * FROM cypher('issue_2391', $$
CREATE (n:Item {keep: 1, drop: null}) RETURN n
$$) AS (n agtype);
SELECT * FROM cypher('issue_2391', $$
MATCH (n:Item) RETURN n
$$) AS (n agtype);
SELECT * FROM drop_graph('issue_2391', true);
8 changes: 8 additions & 0 deletions src/backend/parser/cypher_gram.y
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,14 @@ map:

n = make_ag_node(cypher_map);
n->keyvals = $2;
/*
* By default, a Cypher map literal preserves keys whose
* values are null (openCypher / Neo4j semantics: e.g.
* RETURN {a: null} yields {a: null}, not {}). Callers
* that need property-stripping semantics (CREATE, SET =)
* override this to false in cypher_clause.c.
*/
n->keep_null = true;

$$ = (Node *)n;
}
Expand Down