This is an automated email from the ASF dual-hosted git repository.

mtaha pushed a commit to branch PG17
in repository https://gitbox.apache.org/repos/asf/age.git

commit a1557918282ff324a0f69c1e8e3c8dfff50dc918
Author: John Gemignani <[email protected]>
AuthorDate: Fri Jan 9 12:27:47 2026 -0800

    Fix Issue 2289: handle empty list in IN expression (#2294)
    
    NOTE: This PR was created with AI tools and a human.
    
    When evaluating 'x IN []' with an empty list, the transform_AEXPR_IN
    function would return NULL because no expressions were processed.
    This caused a 'cache lookup failed for type 0' error downstream.
    
    This fix adds an early check for the empty list case:
    - 'x IN []' returns false (nothing can be in an empty list)
    
    Additional NOTE: Cypher does not have 'NOT IN' syntax. To check if
    a value is NOT in a list, use 'NOT (x IN list)'. The NOT operator
    will invert the false from an empty list to true as expected.
    
    The fix returns a boolean constant directly, avoiding the NULL result
    that caused the type lookup failure.
    
    Added regression tests.
    
    modified:   regress/expected/expr.out
    modified:   regress/sql/expr.sql
    modified:   src/backend/parser/cypher_expr.c
---
 regress/expected/expr.out        | 72 ++++++++++++++++++++++++++++++++++++++++
 regress/sql/expr.sql             | 23 +++++++++++++
 src/backend/parser/cypher_expr.c | 30 +++++++++++++++--
 3 files changed, 123 insertions(+), 2 deletions(-)

diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index 926a958d..6d934145 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -319,6 +319,50 @@ $$RETURN 1 IN [[null]]$$) AS r(c boolean);
  f
 (1 row)
 
+-- empty list: x IN [] should always return false
+SELECT * FROM cypher('expr',
+$$RETURN 1 IN []$$) AS r(c boolean);
+ c 
+---
+ f
+(1 row)
+
+SELECT * FROM cypher('expr',
+$$RETURN 'a' IN []$$) AS r(c boolean);
+ c 
+---
+ f
+(1 row)
+
+SELECT * FROM cypher('expr',
+$$RETURN null IN []$$) AS r(c boolean);
+ c 
+---
+ f
+(1 row)
+
+SELECT * FROM cypher('expr',
+$$RETURN [1,2,3] IN []$$) AS r(c boolean);
+ c 
+---
+ f
+(1 row)
+
+-- NOT (x IN []) should always return true
+SELECT * FROM cypher('expr',
+$$RETURN NOT (1 IN [])$$) AS r(c boolean);
+ c 
+---
+ t
+(1 row)
+
+SELECT * FROM cypher('expr',
+$$RETURN NOT ('a' IN [])$$) AS r(c boolean);
+ c 
+---
+ t
+(1 row)
+
 -- should error - ERROR:  object of IN must be a list
 SELECT * FROM cypher('expr',
 $$RETURN null IN 'str' $$) AS r(c boolean);
