Skip to content

Commit 4f9db37

Browse files
gregfeliceclaude
andauthored
Implement predicate functions: all(), any(), none(), single() (apache#2359)
* Implement predicate functions: all(), any(), none(), single() Implement the four openCypher predicate functions (issues apache#552, apache#553, apache#555, apache#556) that test list elements against a predicate: all(x IN list WHERE predicate) -- true if all elements match any(x IN list WHERE predicate) -- true if at least one matches none(x IN list WHERE predicate) -- true if no elements match single(x IN list WHERE predicate) -- true if exactly one matches Implementation approach: - Add cypher_predicate_function node type with CPFK_ALL/ANY/NONE/SINGLE kind enum, reusing the list comprehension's unnest-based transformation - Grammar rules in expr_func_subexpr (alongside EXISTS, COALESCE, COUNT) - Transform to efficient SQL sublinks: all() -> NOT EXISTS (SELECT 1 FROM unnest WHERE NOT pred) any() -> EXISTS (SELECT 1 FROM unnest WHERE pred) none() -> NOT EXISTS (SELECT 1 FROM unnest WHERE pred) single() -> (SELECT count(*) FROM unnest WHERE pred) = 1 - Three new keywords (ANY_P, NONE, SINGLE) added to safe_keywords for backward compatibility as property keys and label names - Shared extract_iter_variable_name() helper for variable validation All 32 regression tests pass. New predicate_functions test covers basic semantics, empty lists, graph data integration, boolean combinations, nested predicates, and keyword backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address Copilot review: NULL semantics, iterator validation, single() perf, tests - Rewrite predicate functions from EXISTS_SUBLINK to EXPR_SUBLINK with aggregate-based CASE expressions (bool_or + IS TRUE/FALSE/NULL) to preserve three-valued Cypher NULL semantics - Add list_length check in extract_iter_variable_name() to reject qualified names like x.y as iterator variables - Add copy/read support for cypher_predicate_function ExtensibleNode to prevent query rewriter crashes - Use IS TRUE filtering in single() count (LIMIT 2 optimization breaks correlated variable refs in graph contexts -- documented) - Add 13 NULL regression tests: null list input, null elements, null predicates for all four functions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address Copilot round 2: NULL-list guard, single() comment, pg_aggregate.h 1. Add NULL-list guard for all predicate functions (all/any/none/single). Wraps the result with CASE WHEN list IS NULL THEN NULL ELSE <result> END in the grammar layer. This fixes single(x IN null WHERE ...) returning false instead of NULL. The expr pointer is safely shared between the NullTest and the predicate function node because AGE's expression transformer creates new nodes without modifying the parse tree in-place. 2. Fix single() block comment in transform_cypher_predicate_function: described LIMIT 2 optimization but implementation uses plain count(*). Updated comment to match actual implementation. 3. Keep #include "catalog/pg_aggregate.h" -- Copilot suggested removal but AGGKIND_NORMAL macro requires it (build fails without it). Regression test: predicate_functions OK. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address Copilot round 3: reuse extract_iter_variable_name for list comprehensions - Refactor build_list_comprehension_node() to reuse the shared extract_iter_variable_name() helper, so `var IN list` validation is consistent between list comprehensions and predicate functions (all/any/none/single). Qualified ColumnRefs like `x.y IN list` are now rejected in list comprehensions the same way they are in predicate functions. - Update list_comprehension expected output for the normalized lowercase "syntax error at or near IN" message. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 15030a0 commit 4f9db37

17 files changed

Lines changed: 1272 additions & 18 deletions

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ REGRESS = scan \
174174
name_validation \
175175
jsonb_operators \
176176
list_comprehension \
177+
predicate_functions \
177178
map_projection \
178179
direct_field_access \
179180
security

regress/expected/list_comprehension.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,11 +577,11 @@ LINE 1: ...$$ RETURN [i IN range(0, 10, 2) WHERE i>5 | i^2], i $$) AS (...
577577
HINT: variable i does not exist within scope of usage
578578
-- Invalid list comprehension
579579
SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) WHERE 2>5] $$) AS (result agtype);
580-
ERROR: Syntax error at or near IN
580+
ERROR: syntax error at or near IN
581581
LINE 1: SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN r...
582582
^
583583
SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN range(0, 10, 2) | 1] $$) AS (result agtype);
584-
ERROR: Syntax error at or near IN
584+
ERROR: syntax error at or near IN
585585
LINE 1: SELECT * FROM cypher('list_comprehension', $$ RETURN [1 IN r...
586586
^
587587
-- Issue - error using list comprehension with WITH *

0 commit comments

Comments
 (0)