Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ REGRESS = scan \
name_validation \
jsonb_operators \
list_comprehension \
pattern_expression \
map_projection \
direct_field_access \
security
Expand Down Expand Up @@ -150,7 +151,19 @@ src/include/parser/cypher_kwlist_d.h: src/include/parser/cypher_kwlist.h $(GEN_K

src/include/parser/cypher_gram_def.h: src/backend/parser/cypher_gram.c

src/backend/parser/cypher_gram.c: BISONFLAGS += --defines=src/include/parser/cypher_gram_def.h
#
# The Cypher grammar uses GLR mode with a number of inherent shift/reduce
# and reduce/reduce conflicts arising from the ambiguity between
# parenthesized expressions and graph patterns (both start with '(').
# GLR handles these correctly at runtime by forking at the conflict
# point; %dprec annotations resolve cases where both forks succeed.
#
# We suppress the conflict warnings rather than hard-coding a conflict
# budget with %expect / %expect-rr, because the exact counts vary across
# Bison versions and would otherwise make the build fragile across
# distros and future Bison releases.
#
src/backend/parser/cypher_gram.c: BISONFLAGS += --defines=src/include/parser/cypher_gram_def.h -Wno-conflicts-sr -Wno-conflicts-rr

src/backend/parser/cypher_parser.o: src/backend/parser/cypher_gram.c src/include/parser/cypher_gram_def.h
src/backend/parser/cypher_parser.bc: src/backend/parser/cypher_gram.c src/include/parser/cypher_gram_def.h
Expand Down
331 changes: 331 additions & 0 deletions regress/expected/pattern_expression.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
LOAD 'age';
SET search_path TO ag_catalog;
SELECT create_graph('pattern_expr');
NOTICE: graph "pattern_expr" has been created
create_graph
--------------

(1 row)

--
-- Setup test data
--
SELECT * FROM cypher('pattern_expr', $$
CREATE (alice:Person {name: 'Alice'})-[:KNOWS]->(bob:Person {name: 'Bob'}),
(alice)-[:WORKS_WITH]->(charlie:Person {name: 'Charlie'}),
(dave:Person {name: 'Dave'})
$$) AS (result agtype);
result
--------
(0 rows)

--
-- Basic pattern expression in WHERE
--
-- Bare pattern: (a)-[:REL]->(b)
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person), (b:Person)
WHERE (a)-[:KNOWS]->(b)
RETURN a.name, b.name
ORDER BY a.name, b.name
$$) AS (a agtype, b agtype);
a | b
---------+-------
"Alice" | "Bob"
(1 row)

--
-- NOT pattern expression
--
-- Find people who don't KNOW anyone
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
WHERE NOT (a)-[:KNOWS]->(:Person)
RETURN a.name
ORDER BY a.name
$$) AS (result agtype);
result
-----------
"Bob"
"Charlie"
"Dave"
(3 rows)

--
-- Pattern with labeled first node
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person), (b:Person)
WHERE (a:Person)-[:KNOWS]->(b)
RETURN a.name, b.name
ORDER BY a.name
$$) AS (a agtype, b agtype);
a | b
---------+-------
"Alice" | "Bob"
(1 row)

--
-- Pattern combined with AND
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person), (b:Person)
WHERE (a)-[:KNOWS]->(b) AND a.name = 'Alice'
RETURN a.name, b.name
$$) AS (a agtype, b agtype);
a | b
---------+-------
"Alice" | "Bob"
(1 row)

--
-- Pattern combined with OR
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person), (b:Person)
WHERE (a)-[:KNOWS]->(b) OR (a)-[:WORKS_WITH]->(b)
RETURN a.name, b.name
ORDER BY a.name, b.name
$$) AS (a agtype, b agtype);
a | b
---------+-----------
"Alice" | "Bob"
"Alice" | "Charlie"
(2 rows)

--
-- Left-directed pattern
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person), (b:Person)
WHERE (a)<-[:KNOWS]-(b)
RETURN a.name, b.name
ORDER BY a.name
$$) AS (a agtype, b agtype);
a | b
-------+---------
"Bob" | "Alice"
(1 row)

--
-- Pattern with anonymous nodes
--
-- Find anyone who has any outgoing KNOWS relationship
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
WHERE (a)-[:KNOWS]->()
RETURN a.name
ORDER BY a.name
$$) AS (result agtype);
result
---------
"Alice"
(1 row)

