Skip to content
Merged
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
44 changes: 42 additions & 2 deletions regress/expected/agtype.out
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,48 @@ SELECT '3.14::numeric'::agtype + '3.14::numeric'::agtype;
6.28::numeric
(1 row)

SELECT 'null'::agtype - '1';
?column?
----------

(1 row)

SELECT 'null'::agtype + '1';
?column?
----------

(1 row)

SELECT 'null'::agtype * '1';
?column?
----------

(1 row)

SELECT 'null'::agtype / '1';
?column?
----------

(1 row)

SELECT 'null'::agtype % '1';
?column?
----------

(1 row)

SELECT 'null'::agtype ^ '1';
?column?
----------

(1 row)

SELECT -'null'::agtype;
?column?
----------

(1 row)

--
-- Test operator - for extended functionality
--
Expand Down Expand Up @@ -1065,8 +1107,6 @@ SELECT '{"a":1 , "b":2, "c":3}'::agtype - 'null';
ERROR: expected agtype string, not agtype NULL
SELECT '{"a":1 , "b":2, "c":3}'::agtype - '["c","b"]' - '[1]' - '["a"]';
ERROR: expected agtype string, not agtype integer
SELECT 'null'::agtype - '1';
ERROR: Invalid input parameter types for agtype_sub
SELECT 'null'::agtype - '[1]';
ERROR: must be object or array, not a scalar value
SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype - '"a"';
Expand Down
63 changes: 63 additions & 0 deletions regress/expected/list_comprehension.out
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,69 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10
{"id": 281474976710668, "label": "", "properties": {"b": [0, 1, 2, 3, 4, 5], "c": [0, 2, 4, 6, 8, 10, 12], "list": [0, 2, 4, 6, 8, 10, 12]}}::vertex
(2 rows)

-- Issue 2394 - projection over a null element should propagate null,
-- not concatenate the null and the projection result into a sublist and
-- not error out for operators other than '+'.
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [null] | x + 1] $$) AS (result agtype);
result
--------
[null]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x + 1] $$) AS (result agtype);
result
--------------
[2, null, 3]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | 1 + x] $$) AS (result agtype);
result
--------------
[2, null, 3]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x] $$) AS (result agtype);
result
--------------
[1, null, 2]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x - 1] $$) AS (result agtype);
result
--------------
[0, null, 1]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x * 2] $$) AS (result agtype);
result
--------------
[2, null, 4]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x / 1] $$) AS (result agtype);
result
--------------
[1, null, 2]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x % 2] $$) AS (result agtype);
result
--------------
[1, null, 0]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x ^ 2] $$) AS (result agtype);
result
------------------
[1.0, null, 4.0]
(1 row)

SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | -x] $$) AS (result agtype);
result
----------------
[-1, null, -2]
(1 row)

-- Clean up
SELECT * FROM drop_graph('list_comprehension', true);
NOTICE: drop cascades to 4 other objects
Expand Down
8 changes: 7 additions & 1 deletion regress/sql/agtype.sql
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ SELECT '3'::agtype + '3.14'::agtype;
SELECT '3'::agtype + '3.14::numeric'::agtype;
SELECT '3.14'::agtype + '3.14::numeric'::agtype;
SELECT '3.14::numeric'::agtype + '3.14::numeric'::agtype;
SELECT 'null'::agtype - '1';
SELECT 'null'::agtype + '1';
SELECT 'null'::agtype * '1';
SELECT 'null'::agtype / '1';
SELECT 'null'::agtype % '1';
SELECT 'null'::agtype ^ '1';
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests pass a bare string literal as the RHS (type unknown), which relies on implicit coercion/operator resolution and can make the intent of testing the scalar agtype operator path less explicit. Consider casting the RHS to agtype (e.g., '1'::agtype) so the tests more directly exercise agtype-vs-agtype arithmetic and are less sensitive to changes in implicit casts.

Suggested change
SELECT 'null'::agtype ^ '1';
SELECT 'null'::agtype ^ '1'::agtype;

Copilot uses AI. Check for mistakes.
SELECT -'null'::agtype;

