This is an automated email from the ASF dual-hosted git repository.
mtaha pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/master by this push:
new 2e8f7ab9 Fix Issue 2289: handle empty list in IN expression (#2294)
2e8f7ab9 is described below
commit 2e8f7ab992fc5db6cedf88cbdffc15df3a3cf932
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));