diff --git a/regress/expected/list_comprehension.out b/regress/expected/list_comprehension.out index e12ad621d..e431c0d9c 100644 --- a/regress/expected/list_comprehension.out +++ b/regress/expected/list_comprehension.out @@ -503,6 +503,31 @@ SELECT * FROM cypher('list_comprehension', $$ CREATE n=()-[:edge]->() RETURN [n [{"id": 281474976710664, "label": "", "properties": {}}::vertex, {"id": 281474976710665, "label": "", "properties": {}}::vertex] (1 row) +-- Property access on the list-comprehension loop variable +SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [{name:'a'}, {name:'b'}] | x.name] $$) AS (result agtype); + result +------------ + ["a", "b"] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [{n:1}, {n:2}, {n:3}] WHERE x.n > 1 | x.n] $$) AS (result agtype); + result +-------- + [2, 3] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH p=()-[:edge]->() RETURN [n IN nodes(p) | n.name] $$) AS (result agtype); + result +-------------- + [null, null] +(1 row) + +SELECT * FROM cypher('list_comprehension', $$ MATCH (u:csm_match) WITH collect(u) AS ns RETURN [x IN ns | x.list] $$) AS (result agtype); + result +------------------------- + [["abc", "def", "ghi"]] +(1 row) + -- Multiple list comprehensions in RETURN and WITH clause SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN [1,2,3]] $$) AS (result agtype, result2 agtype); result | result2 diff --git a/regress/expected/predicate_functions.out b/regress/expected/predicate_functions.out index 47226453d..9ee673a4e 100644 --- a/regress/expected/predicate_functions.out +++ b/regress/expected/predicate_functions.out @@ -351,6 +351,154 @@ $$) AS (result agtype); "even" (1 row) +-- +-- Property access on the loop variable +-- +-- any: true (2 > 1) / false (none > 5) +SELECT * FROM cypher('predicate_functions', $$ + RETURN any(x IN [{n: 1}, {n: 2}] WHERE x.n > 1) +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + RETURN any(x IN [{n: 1}, {n: 2}] WHERE x.n > 5) +$$) AS (result agtype); + result +-------- + false +(1 row) + +-- all: true (both > 0) / false (not all > 1) +SELECT * FROM cypher('predicate_functions', $$ + RETURN all(x IN [{n: 1}, {n: 2}] WHERE x.n > 0) +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + RETURN all(x IN [{n: 1}, {n: 2}] WHERE x.n > 1) +$$) AS (result agtype); + result +-------- + false +(1 row) + +-- none: true (neither > 2) / false (one matches) +SELECT * FROM cypher('predicate_functions', $$ + RETURN none(x IN [{n: 1}, {n: 2}] WHERE x.n > 2) +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + RETURN none(x IN [{n: 1}, {n: 2}] WHERE x.n = 1) +$$) AS (result agtype); + result +-------- + false +(1 row) + +-- single: true (exactly one) / false (both match) +SELECT * FROM cypher('predicate_functions', $$ + RETURN single(x IN [{n: 1}, {n: 2}] WHERE x.n = 1) +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + RETURN single(x IN [{n: 1}, {n: 2}] WHERE x.n > 0) +$$) AS (result agtype); + result +-------- + false +(1 row) + +-- Property access on vertex loop variables over a collected node list +-- any: true ('even' exists) / false (no 'missing') +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN any(x IN ns WHERE x.name = 'even') +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN any(x IN ns WHERE x.name = 'missing') +$$) AS (result agtype); + result +-------- + false +(1 row) + +-- all: true (all have non-empty vals) / false (not all named 'even') +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN all(x IN ns WHERE size(x.vals) > 0) +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN all(x IN ns WHERE x.name = 'even') +$$) AS (result agtype); + result +-------- + false +(1 row) + +-- none: true (none 'missing') / false ('even' matches) +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN none(x IN ns WHERE x.name = 'missing') +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN none(x IN ns WHERE x.name = 'even') +$$) AS (result agtype); + result +-------- + false +(1 row) + +-- single: true (only one 'odd') / false (all have non-empty vals) +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN single(x IN ns WHERE x.name = 'odd') +$$) AS (result agtype); + result +-------- + true +(1 row) + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN single(x IN ns WHERE size(x.vals) > 0) +$$) AS (result agtype); + result +-------- + false +(1 row) + -- -- Predicate functions in boolean expressions -- diff --git a/regress/sql/list_comprehension.sql b/regress/sql/list_comprehension.sql index 572b2e6bb..0ef07c974 100644 --- a/regress/sql/list_comprehension.sql +++ b/regress/sql/list_comprehension.sql @@ -123,6 +123,12 @@ SELECT * FROM cypher('list_comprehension', $$ WITH 1 AS m RETURN [m IN [1, 2, 3] SELECT * FROM cypher('list_comprehension', $$ WITH [m IN [1,2,3]] AS m RETURN [m IN [1, 2, 3]], m $$) AS (result agtype, result2 agtype); SELECT * FROM cypher('list_comprehension', $$ CREATE n=()-[:edge]->() RETURN [n IN nodes(n)] $$) AS (u agtype); +-- Property access on the list-comprehension loop variable +SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [{name:'a'}, {name:'b'}] | x.name] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ RETURN [x IN [{n:1}, {n:2}, {n:3}] WHERE x.n > 1 | x.n] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH p=()-[:edge]->() RETURN [n IN nodes(p) | n.name] $$) AS (result agtype); +SELECT * FROM cypher('list_comprehension', $$ MATCH (u:csm_match) WITH collect(u) AS ns RETURN [x IN ns | x.list] $$) AS (result agtype); + -- Multiple list comprehensions in RETURN and WITH clause SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3]], [u IN [1,2,3]] $$) AS (result agtype, result2 agtype); SELECT * FROM cypher('list_comprehension', $$ RETURN [u IN [1,2,3] WHERE u>1], [u IN [1,2,3] WHERE u>2] $$) AS (result agtype, result2 agtype); diff --git a/regress/sql/predicate_functions.sql b/regress/sql/predicate_functions.sql index 7466cc2a4..82933f87a 100644 --- a/regress/sql/predicate_functions.sql +++ b/regress/sql/predicate_functions.sql @@ -217,6 +217,90 @@ SELECT * FROM cypher('predicate_functions', $$ ORDER BY u.name $$) AS (result agtype); +-- +-- Property access on the loop variable +-- +-- any: true (2 > 1) / false (none > 5) +SELECT * FROM cypher('predicate_functions', $$ + RETURN any(x IN [{n: 1}, {n: 2}] WHERE x.n > 1) +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + RETURN any(x IN [{n: 1}, {n: 2}] WHERE x.n > 5) +$$) AS (result agtype); + +-- all: true (both > 0) / false (not all > 1) +SELECT * FROM cypher('predicate_functions', $$ + RETURN all(x IN [{n: 1}, {n: 2}] WHERE x.n > 0) +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + RETURN all(x IN [{n: 1}, {n: 2}] WHERE x.n > 1) +$$) AS (result agtype); + +-- none: true (neither > 2) / false (one matches) +SELECT * FROM cypher('predicate_functions', $$ + RETURN none(x IN [{n: 1}, {n: 2}] WHERE x.n > 2) +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + RETURN none(x IN [{n: 1}, {n: 2}] WHERE x.n = 1) +$$) AS (result agtype); + +-- single: true (exactly one) / false (both match) +SELECT * FROM cypher('predicate_functions', $$ + RETURN single(x IN [{n: 1}, {n: 2}] WHERE x.n = 1) +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + RETURN single(x IN [{n: 1}, {n: 2}] WHERE x.n > 0) +$$) AS (result agtype); + +-- Property access on vertex loop variables over a collected node list +-- any: true ('even' exists) / false (no 'missing') +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN any(x IN ns WHERE x.name = 'even') +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN any(x IN ns WHERE x.name = 'missing') +$$) AS (result agtype); + +-- all: true (all have non-empty vals) / false (not all named 'even') +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN all(x IN ns WHERE size(x.vals) > 0) +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN all(x IN ns WHERE x.name = 'even') +$$) AS (result agtype); + +-- none: true (none 'missing') / false ('even' matches) +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN none(x IN ns WHERE x.name = 'missing') +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN none(x IN ns WHERE x.name = 'even') +$$) AS (result agtype); + +-- single: true (only one 'odd') / false (all have non-empty vals) +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN single(x IN ns WHERE x.name = 'odd') +$$) AS (result agtype); + +SELECT * FROM cypher('predicate_functions', $$ + MATCH (u) WITH collect(u) AS ns + RETURN single(x IN ns WHERE size(x.vals) > 0) +$$) AS (result agtype); + -- -- Predicate functions in boolean expressions -- diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index fc0335def..1ed777486 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -1326,21 +1326,14 @@ static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, } /* find the properties column of the NSI and return a var for it */ - node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", + node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", cr->location); /* - * Error out if we couldn't find it. - * - * TODO: Should we error out or return NULL for further processing? - * For now, just error out. + * If there's no "properties" column, continue transforming the + * ColumnRef as an agtype value and try to apply the indirection via + * agtype_access_operator. */ - if (!node) - { - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find properties for %s", relname))); - } - return node; }