--
-- Multiple relationship pattern
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person), (c:Person)
WHERE (a)-[:KNOWS]->()-[:WORKS_WITH]->(c)
RETURN a.name, c.name
ORDER BY a.name
$$) AS (a agtype, c agtype);
a | c
---+---
(0 rows)

--
-- Existing EXISTS() syntax still works (backward compatibility)
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person), (b:Person)
WHERE EXISTS((a)-[:KNOWS]->(b))
RETURN a.name, b.name
ORDER BY a.name
$$) AS (a agtype, b agtype);
a | b
---------+-------
"Alice" | "Bob"
(1 row)

--
-- Pattern expression produces same results as EXISTS()
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
WHERE (a)-[:KNOWS]->(:Person)
RETURN a.name
ORDER BY a.name
$$) AS (result agtype);
result
---------
"Alice"
(1 row)

SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
WHERE EXISTS((a)-[:KNOWS]->(:Person))
RETURN a.name
ORDER BY a.name
$$) AS (result agtype);
result
---------
"Alice"
(1 row)

--
-- Regular (non-pattern) expressions still work (no regression)
--
SELECT * FROM cypher('pattern_expr', $$
RETURN (1 + 2)
$$) AS (result agtype);
result
--------
3
(1 row)

SELECT * FROM cypher('pattern_expr', $$
MATCH (n:Person)
WHERE n.name = 'Alice'
RETURN (n.name)
$$) AS (result agtype);
result
---------
"Alice"
(1 row)

--
-- Pattern expressions in RETURN (boolean projection)
--
-- Each person gets a column showing whether they know someone
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
RETURN a.name, (a)-[:KNOWS]->(:Person) AS knows_someone
ORDER BY a.name
$$) AS (name agtype, knows_someone agtype);
name | knows_someone
-----------+---------------
"Alice" | true
"Bob" | false
"Charlie" | false
"Dave" | false
(4 rows)

-- Mix pattern expression with other projections
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
RETURN a.name, (a)-[:KNOWS]->(:Person), (a)-[:WORKS_WITH]->(:Person)
ORDER BY a.name
$$) AS (name agtype, knows agtype, works_with agtype);
name | knows | works_with
-----------+-------+------------
"Alice" | true | true
"Bob" | false | false
"Charlie" | false | false
"Dave" | false | false
(4 rows)

--
-- Pattern expressions in CASE WHEN
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
RETURN a.name,
CASE WHEN (a)-[:KNOWS]->(:Person) THEN 'social'
ELSE 'loner'
END
ORDER BY a.name
$$) AS (name agtype, kind agtype);
name | kind
-----------+----------
"Alice" | "social"
"Bob" | "loner"
"Charlie" | "loner"
"Dave" | "loner"
(4 rows)

--
-- Pattern expressions combined with boolean operators in RETURN
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
RETURN a.name,
(a)-[:KNOWS]->(:Person) AND (a)-[:WORKS_WITH]->(:Person) AS has_both,
(a)-[:KNOWS]->(:Person) OR (a)-[:WORKS_WITH]->(:Person) AS has_either
ORDER BY a.name
$$) AS (name agtype, has_both agtype, has_either agtype);
name | has_both | has_either
-----------+----------+------------
"Alice" | true | true
"Bob" | false | false
"Charlie" | false | false
"Dave" | false | false
(4 rows)

--
-- Pattern expression in SET (store boolean as property)
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
SET a.is_social = (a)-[:KNOWS]->(:Person)
RETURN a.name, a.is_social
ORDER BY a.name
$$) AS (name agtype, is_social agtype);
name | is_social
-----------+-----------
"Alice" | true
"Bob" | false
"Charlie" | false
"Dave" | false
(4 rows)

--
-- Pattern expression in WITH (carry boolean through pipeline)
--
SELECT * FROM cypher('pattern_expr', $$
MATCH (a:Person)
WITH a.name AS name, (a)-[:KNOWS]->(:Person) AS knows
WHERE knows
RETURN name
ORDER BY name
$$) AS (result agtype);
result
---------
"Alice"
(1 row)

--
-- Cleanup
--
SELECT * FROM drop_graph('pattern_expr', true);
NOTICE: drop cascades to 5 other objects
DETAIL: drop cascades to table pattern_expr._ag_label_vertex
drop cascades to table pattern_expr._ag_label_edge
drop cascades to table pattern_expr."Person"
drop cascades to table pattern_expr."KNOWS"
drop cascades to table pattern_expr."WORKS_WITH"
NOTICE: graph "pattern_expr" has been dropped
drop_graph
------------

(1 row)

Loading