Propagate null through agtype arithmetic operators#2405
Propagate null through agtype arithmetic operators#2405jrgemignani merged 1 commit intoapache:masterfrom
Conversation
The scalar branch of agtype_add lacked an AGTV_NULL case and fell through to the scalar-scalar concat path, so `null + 1` produced the two-element list `[null, 1]` instead of null. This surfaced most visibly inside a list comprehension projection (e.g. `[x IN [1, null, 2] | x + 1]` yielded `[2, [null, 1], 3]`) because there both operands are typed agtype and bypass the agtype_any_add wrapper that already short-circuits on null. The sibling operators (-, *, /, %, ^, unary -) had the same gap but surfaced as "Invalid input parameter types" errors rather than concats. Short-circuit all seven functions to SQL NULL when any operand is AGTV_NULL. SQL NULL (not a wrapped AGTV_NULL scalar) matches AGE's existing convention: `RETURN null AS v` already yields SQL NULL at the row level, and agtype_any_add already returns PG NULL for AGTV_NULL input, so the two operator-resolution paths stay indistinguishable. List aggregation re-packs SQL NULL entries as AGTV_NULL, so `[x IN [1, null, 2] | x + 1]` now renders as `[2, null, 3]`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
44c9bd6 to
b4cb92d
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Propagates SQL NULL through scalar agtype arithmetic operators to match openCypher semantics and prevent unintended concatenation / type errors, especially in list comprehension projections.
Changes:
- Add early-return
PG_RETURN_NULL()handling forAGTV_NULLoperands across+ - * / % ^and unary-. - Extend regression coverage for list comprehensions to ensure null propagation across arithmetic operators.
- Add/adjust
agtyperegression expectations to reflect SQL NULL results for arithmetic involvingnull.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/backend/utils/adt/agtype_ops.c | Adds AGTV_NULL short-circuit returns for scalar arithmetic operators. |
| regress/sql/list_comprehension.sql | Adds regression queries covering null propagation in list comprehension projections. |
| regress/sql/agtype.sql | Adds regression queries for arithmetic operators with null operands. |
| regress/expected/list_comprehension.out | Updates expected results to show null propagation instead of sublists/errors. |
| regress/expected/agtype.out | Updates expected results to show SQL NULL outputs for null arithmetic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| SELECT 'null'::agtype * '1'; | ||
| SELECT 'null'::agtype / '1'; | ||
| SELECT 'null'::agtype % '1'; | ||
| SELECT 'null'::agtype ^ '1'; |
There was a problem hiding this comment.
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.
| SELECT 'null'::agtype ^ '1'; | |
| SELECT 'null'::agtype ^ '1'::agtype; |
| /* openCypher: arithmetic over null yields null. */ | ||
| if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) | ||
| { | ||
| PG_RETURN_NULL(); | ||
| } |
There was a problem hiding this comment.
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.
The scalar branch of agtype_add lacked an AGTV_NULL case and fell through to the scalar-scalar concat path, so
null + 1produced the two-element list[null, 1]instead of null. This surfaced most visibly inside a list comprehension projection (e.g.[x IN [1, null, 2] | x + 1]yielded[2, [null, 1], 3]) because there both operands are typed agtype and bypass the agtype_any_add wrapper that already short-circuits on null.The sibling operators (-, *, /, %, ^, unary -) had the same gap but surfaced as "Invalid input parameter types" errors rather than concats.
Short-circuit all seven functions to SQL NULL when any operand is AGTV_NULL. SQL NULL (not a wrapped AGTV_NULL scalar) matches AGE's existing convention:
RETURN null AS valready yields SQL NULL at the row level, and agtype_any_add already returns PG NULL for AGTV_NULL input, so the two operator-resolution paths stay indistinguishable. List aggregation re-packs SQL NULL entries as AGTV_NULL, so[x IN [1, null, 2] | x + 1]now renders as[2, null, 3].