@@ -9155,9 +9199,37 @@ ERROR:  could not find rte for x
 LINE 2: ...({ a0:COUNT { MATCH () WHERE CASE WHEN true THEN (x IS NULL)...
                                                              ^
 HINT:  variable x does not exist within scope of usage
+--
+-- Issue 2289: 1 IN [] causes cache lookup failed for type 0
+--
+-- Additional test cases were added above to the IN operator
+--
+SELECT * FROM create_graph('issue_2289');
+NOTICE:  graph "issue_2289" has been created
+ create_graph 
+--------------
+ 
+(1 row)
+
+SELECT * FROM cypher('issue_2289', $$ RETURN (1 IN []) AS v $$) AS (v agtype);
+   v   
+-------
+ false
+(1 row)
+
 --
 -- Cleanup
 --
+SELECT * FROM drop_graph('issue_2289', true);
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table issue_2289._ag_label_vertex
+drop cascades to table issue_2289._ag_label_edge
+NOTICE:  graph "issue_2289" has been dropped
+ drop_graph 
+------------
+ 
+(1 row)
+
 SELECT * FROM drop_graph('issue_2263', true);
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to table issue_2263._ag_label_vertex
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index 7bf1f26b..445e2d23 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -157,6 +157,20 @@ SELECT * FROM cypher('expr',
 $$RETURN 1 in [[1]]$$) AS r(c boolean);
 SELECT * FROM cypher('expr',
 $$RETURN 1 IN [[null]]$$) AS r(c boolean);
+-- empty list: x IN [] should always return false
+SELECT * FROM cypher('expr',
+$$RETURN 1 IN []$$) AS r(c boolean);
+SELECT * FROM cypher('expr',
+$$RETURN 'a' IN []$$) AS r(c boolean);
+SELECT * FROM cypher('expr',
+$$RETURN null IN []$$) AS r(c boolean);
+SELECT * FROM cypher('expr',
+$$RETURN [1,2,3] IN []$$) AS r(c boolean);
+-- NOT (x IN []) should always return true
+SELECT * FROM cypher('expr',
+$$RETURN NOT (1 IN [])$$) AS r(c boolean);
+SELECT * FROM cypher('expr',
+$$RETURN NOT ('a' IN [])$$) AS r(c boolean);
 -- should error - ERROR:  object of IN must be a list
 SELECT * FROM cypher('expr',
 $$RETURN null IN 'str' $$) AS r(c boolean);
@@ -3690,9 +3704,18 @@ SELECT * FROM cypher('issue_2263', $$
     CREATE x = (), ({ a0:COUNT { MATCH () WHERE CASE WHEN true THEN (x IS 
NULL) END RETURN 0 } })
 $$) AS (out agtype);
 
+--
+-- Issue 2289: 1 IN [] causes cache lookup failed for type 0
+--
+-- Additional test cases were added above to the IN operator
+--
+SELECT * FROM create_graph('issue_2289');
+SELECT * FROM cypher('issue_2289', $$ RETURN (1 IN []) AS v $$) AS (v agtype);
+
 --
 -- Cleanup
 --
+SELECT * FROM drop_graph('issue_2289', true);
 SELECT * FROM drop_graph('issue_2263', true);
 SELECT * FROM drop_graph('issue_1988', true);
 SELECT * FROM drop_graph('issue_1953', true);
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index 5f4de86b..fc0335de 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -600,6 +600,34 @@ static Node *transform_AEXPR_IN(cypher_parsestate 
*cpstate, A_Expr *a)
 
     Assert(is_ag_node(a->rexpr, cypher_list));
 
+    rexpr = (cypher_list *)a->rexpr;
+
+    /*
+     * Handle empty list case: x IN [] is always false, x NOT IN [] is always 
true.
+     * We need to check this before processing to avoid returning NULL result
+     * which causes "cache lookup failed for type 0" error.
+     */
+    if (rexpr->elems == NIL || list_length((List *)rexpr->elems) == 0)
+    {
+        Datum bool_value;
+        Const *const_result;
+
+        /* If operator is <> (NOT IN), result is true; otherwise (IN) result 
is false */
+        if (strcmp(strVal(linitial(a->name)), "<>") == 0)
+        {
+            bool_value = BoolGetDatum(true);
+        }
+        else
+        {
+            bool_value = BoolGetDatum(false);
+        }
+
+        const_result = makeConst(BOOLOID, -1, InvalidOid, sizeof(bool),
+                                 bool_value, false, true);
+
+        return (Node *)const_result;
+    }
+
     /* If the operator is <>, combine with AND not OR. */
     if (strcmp(strVal(linitial(a->name)), "<>") == 0)
     {
@@ -614,8 +642,6 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, 
A_Expr *a)
 
     rexprs = rvars = rnonvars = NIL;
 
-    rexpr = (cypher_list *)a->rexpr;
-
     foreach(l, (List *) rexpr->elems)
     {
         Node *rexpr = transform_cypher_expr_recurse(cpstate, lfirst(l));

Reply via email to