--
-- Test operator - for extended functionality
Expand Down Expand Up @@ -305,7 +312,6 @@ SELECT '{"a":1 , "b":2, "c":3}'::agtype - '[null]';
SELECT '{"a":1 , "b":2, "c":3}'::agtype - '1';
SELECT '{"a":1 , "b":2, "c":3}'::agtype - 'null';
SELECT '{"a":1 , "b":2, "c":3}'::agtype - '["c","b"]' - '[1]' - '["a"]';
SELECT 'null'::agtype - '1';
SELECT 'null'::agtype - '[1]';
SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype - '"a"';
SELECT '{"id": 1125899906842625, "label": "Vertex", "properties": {"a": "xyz", "b": true, "c": -19.888, "e": {"f": "abcdef", "g": {}, "h": [[], {}]}, "i": {"j": 199, "k": {"l": "mnopq"}}}}::vertex'::agtype - '["a"]';
Expand Down
14 changes: 14 additions & 0 deletions regress/sql/list_comprehension.sql
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,19 @@ SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10
SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WHERE u.list = [u IN [1, u]] RETURN u $$) AS (u agtype);
SELECT * FROM cypher('list_comprehension', $$ MATCH (u {list: [0, 2, 4, 6, 8, 10, 12]}) WHERE u.list IN [u IN [1, u.list]] RETURN u $$) AS (u agtype);

-- Issue 2394 - projection over a null element should propagate null,
-- not concatenate the null and the projection result into a sublist and
-- not error out for operators other than '+'.
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [null] | x + 1] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x + 1] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | 1 + x] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x - 1] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x * 2] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x / 1] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x % 2] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | x ^ 2] $$) AS (result agtype);
SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [1, null, 2] | -x] $$) AS (result agtype);

-- Clean up
SELECT * FROM drop_graph('list_comprehension', true);
42 changes: 42 additions & 0 deletions src/backend/utils/adt/agtype_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ Datum agtype_add(PG_FUNCTION_ARGS)
agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0);
agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0);

/* openCypher: arithmetic over null yields null. */
if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL)
{
PG_RETURN_NULL();
}
Comment on lines +168 to +172
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The identical AGTV_NULL short-circuit block is now duplicated across multiple operator functions (add/sub/mul/div/mod/pow/neg). To reduce repetition and keep behavior consistent over time, consider factoring this into a small helper (e.g., a static inline function or macro) used by each operator.

Copilot uses AI. Check for mistakes.

/*
* One or both values is a string OR one is a string and the other is
* either an integer, float, or numeric. If so, concatenate them.
Expand Down Expand Up @@ -525,6 +531,12 @@ Datum agtype_sub(PG_FUNCTION_ARGS)
agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0);
agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0);

/* openCypher: arithmetic over null yields null. */
if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL)
{
PG_RETURN_NULL();
}

if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER)
{
agtv_result.type = AGTV_INTEGER;
Expand Down Expand Up @@ -615,6 +627,12 @@ Datum agtype_neg(PG_FUNCTION_ARGS)

agtv_value = get_ith_agtype_value_from_container(&v->root, 0);

/* openCypher: arithmetic over null yields null. */
if (agtv_value->type == AGTV_NULL)
{
PG_RETURN_NULL();
}

if (agtv_value->type == AGTV_INTEGER)
{
agtv_result.type = AGTV_INTEGER;
Expand Down Expand Up @@ -666,6 +684,12 @@ Datum agtype_mul(PG_FUNCTION_ARGS)
agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0);
agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0);

/* openCypher: arithmetic over null yields null. */
if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL)
{
PG_RETURN_NULL();
}

if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER)
{
agtv_result.type = AGTV_INTEGER;
Expand Down Expand Up @@ -756,6 +780,12 @@ Datum agtype_div(PG_FUNCTION_ARGS)
agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0);
agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0);

/* openCypher: arithmetic over null yields null. */
if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL)
{
PG_RETURN_NULL();
}

if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER)
{
if (agtv_rhs->val.int_value == 0)
Expand Down Expand Up @@ -874,6 +904,12 @@ Datum agtype_mod(PG_FUNCTION_ARGS)
agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0);
agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0);

/* openCypher: arithmetic over null yields null. */
if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL)
{
PG_RETURN_NULL();
}

if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER)
{
agtv_result.type = AGTV_INTEGER;
Expand Down Expand Up @@ -964,6 +1000,12 @@ Datum agtype_pow(PG_FUNCTION_ARGS)
agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0);
agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0);

/* openCypher: arithmetic over null yields null. */
if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL)
{
PG_RETURN_NULL();
}

if (agtv_lhs->type == AGTV_INTEGER && agtv_rhs->type == AGTV_INTEGER)
{
agtv_result.type = AGTV_FLOAT;
Expand Down
Loading