You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by jo...@apache.org on 2021/03/11 23:12:17 UTC
[incubator-age] branch master updated: Implement DELETE clause
This is an automated email from the ASF dual-hosted git repository.
joshinnis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git
The following commit(s) were added to refs/heads/master by this push:
new 87ae8fe Implement DELETE clause
87ae8fe is described below
commit 87ae8fe83e059820c0d384b4747371cf58cf447b
Author: Josh Innis <Jo...@gmail.com>
AuthorDate: Thu Mar 11 15:10:32 2021 -0800
Implement DELETE clause
Implemented the DELETE clause and fixed an error with how
CREATE, SET, and REMOVE interacted with Postgres' transaction
system.
---
Makefile | 2 +
age--0.3.0.sql | 5 +
regress/expected/cypher_delete.out | 460 +++++++++++++++++++++++++++
regress/expected/cypher_set.out | 51 ++-
regress/sql/cypher_delete.sql | 180 +++++++++++
src/backend/catalog/ag_label.c | 62 ++++
src/backend/commands/graph_commands.c | 1 -
src/backend/executor/cypher_create.c | 97 ++++--
src/backend/executor/cypher_delete.c | 496 ++++++++++++++++++++++++++++++
src/backend/executor/cypher_set.c | 133 ++++----
src/backend/executor/cypher_utils.c | 86 ++++--
src/backend/optimizer/cypher_createplan.c | 64 ++++
src/backend/optimizer/cypher_pathnode.c | 50 ++-
src/backend/optimizer/cypher_paths.c | 47 ++-
src/backend/parser/cypher_clause.c | 133 +++++++-
src/backend/parser/cypher_gram.y | 5 +-
src/backend/utils/adt/cypher_funcs.c | 7 +
src/include/catalog/ag_label.h | 4 +
src/include/executor/cypher_executor.h | 7 +
src/include/executor/cypher_utils.h | 35 ++-
src/include/nodes/cypher_nodes.h | 17 +
src/include/optimizer/cypher_createplan.h | 4 +
src/include/optimizer/cypher_pathnode.h | 6 +
src/include/utils/ag_func.h | 4 +
src/include/utils/graphid.h | 5 +
25 files changed, 1809 insertions(+), 152 deletions(-)
diff --git a/Makefile b/Makefile
index 9f166fb..245f0f7 100644
--- a/Makefile
+++ b/Makefile
@@ -35,6 +35,7 @@ OBJS = src/backend/age.o \
src/backend/parser/ag_scanner.o \
src/backend/parser/cypher_analyze.o \
src/backend/parser/cypher_clause.o \
+ src/backend/executor/cypher_delete.o \
src/backend/parser/cypher_expr.o \
src/backend/parser/cypher_gram.o \
src/backend/parser/cypher_item.o \
@@ -68,6 +69,7 @@ REGRESS = scan \
cypher_match \
cypher_set \
cypher_remove \
+ cypher_delete \
cypher_with \
drop
diff --git a/age--0.3.0.sql b/age--0.3.0.sql
index 21b4211..7460416 100644
--- a/age--0.3.0.sql
+++ b/age--0.3.0.sql
@@ -887,6 +887,11 @@ RETURNS void
LANGUAGE c
AS 'MODULE_PATHNAME';
+CREATE FUNCTION ag_catalog._cypher_delete_clause(internal)
+RETURNS void
+LANGUAGE c
+AS 'MODULE_PATHNAME';
+
--
-- query functions
--
diff --git a/regress/expected/cypher_delete.out b/regress/expected/cypher_delete.out
new file mode 100644
index 0000000..8d25912
--- /dev/null
+++ b/regress/expected/cypher_delete.out
@@ -0,0 +1,460 @@
+/*
+ * 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 drop_graph('cypher_delete', true);
+ERROR: graph "cypher_delete" does not exist
+SELECT create_graph('cypher_delete');
+NOTICE: graph "cypher_delete" has been created
+ create_graph
+--------------
+
+(1 row)
+
+--Test 1: Delete Vertices
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 0, j: 5, a: 0})$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 1})$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+ a
+---------------------------------------------------------------------------------------
+ {"id": 844424930131969, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 0, "j": 5}}::vertex
+ {"id": 844424930131971, "label": "v", "properties": {"i": 1}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 2: Delete Edges
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Should Fail
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DELETE n1 RETURN n1$$) AS (a agtype);
+ERROR: Cannot delete vertex n1, because it still has edges attached. To delete this vertex, you must first delete the attached edges.
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DELETE n2 RETURN n2$$) AS (a agtype);
+ERROR: Cannot delete vertex n2, because it still has edges attached. To delete this vertex, you must first delete the attached edges.
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DELETE e RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842625, "label": "e", "end_id": 844424930131973, "start_id": 844424930131972, "properties": {}}::edge
+(1 row)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH()-[e]->() DELETE e RETURN e$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131972, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131973, "label": "v", "properties": {}}::vertex
+(2 rows)
+
+--Test 4: DETACH DELECT a vertex
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1 RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842626, "label": "e", "end_id": 844424930131975, "start_id": 844424930131974, "properties": {}}::edge
+(1 row)
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131975, "label": "v", "properties": {}}::vertex
+(1 row)
+
+--Test 4: DETACH DELECT two vertices tied to the same edge
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842627, "label": "e", "end_id": 844424930131977, "start_id": 844424930131976, "properties": {}}::edge
+(1 row)
+
+--Test 4: DETACH DELECT a vertex
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842628, "label": "e", "end_id": 844424930131979, "start_id": 844424930131978, "properties": {}}::edge
+(1 row)
+
+--Test 5: DETACH DELETE a vertex that points to itself
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DETACH DELETE n1 RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842629, "label": "e", "end_id": 844424930131980, "start_id": 844424930131980, "properties": {}}::edge
+(1 row)
+
+--Test 6: DETACH Delete a vertex twice
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842630, "label": "e", "end_id": 844424930131981, "start_id": 844424930131981, "properties": {}}::edge
+(1 row)
+
+--Test 7: Test the SET Clause on DELETED node
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 0, j: 5, a: 0})$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 1})$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n SET n.lol = 'ftw' RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------------------------------------------
+ {"id": 844424930131975, "label": "v", "properties": {"lol": "ftw"}}::vertex
+ {"id": 844424930131982, "label": "v", "properties": {"lol": "ftw"}}::vertex
+ {"id": 844424930131983, "label": "v", "properties": {"a": 0, "i": 0, "j": 5, "lol": "ftw"}}::vertex
+ {"id": 844424930131984, "label": "v", "properties": {"i": 1, "lol": "ftw"}}::vertex
+(4 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 8:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v) CREATE (n)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[]->() DELETE n1 RETURN n1$$) AS (a agtype);
+ERROR: Cannot delete vertex n1, because it still has edges attached. To delete this vertex, you must first delete the attached edges.
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131985, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131986, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131987, "label": "v", "properties": {}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 9:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DELETE e, n1 RETURN n1$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131988, "label": "v", "properties": {}}::vertex
+(1 row)
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131989, "label": "v", "properties": {}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 10:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DELETE n1, e RETURN n1$$) AS (a agtype);
+ERROR: Cannot delete vertex n1, because it still has edges attached. To delete this vertex, you must first delete the attached edges.
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131990, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131991, "label": "v", "properties": {}}::vertex
+(2 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 11: Delete a vertex twice
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v), (n)-[:e]->(:v) RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131992, "label": "v", "properties": {}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DETACH DELETE n1 RETURN n1, e$$) AS (a agtype, b agtype);
+ a | b
+-----------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------
+ {"id": 844424930131992, "label": "v", "properties": {}}::vertex | {"id": 1125899906842635, "label": "e", "end_id": 844424930131993, "start_id": 844424930131992, "properties": {}}::edge
+ {"id": 844424930131992, "label": "v", "properties": {}}::vertex | {"id": 1125899906842636, "label": "e", "end_id": 844424930131994, "start_id": 844424930131992, "properties": {}}::edge
+(2 rows)
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131993, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131994, "label": "v", "properties": {}}::vertex
+(2 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 12:
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->() DETACH DELETE n CREATE (n)-[:e2]->(:v) RETURN e$$) AS (a agtype);
+ERROR: vertex assigned to variable n was deleted
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131995, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131996, "label": "v", "properties": {}}::vertex
+(2 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 13:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e$$) AS (a agtype);
+ERROR: vertex assigned to variable m was deleted
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131997, "label": "v", "properties": {}}::vertex
+(1 row)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 14:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e$$) AS (a agtype);
+ERROR: vertex assigned to variable m was deleted
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930131998, "label": "v", "properties": {}}::vertex
+ {"id": 844424930131999, "label": "v", "properties": {}}::vertex
+ {"id": 844424930132000, "label": "v", "properties": {}}::vertex
+(3 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 15:
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) SET n.i = 0 DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------------
+ {"id": 844424930132001, "label": "v", "properties": {"i": 0}}::vertex
+(1 row)
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 16:
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n SET n.i = 0 RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------------
+ {"id": 844424930132002, "label": "v", "properties": {"i": 0}}::vertex
+(1 row)
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+--Test 17:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) DETACH DELETE n SET e.i = 1 RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842641, "label": "e", "end_id": 844424930132004, "start_id": 844424930132003, "properties": {"i": 1}}::edge
+(1 row)
+
+--Cleanup
+--Note: Expect 1 vertex
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930132004, "label": "v", "properties": {}}::vertex
+(1 row)
+
+--Test 18:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) SET e.i = 1 DETACH DELETE n RETURN e$$) AS (a agtype);
+ a
+------------------------------------------------------------------------------------------------------------------------------
+ {"id": 1125899906842642, "label": "e", "end_id": 844424930132006, "start_id": 844424930132005, "properties": {"i": 1}}::edge
+(1 row)
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930132006, "label": "v", "properties": {}}::vertex
+(1 row)
+
+--Test 19:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)$$) AS (a agtype);
+ a
+---
+(0 rows)
+
+SELECT * FROM cypher('cypher_delete', $$MATCH (n) DELETE n CREATE (n)-[:e]->(:v) RETURN n$$) AS (a agtype);
+ERROR: vertex assigned to variable n was deleted
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+ a
+-----------------------------------------------------------------
+ {"id": 844424930132007, "label": "v", "properties": {}}::vertex
+(1 row)
+
+--Test 20 Undefined Reference:
+SELECT * FROM cypher('cypher_delete', $$MATCH (n) DELETE m RETURN n$$) AS (a agtype);
+ERROR: undefined reference to variable m in DELETE clause
+LINE 1: ...* FROM cypher('cypher_delete', $$MATCH (n) DELETE m RETURN n...
+ ^
+--
+-- Clean up
+--
+SELECT drop_graph('cypher_delete', true);
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to table cypher_delete._ag_label_vertex
+drop cascades to table cypher_delete._ag_label_edge
+drop cascades to table cypher_delete.v
+drop cascades to table cypher_delete.e
+NOTICE: graph "cypher_delete" has been dropped
+ drop_graph
+------------
+
+(1 row)
+
+--
+-- End
+--
diff --git a/regress/expected/cypher_set.out b/regress/expected/cypher_set.out
index 2cfde99..40d4e63 100644
--- a/regress/expected/cypher_set.out
+++ b/regress/expected/cypher_set.out
@@ -124,51 +124,35 @@ SELECT * FROM cypher('cypher_set', $$MATCH (n) CREATE (n)-[:e {j:20}]->(:other_v
{"id": 844424930131969, "label": "v", "properties": {"i": 3}}::vertex
{"id": 844424930131971, "label": "v", "properties": {"i": 3}}::vertex
{"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 3, "j": 5}}::vertex
- {"id": 1407374883553281, "label": "other_v", "properties": {"k": 10}}::vertex
- {"id": 1407374883553282, "label": "other_v", "properties": {"k": 10}}::vertex
- {"id": 1407374883553283, "label": "other_v", "properties": {"k": 10}}::vertex
- {"id": 1407374883553284, "label": "other_v", "properties": {"k": 10}}::vertex
-(8 rows)
+(4 rows)
SELECT * FROM cypher('cypher_set', $$MATCH p=(n)-[]->() SET n.i = 50 RETURN p$$) AS (a agtype);
- a
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ a
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[{"id": 281474976710657, "label": "", "properties": {"i": 50}}::vertex, {"id": 1125899906842625, "label": "e", "end_id": 1407374883553281, "start_id": 281474976710657, "properties": {"j": 20}}::edge, {"id": 1407374883553281, "label": "other_v", "properties": {"k": 10}}::vertex]::path
[{"id": 844424930131969, "label": "v", "properties": {"i": 50}}::vertex, {"id": 1125899906842626, "label": "e", "end_id": 1407374883553282, "start_id": 844424930131969, "properties": {"j": 20}}::edge, {"id": 1407374883553282, "label": "other_v", "properties": {"k": 10}}::vertex]::path
[{"id": 844424930131971, "label": "v", "properties": {"i": 50}}::vertex, {"id": 1125899906842627, "label": "e", "end_id": 1407374883553283, "start_id": 844424930131971, "properties": {"j": 20}}::edge, {"id": 1407374883553283, "label": "other_v", "properties": {"k": 10}}::vertex]::path
[{"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5}}::vertex, {"id": 1125899906842628, "label": "e", "end_id": 1407374883553284, "start_id": 844424930131970, "properties": {"j": 20}}::edge, {"id": 1407374883553284, "label": "other_v", "properties": {"k": 10}}::vertex]::path
- [{"id": 1407374883553281, "label": "other_v", "properties": {"i": 50, "k": 10}}::vertex, {"id": 1125899906842629, "label": "e", "end_id": 1407374883553285, "start_id": 1407374883553281, "properties": {"j": 20}}::edge, {"id": 1407374883553285, "label": "other_v", "properties": {"k": 10}}::vertex]::path
- [{"id": 1407374883553282, "label": "other_v", "properties": {"i": 50, "k": 10}}::vertex, {"id": 1125899906842630, "label": "e", "end_id": 1407374883553286, "start_id": 1407374883553282, "properties": {"j": 20}}::edge, {"id": 1407374883553286, "label": "other_v", "properties": {"k": 10}}::vertex]::path
- [{"id": 1407374883553283, "label": "other_v", "properties": {"i": 50, "k": 10}}::vertex, {"id": 1125899906842631, "label": "e", "end_id": 1407374883553287, "start_id": 1407374883553283, "properties": {"j": 20}}::edge, {"id": 1407374883553287, "label": "other_v", "properties": {"k": 10}}::vertex]::path
- [{"id": 1407374883553284, "label": "other_v", "properties": {"i": 50, "k": 10}}::vertex, {"id": 1125899906842632, "label": "e", "end_id": 1407374883553288, "start_id": 1407374883553284, "properties": {"j": 20}}::edge, {"id": 1407374883553288, "label": "other_v", "properties": {"k": 10}}::vertex]::path
-(8 rows)
+(4 rows)
--Edges
SELECT * FROM cypher('cypher_set', $$MATCH ()-[n]-(:other_v) SET n.i = 3 RETURN n$$) AS (a agtype);
- a
------------------------------------------------------------------------------------------------------------------------------------------
+ a
+----------------------------------------------------------------------------------------------------------------------------------------
{"id": 1125899906842625, "label": "e", "end_id": 1407374883553281, "start_id": 281474976710657, "properties": {"i": 3, "j": 20}}::edge
{"id": 1125899906842626, "label": "e", "end_id": 1407374883553282, "start_id": 844424930131969, "properties": {"i": 3, "j": 20}}::edge
{"id": 1125899906842627, "label": "e", "end_id": 1407374883553283, "start_id": 844424930131971, "properties": {"i": 3, "j": 20}}::edge
{"id": 1125899906842628, "label": "e", "end_id": 1407374883553284, "start_id": 844424930131970, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842629, "label": "e", "end_id": 1407374883553285, "start_id": 1407374883553281, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842630, "label": "e", "end_id": 1407374883553286, "start_id": 1407374883553282, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842631, "label": "e", "end_id": 1407374883553287, "start_id": 1407374883553283, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842632, "label": "e", "end_id": 1407374883553288, "start_id": 1407374883553284, "properties": {"i": 3, "j": 20}}::edge
-(8 rows)
+(4 rows)
SELECT * FROM cypher('cypher_set', $$MATCH ()-[n]->(:other_v) RETURN n$$) AS (a agtype);
- a
------------------------------------------------------------------------------------------------------------------------------------------
+ a
+----------------------------------------------------------------------------------------------------------------------------------------
{"id": 1125899906842625, "label": "e", "end_id": 1407374883553281, "start_id": 281474976710657, "properties": {"i": 3, "j": 20}}::edge
{"id": 1125899906842626, "label": "e", "end_id": 1407374883553282, "start_id": 844424930131969, "properties": {"i": 3, "j": 20}}::edge
{"id": 1125899906842627, "label": "e", "end_id": 1407374883553283, "start_id": 844424930131971, "properties": {"i": 3, "j": 20}}::edge
{"id": 1125899906842628, "label": "e", "end_id": 1407374883553284, "start_id": 844424930131970, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842629, "label": "e", "end_id": 1407374883553285, "start_id": 1407374883553281, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842630, "label": "e", "end_id": 1407374883553286, "start_id": 1407374883553282, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842631, "label": "e", "end_id": 1407374883553287, "start_id": 1407374883553283, "properties": {"i": 3, "j": 20}}::edge
- {"id": 1125899906842632, "label": "e", "end_id": 1407374883553288, "start_id": 1407374883553284, "properties": {"i": 3, "j": 20}}::edge
-(8 rows)
+(4 rows)
SELECT * FROM cypher('cypher_set', $$
MATCH (n {j: 5})
@@ -199,7 +183,7 @@ SELECT * FROM cypher('cypher_set', $$
$$) AS (a agtype, b agtype);
a | b
----------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}}::vertex | [{"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}}::vertex, {"id": 1125899906842633, "label": "e", "end_id": 844424930131970, "start_id": 844424930131970, "properties": {"j": 34}}::edge, {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}}::vertex]::path
+ {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}}::vertex | [{"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}}::vertex, {"id": 1125899906842629, "label": "e", "end_id": 844424930131970, "start_id": 844424930131970, "properties": {"j": 34}}::edge, {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "y": 99, "z": 99}}::vertex]::path
(1 row)
--Create a loop and see that set can work after create
@@ -210,7 +194,7 @@ SELECT * FROM cypher('cypher_set', $$
$$) AS (a agtype);
a
----------------------------------------------------------------------------------------------------------------------------------------
- {"id": 1125899906842634, "label": "e", "end_id": 281474976710659, "start_id": 281474976710658, "properties": {"j": 34, "y": 99}}::edge
+ {"id": 1125899906842630, "label": "e", "end_id": 281474976710659, "start_id": 281474976710658, "properties": {"j": 34, "y": 99}}::edge
(1 row)
SELECT * FROM cypher('cypher_set', $$
@@ -244,7 +228,16 @@ SELECT * FROM cypher('cypher_set', $$MATCH (n)-[]->(n) SET n.y = 99 RETURN n$$)
(1 row)
SELECT * FROM cypher('cypher_set', $$MATCH (n) MATCH (n)-[]->(m) SET n.t = 150 RETURN n$$) AS (a agtype);
-ERROR: Entity could not be locked for updating
+ a
+--------------------------------------------------------------------------------------------------------------------
+ {"id": 281474976710657, "label": "", "properties": {"i": 50, "t": 150}}::vertex
+ {"id": 281474976710658, "label": "", "properties": {"t": 150, "y": 1}}::vertex
+ {"id": 844424930131969, "label": "v", "properties": {"i": 50, "t": 150}}::vertex
+ {"id": 844424930131971, "label": "v", "properties": {"i": 50, "t": 150}}::vertex
+ {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "t": 150, "y": 99, "z": 99}}::vertex
+ {"id": 844424930131970, "label": "v", "properties": {"a": 0, "i": 50, "j": 5, "t": 150, "y": 99, "z": 99}}::vertex
+(6 rows)
+
--Errors
SELECT * FROM cypher('cypher_set', $$SET n.i = NULL$$) AS (a agtype);
ERROR: SET cannot be the first clause in a Cypher query
diff --git a/regress/sql/cypher_delete.sql b/regress/sql/cypher_delete.sql
new file mode 100644
index 0000000..8d3c9ae
--- /dev/null
+++ b/regress/sql/cypher_delete.sql
@@ -0,0 +1,180 @@
+/*
+ * 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 drop_graph('cypher_delete', true);
+SELECT create_graph('cypher_delete');
+
+--Test 1: Delete Vertices
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 0, j: 5, a: 0})$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 1})$$) AS (a agtype);
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 2: Delete Edges
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+
+--Should Fail
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DELETE n1 RETURN n1$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DELETE n2 RETURN n2$$) AS (a agtype);
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DELETE e RETURN e$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH()-[e]->() DELETE e RETURN e$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+
+--Test 4: DETACH DELECT a vertex
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1 RETURN e$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 4: DETACH DELECT two vertices tied to the same edge
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e$$) AS (a agtype);
+
+--Test 4: DETACH DELECT a vertex
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e$$) AS (a agtype);
+
+--Test 5: DETACH DELETE a vertex that points to itself
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DETACH DELETE n1 RETURN e$$) AS (a agtype);
+
+--Test 6: DETACH Delete a vertex twice
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->(n2) DETACH DELETE n1, n2 RETURN e$$) AS (a agtype);
+
+--Test 7: Test the SET Clause on DELETED node
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 0, j: 5, a: 0})$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v {i: 1})$$) AS (a agtype);
+
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n SET n.lol = 'ftw' RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 8:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v) CREATE (n)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[]->() DELETE n1 RETURN n1$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 9:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DELETE e, n1 RETURN n1$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 10:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DELETE n1, e RETURN n1$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 11: Delete a vertex twice
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v), (n)-[:e]->(:v) RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n1)-[e]->() DETACH DELETE n1 RETURN n1, e$$) AS (a agtype, b agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 12:
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->() DETACH DELETE n CREATE (n)-[:e2]->(:v) RETURN e$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 13:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 14:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(n)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) DETACH DELETE n CREATE (m)-[:e2]->(:v) RETURN e$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DETACH DELETE n RETURN n$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 15:
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) SET n.i = 0 DELETE n RETURN n$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 16:
+SELECT * FROM cypher('cypher_delete', $$CREATE (:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n SET n.i = 0 RETURN n$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 17:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) DETACH DELETE n SET e.i = 1 RETURN e$$) AS (a agtype);
+
+--Cleanup
+--Note: Expect 1 vertex
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+
+--Test 18:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)-[:e]->(:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH(n)-[e]->(m) SET e.i = 1 DETACH DELETE n RETURN e$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) DELETE n RETURN n$$) AS (a agtype);
+
+--Test 19:
+SELECT * FROM cypher('cypher_delete', $$CREATE (n:v)$$) AS (a agtype);
+SELECT * FROM cypher('cypher_delete', $$MATCH (n) DELETE n CREATE (n)-[:e]->(:v) RETURN n$$) AS (a agtype);
+
+--Cleanup
+SELECT * FROM cypher('cypher_delete', $$MATCH(n) RETURN n$$) AS (a agtype);
+
+--Test 20 Undefined Reference:
+SELECT * FROM cypher('cypher_delete', $$MATCH (n) DELETE m RETURN n$$) AS (a agtype);
+--
+-- Clean up
+--
+
+SELECT drop_graph('cypher_delete', true);
+
+--
+-- End
+--
diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c
index 2d33853..bd72db3 100644
--- a/src/backend/catalog/ag_label.c
+++ b/src/backend/catalog/ag_label.c
@@ -27,6 +27,7 @@
#include "access/stratnum.h"
#include "catalog/indexing.h"
#include "fmgr.h"
+#include "nodes/execnodes.h"
#include "nodes/makefuncs.h"
#include "storage/lockdefs.h"
#include "utils/builtins.h"
@@ -38,6 +39,7 @@
#include "catalog/ag_graph.h"
#include "catalog/ag_label.h"
#include "commands/label_commands.h"
+#include "executor/cypher_utils.h"
#include "utils/ag_cache.h"
#include "utils/graphid.h"
@@ -258,3 +260,63 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid,
return makeRangeVar(graph_name, relname, -1);
}
+
+/*
+ * Retrieves a list of all the names of a graph.
+ *
+ * XXX: We may want to use the cache system for this function,
+ * however the cache system currently requires us to know the
+ * name of the label we want.
+ */
+List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid)
+{
+ List *labels = NIL;
+ ScanKeyData scan_keys[2];
+ Relation ag_label;
+ HeapScanDesc scan_desc;
+ HeapTuple tuple;
+ TupleTableSlot *slot;
+ ResultRelInfo *resultRelInfo;
+
+ // setup scan keys to get all edges for the given graph oid
+ ScanKeyInit(&scan_keys[1], Anum_ag_label_graph, BTEqualStrategyNumber,
+ F_OIDEQ, ObjectIdGetDatum(graph_oid));
+ ScanKeyInit(&scan_keys[0], Anum_ag_label_kind, BTEqualStrategyNumber,
+ F_CHAREQ, CharGetDatum(LABEL_TYPE_EDGE));
+
+ // setup the table to be scanned
+ ag_label = heap_open(ag_label_relation_id(), RowExclusiveLock);
+ scan_desc = heap_beginscan(ag_label, estate->es_snapshot, 2, scan_keys);
+
+ resultRelInfo = create_entity_result_rel_info(estate, "ag_catalog", "ag_label");
+
+ slot = ExecInitExtraTupleSlot(estate,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc));
+
+ // scan through the results and get all the label names.
+ while(true)
+ {
+ Name label;
+ bool isNull;
+ Datum datum;
+
+ tuple = heap_getnext(scan_desc, ForwardScanDirection);
+
+ // no more labels to process
+ if (!HeapTupleIsValid(tuple))
+ break;
+
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+
+ datum = slot_getattr(slot, Anum_ag_label_name, &isNull);
+ label = DatumGetName(datum);
+
+ labels = lappend(labels, label);
+ }
+
+ heap_endscan(scan_desc);
+ heap_close(ag_label, RowExclusiveLock);
+ heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+
+ return labels;
+}
diff --git a/src/backend/commands/graph_commands.c b/src/backend/commands/graph_commands.c
index 40193b9..c201ee8 100644
--- a/src/backend/commands/graph_commands.c
+++ b/src/backend/commands/graph_commands.c
@@ -377,4 +377,3 @@ void drop_graphs(List *graphnames)
drop_graph, CStringGetDatum(graphname), BoolGetDatum(true));
}
}
-
diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c
index e84b127..36f919b 100644
--- a/src/backend/executor/cypher_create.c
+++ b/src/backend/executor/cypher_create.c
@@ -29,12 +29,14 @@
#include "parser/parse_relation.h"
#include "rewrite/rewriteHandler.h"
#include "utils/rel.h"
+#include "utils/tqual.h"
#include "catalog/ag_label.h"
#include "executor/cypher_executor.h"
#include "executor/cypher_utils.h"
#include "nodes/cypher_nodes.h"
#include "utils/agtype.h"
+#include "utils/ag_cache.h"
#include "utils/graphid.h"
static void begin_cypher_create(CustomScanState *node, EState *estate,
@@ -53,8 +55,9 @@ static HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo,
TupleTableSlot *elemTupleSlot, EState *estate);
static void process_pattern(cypher_create_custom_scan_state *css);
static void process_all_tuples(CustomScanState *node, EState *estate);
+static bool entity_exists(EState *estate, Oid graph_oid, graphid id);
-const CustomExecMethods cypher_create_exec_methods = {"Cypher Create",
+const CustomExecMethods cypher_create_exec_methods = {CREATE_SCAN_STATE_NAME,
begin_cypher_create,
exec_cypher_create,
end_cypher_create,
@@ -136,7 +139,18 @@ static void begin_cypher_create(CustomScanState *node, EState *estate,
}
}
- estate->es_output_cid++;
+ /*
+ * Postgres does not assign the es_output_cid in queries that do
+ * not write to disk, ie: SELECT commands. We need the command id
+ * for our clauses, and we may need to initialize it. We cannot use
+ * GetCurrentCommandId because there may be other cypher clauses
+ * that have modified the command id.
+ */
+ if (estate->es_output_cid == 0)
+ estate->es_output_cid = estate->es_snapshot->curcid;
+
+ CommandCounterIncrement();
+ Increment_Estate_CommandId(estate);
}
/*
@@ -196,11 +210,11 @@ static void process_all_tuples(CustomScanState *node, EState *estate)
do
{
- estate->es_output_cid++;
process_pattern(css);
- estate->es_output_cid--;
+ Decrement_Estate_CommandId(estate);
slot = ExecProcNode(node->ss.ps.lefttree);
+ Increment_Estate_CommandId(estate);
} while (!TupIsNull(slot));
}
@@ -217,10 +231,11 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node)
saved_resultRelInfo = estate->es_result_relation_info;
//Process the subtree first
- estate->es_output_cid--;
+ Decrement_Estate_CommandId(estate);
slot = ExecProcNode(node->ss.ps.lefttree);
+ Increment_Estate_CommandId(estate);
+
css->slot = slot;
- estate->es_output_cid++;
econtext->ecxt_scantuple =
node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple;
@@ -287,8 +302,6 @@ static void end_cypher_create(CustomScanState *node)
RowExclusiveLock);
}
}
-
- GetCurrentCommandId(true);
}
static void rescan_cypher_create(CustomScanState *node)
@@ -311,6 +324,7 @@ Node *create_cypher_create_plan_state(CustomScan *cscan)
cypher_css->pattern = target_nodes->paths;
cypher_css->tuple_info = NIL;
cypher_css->flags = target_nodes->flags;
+ cypher_css->graph_oid = target_nodes->graph_oid;
cypher_css->css.ss.ps.type = T_CustomScanState;
cypher_css->css.methods = &cypher_create_exec_methods;
@@ -397,16 +411,7 @@ static void create_edge(cypher_create_custom_scan_state *css,
tuple = insert_entity_tuple(resultRelInfo, elemTupleSlot, estate);
if (node->variable_name != NULL)
- {
- clause_tuple_information *tuple_info;
-
- tuple_info = palloc(sizeof(clause_tuple_information));
-
- tuple_info->tuple = tuple;
- tuple_info->name = node->variable_name;
-
- css->tuple_info = lappend(css->tuple_info, tuple_info);
- }
+ css->tuple_info = add_tuple_info(css->tuple_info, tuple, node->variable_name);
/*
* When the edge is used by clauses higher in the execution tree
@@ -533,6 +538,7 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
{
agtype *a;
agtype_value *v;
+ bool is_deleted = false;
a = (agtype *)scanTupleSlot->tts_values[node->id_var_no];
@@ -544,6 +550,20 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
errmsg("id agtype must resolve to a graphid")));
id = GRAPHID_GET_DATUM(v->val.int_value);
+
+ /*
+ * Its possible the variable has already been deleted. There are two ways
+ * this can happen. One is the query explicitly deleted the variable, the
+ * is_deleted flag will catch that. However, it is possible the user deleted
+ * the vertex using another variable name. We need to scan the table to find
+ * the vertex's current status relative to this CREATE clause.
+ */
+ get_heap_tuple(&css->css, node->variable_name, &is_deleted);
+
+ if (is_deleted || !entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id)))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("vertex assigned to variable %s was deleted", node->variable_name)));
}
else if (CYPHER_TARGET_NODE_ID_IS_GRAPHID(node->flags))
{
@@ -572,6 +592,47 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
}
/*
+ * Find out if the entity still exists. This is for 'implicit' deletion
+ * of an entity.
+ */
+static bool entity_exists(EState *estate, Oid graph_oid, graphid id)
+{
+ label_cache_data *label;
+ ScanKeyData scan_keys[1];
+ HeapScanDesc scan_desc;
+ HeapTuple tuple;
+ Relation rel;
+ bool result = true;
+
+ /*
+ * Extract the label id from the graph id and get the table name
+ * the entity is part of.
+ */
+ label = search_label_graph_id_cache(graph_oid, GET_LABEL_ID(id));
+
+ // Setup the scan key to be the graphid
+ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber,
+ F_GRAPHIDEQ, GRAPHID_GET_DATUM(id));
+
+ rel = heap_open(label->relation, RowExclusiveLock);
+ scan_desc = heap_beginscan(rel, estate->es_snapshot, 1, scan_keys);
+
+ tuple = heap_getnext(scan_desc, ForwardScanDirection);
+
+ /*
+ * If a single tuple was returned, the tuple is still valid, otherwise'
+ * set to false.
+ */
+ if (!HeapTupleIsValid(tuple))
+ result = false;
+
+ heap_endscan(scan_desc);
+ heap_close(rel, RowExclusiveLock);
+
+ return result;
+}
+
+/*
* Insert the edge/vertex tuple into the table and indices. If the table's
* constraints have not been violated.
*/
diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c
new file mode 100644
index 0000000..8c0e0a4
--- /dev/null
+++ b/src/backend/executor/cypher_delete.c
@@ -0,0 +1,496 @@
+/*
+ * 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.
+ */
+
+#include "postgres.h"
+
+#include "access/sysattr.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/xact.h"
+#include "storage/bufmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
+#include "nodes/extensible.h"
+#include "nodes/nodes.h"
+#include "nodes/plannodes.h"
+#include "parser/parsetree.h"
+#include "parser/parse_relation.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/rel.h"
+#include "utils/tqual.h"
+
+#include "catalog/ag_label.h"
+#include "commands/label_commands.h"
+#include "executor/cypher_executor.h"
+#include "executor/cypher_utils.h"
+#include "parser/cypher_parse_node.h"
+#include "nodes/cypher_nodes.h"
+#include "utils/agtype.h"
+#include "utils/graphid.h"
+
+static void begin_cypher_delete(CustomScanState *node, EState *estate,
+ int eflags);
+static TupleTableSlot *exec_cypher_delete(CustomScanState *node);
+static void end_cypher_delete(CustomScanState *node);
+static void rescan_cypher_delete(CustomScanState *node);
+
+static void process_delete_list(CustomScanState *node);
+
+static void find_connected_edges(CustomScanState *node, char *graph_name, List *labels,
+ char *var_name, graphid id, bool detach_delete);
+static agtype_value *extract_entity(CustomScanState *node, TupleTableSlot *scanTupleSlot,
+ int entity_position);
+static void delete_entity(CustomScanState *node, char *graph_name,
+ char *label_name, HeapTuple tuple);
+
+const CustomExecMethods cypher_delete_exec_methods = {DELETE_SCAN_STATE_NAME,
+ begin_cypher_delete,
+ exec_cypher_delete,
+ end_cypher_delete,
+ rescan_cypher_delete,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL};
+
+/*
+ * Initialization at the beginning of execution. Setup the child node,
+ * setup its scan tuple slot and projection infp, expression context,
+ * collect metadata about visible edges, and alter the commandid for
+ * the transaction.
+ */
+static void begin_cypher_delete(CustomScanState *node, EState *estate,
+ int eflags)
+{
+ cypher_delete_custom_scan_state *css =
+ (cypher_delete_custom_scan_state *)node;
+ Plan *subplan;
+
+ Assert(list_length(css->cs->custom_plans) == 1);
+
+ // setup child
+ subplan = linitial(css->cs->custom_plans);
+ node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags);
+
+ // setup expr context
+ ExecAssignExprContext(estate, &node->ss.ps);
+
+ // setup scan tuple slot and projection info
+ ExecInitScanTupleSlot(estate, &node->ss,
+ ExecGetResultType(node->ss.ps.lefttree));
+
+ if (!CYPHER_CLAUSE_IS_TERMINAL(css->flags))
+ {
+ TupleDesc tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+
+ ExecAssignProjectionInfo(&node->ss.ps, tupdesc);
+ }
+
+ /*
+ * Get all the labels that are visible to this delete clause at this point
+ * in the transaction. To be used later when the delete clause finds vertices.
+ */
+ css->edge_labels = get_all_edge_labels_per_graph(estate, css->delete_data->graph_oid);
+
+ /*
+ * Postgres does not assign the es_output_cid in queries that do
+ * not write to disk, ie: SELECT commands. We need the command id
+ * for our clauses, and we may need to initialize it. We cannot use
+ * GetCurrentCommandId because there may be other cypher clauses
+ * that have modified the command id.
+ */
+ if (estate->es_output_cid == 0)
+ estate->es_output_cid = estate->es_snapshot->curcid;
+
+ CommandCounterIncrement();
+ Increment_Estate_CommandId(estate);
+}
+
+/*
+ * Called once per tuple. If this is a terminal DELETE clause
+ * process everyone of its child tuple, otherwise process the
+ * next tuple.
+ */
+static TupleTableSlot *exec_cypher_delete(CustomScanState *node)
+{
+ cypher_delete_custom_scan_state *css =
+ (cypher_delete_custom_scan_state *)node;
+ EState *estate = css->css.ss.ps.state;
+ ExprContext *econtext = css->css.ss.ps.ps_ExprContext;
+ TupleTableSlot *slot;
+
+ if (CYPHER_CLAUSE_IS_TERMINAL(css->flags))
+ {
+ /*
+ * If the DELETE clause was the final cypher clause written
+ * then we aren't returning anything from this result node.
+ * So the exec_cypher_delete function will only be called once.
+ * Therefore we will process all tuples from the subtree at once.
+ */
+ while(true)
+ {
+ //Process the subtree first
+ Decrement_Estate_CommandId(estate)
+ slot = ExecProcNode(node->ss.ps.lefttree);
+ Increment_Estate_CommandId(estate)
+
+ if (TupIsNull(slot))
+ break;
+
+ // setup the scantuple that the process_delete_list needs
+ econtext->ecxt_scantuple =
+ node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple;
+
+ css->tuple_info = NIL;
+
+ process_delete_list(node);
+ }
+
+ return NULL;
+ }
+ else
+ {
+ //Process the subtree first
+ Decrement_Estate_CommandId(estate)
+ slot = ExecProcNode(node->ss.ps.lefttree);
+ Increment_Estate_CommandId(estate)
+
+ if (TupIsNull(slot))
+ return NULL;
+
+ // setup the scantuple that the process_delete_list needs
+ econtext->ecxt_scantuple =
+ node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple;
+
+ css->tuple_info = NIL;
+
+ process_delete_list(node);
+
+ econtext->ecxt_scantuple =
+ ExecProject(node->ss.ps.lefttree->ps_ProjInfo);
+
+ return ExecProject(node->ss.ps.ps_ProjInfo);
+ }
+}
+
+/*
+ * Called at the end of execution. Tell its child to
+ * end its execution.
+ */
+static void end_cypher_delete(CustomScanState *node)
+{
+ ExecEndNode(node->ss.ps.lefttree);
+}
+
+/*
+ * Used for rewinding the scan state and reprocessing the results.
+ *
+ * XXX: This is not currently supported. We need to find out
+ * when this function will be called and determine a process
+ * for allowing the Delete clause to run multiple times without
+ * redundant edits to the database.
+ */
+static void rescan_cypher_delete(CustomScanState *node)
+{
+ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cypher DELETE clause cannot be rescaned"),
+ errhint("its unsafe to use joins in a query with a Cypher DELETE clause")));
+}
+
+/*
+ * Create the CustomScanState from the CustomScan and pass
+ * necessary metadata.
+ */
+Node *create_cypher_delete_plan_state(CustomScan *cscan)
+{
+ cypher_delete_custom_scan_state *cypher_css =
+ palloc0(sizeof(cypher_delete_custom_scan_state));
+ cypher_delete_information *delete_data;
+
+ cypher_css->cs = cscan;
+
+ delete_data = linitial(cscan->custom_private);
+ cypher_css->delete_data = delete_data;
+ cypher_css->flags = delete_data->flags;
+
+ cypher_css->css.ss.ps.type = T_CustomScanState;
+ cypher_css->css.methods = &cypher_delete_exec_methods;
+
+ return (Node *)cypher_css;
+}
+
+/*
+ * Extract the vertex or edge to be deleted, perform some type checking to validate
+ * datum is an agtype vertex or edge.
+ */
+static agtype_value *extract_entity(CustomScanState *node, TupleTableSlot *scanTupleSlot,
+ int entity_position)
+{
+ agtype_value *original_entity_value;
+ agtype *original_entity;
+ TupleDesc tupleDescriptor;
+
+ tupleDescriptor = scanTupleSlot->tts_tupleDescriptor;
+
+ // type checking, make sure the entity is an agtype vertex or edge
+ if (tupleDescriptor->attrs[entity_position -1].atttypid != AGTYPEOID)
+ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DELETE clause can only delete agtype")));
+
+ original_entity = DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[entity_position - 1]);
+ original_entity_value = get_ith_agtype_value_from_container(&original_entity->root, 0);
+
+ if (original_entity_value->type != AGTV_VERTEX && original_entity_value->type != AGTV_EDGE)
+ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DELETE clause can only update vertex and edges")));
+
+ return original_entity_value;
+}
+
+/*
+ * Try and delete the entity that is describe by the HeapTuple in
+ * the table described by the graph_name and label_name.
+ */
+static void delete_entity(CustomScanState *node, char *graph_name,
+ char *label_name, HeapTuple tuple)
+{
+ cypher_delete_custom_scan_state *css =
+ (cypher_delete_custom_scan_state *)node;
+ EState *estate = css->css.ss.ps.state;
+ ResultRelInfo *resultRelInfo, *saved_resultRelInfo;
+ LockTupleMode lockmode;
+ HeapUpdateFailureData hufd;
+ HTSU_Result lock_result;
+ HTSU_Result delete_result;
+ Buffer buffer;
+
+ resultRelInfo = create_entity_result_rel_info(estate, graph_name, label_name);
+ ExecOpenIndices(resultRelInfo, false);
+
+ // Find the physical tuple, this variable is coming from
+ saved_resultRelInfo = estate->es_result_relation_info;
+ estate->es_result_relation_info = resultRelInfo;
+
+ lockmode = ExecUpdateLockMode(estate, resultRelInfo);
+
+ lock_result = heap_lock_tuple(resultRelInfo->ri_RelationDesc, tuple, estate->es_output_cid,
+ lockmode, LockWaitBlock, false, &buffer, &hufd);
+
+ /*
+ * It is possible the entity may have already been deleted. If the tuple
+ * can be deleted, the lock result will be HeapTupleMayBeUpdated. If the
+ * tuple was already deleted by this DELETE clause, the result would be
+ * HeapTupleSelfUpdated, if the result was deleted by a previous delete
+ * clause, the result will HeapTupleInvisible. Throw an error if any
+ * other result was returned.
+ */
+ if (lock_result == HeapTupleMayBeUpdated)
+ {
+ delete_result = heap_delete(resultRelInfo->ri_RelationDesc, &tuple->t_self,
+ estate->es_output_cid, estate->es_crosscheck_snapshot, true,
+ &hufd, false);
+
+ /*
+ * Unlike locking, the heap_delete either succeeded HeapTupleMayBeUpdate,
+ * or it failed and returned any other result.
+ */
+ switch (delete_result)
+ {
+ case HeapTupleMayBeUpdated:
+ break;
+ case HeapTupleSelfUpdated:
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("deleting the same entity more than once cannot happen")));
+ break;
+ case HeapTupleUpdated:
+ ereport(ERROR,
+ (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+ errmsg("could not serialize access due to concurrent update")));
+ return;
+ default:
+ elog(ERROR, "Entity failed to be update");
+ return;
+ }
+
+ }
+ else if (lock_result != HeapTupleInvisible && lock_result != HeapTupleSelfUpdated)
+ {
+ ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("Entity could not be locked for updating")));
+
+ }
+
+ ReleaseBuffer(buffer);
+
+ estate->es_result_relation_info = saved_resultRelInfo;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+}
+
+/*
+ * After the delete's subtress has been processed, we then go through
+ * the list of variables to be deleted
+ */
+static void process_delete_list(CustomScanState *node)
+{
+ cypher_delete_custom_scan_state *css =
+ (cypher_delete_custom_scan_state *)node;
+ ListCell *lc;
+ ExprContext *econtext = css->css.ss.ps.ps_ExprContext;
+ TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple;
+
+ foreach(lc, css->delete_data->delete_items)
+ {
+ cypher_delete_item *item;
+ agtype_value *original_entity_value, *id, *label;
+ HeapTuple heap_tuple;
+ char *label_name;
+ Value *pos;
+ int entity_position;
+ bool is_deleted;
+
+ item = lfirst(lc);
+
+ pos = item->entity_position;
+ entity_position = pos->val.ival;
+
+ // skip if the entity is null
+ if (scanTupleSlot->tts_isnull[entity_position - 1])
+ continue;
+
+ /*
+ * find the where the entity came from, if the tuple was deleted
+ * by a previous DELETE clause its safe to skip this tuple
+ */
+ heap_tuple = get_heap_tuple(node, item->var_name, &is_deleted);
+ if (is_deleted || heap_tuple == NULL)
+ continue;
+
+ original_entity_value = extract_entity(node, scanTupleSlot, entity_position);
+
+ id = get_agtype_value_object_value(original_entity_value, "id");
+ label = get_agtype_value_object_value(original_entity_value, "label");
+ label_name = pnstrdup(label->val.string.val, label->val.string.len);
+
+ /*
+ * For vertices, we need to check if the vertex is connected to any edges,
+ * if there are, we need to either delete them, or throw an error, depending
+ * on if the query specified the DETACH option.
+ */
+ if (original_entity_value->type == AGTV_VERTEX)
+ find_connected_edges(node, css->delete_data->graph_name, css->edge_labels,
+ item->var_name, id->val.int_value, css->delete_data->detach);
+
+ /*
+ * At this point, we are ready to delete the node/vertex.
+ */
+ delete_entity(node, css->delete_data->graph_name, label_name, heap_tuple);
+
+ /*
+ * Add the deleted tuple to the custom scan state's info on updated
+ * tuples.
+ */
+ css->tuple_info = add_tuple_info(css->tuple_info, heap_tuple, item->var_name);
+ }
+}
+
+/*
+ * Find the edges connected to the given node. If there is any edges either delete them
+ * or throw an error, depending on the detach delete option.
+ */
+static void find_connected_edges(CustomScanState *node, char *graph_name, List *labels,
+ char *var_name, graphid id, bool detach_delete)
+{
+ cypher_delete_custom_scan_state *css =
+ (cypher_delete_custom_scan_state *)node;
+ EState *estate = css->css.ss.ps.state;
+ ListCell *lc;
+
+ Increment_Estate_CommandId(estate);
+
+ /*
+ * We need to scan through all the edges to see if this vertex has
+ * any edges attached to it.
+ *
+ * XXX: If we implement an on-disc graph storage system. Such as
+ * an adjacency matrix, the performace of this check can be massively
+ * improved. However, right now we have to scan every edge to see if
+ * one has this vertex as a start or end vertex.
+ */
+ foreach(lc, labels)
+ {
+ char *label_name = lfirst(lc);
+ ResultRelInfo *resultRelInfo;
+ HeapScanDesc scan_desc;
+ HeapTuple tuple;
+ TupleTableSlot *slot;
+
+ resultRelInfo = create_entity_result_rel_info(estate, graph_name, label_name);
+
+ scan_desc = heap_beginscan(resultRelInfo->ri_RelationDesc, estate->es_snapshot, 0, NULL);
+
+ slot = ExecInitExtraTupleSlot(estate,
+ RelationGetDescr(resultRelInfo->ri_RelationDesc));
+
+ // scan the table
+ while(true)
+ {
+ graphid startid, endid;
+ bool isNull;
+
+ tuple = heap_getnext(scan_desc, ForwardScanDirection);
+
+ // no more tuples to process, break and scan the next label.
+ if (!HeapTupleIsValid(tuple))
+ break;
+
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+
+ startid = GRAPHID_GET_DATUM(slot_getattr(slot, Anum_ag_label_edge_table_start_id, &isNull));
+ endid = GRAPHID_GET_DATUM(slot_getattr(slot, Anum_ag_label_edge_table_end_id, &isNull));
+
+ if (id == startid || id == endid)
+ {
+ /*
+ * We have found an edge that uses the vertex. Either delete the edge
+ * or throw an error. Depending on whether the DETACH option was
+ * specified in the query.
+ */
+ if (detach_delete)
+ delete_entity(node, graph_name, label_name, tuple);
+ else
+ ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("Cannot delete vertex %s, because it still has edges attached. "
+ "To delete this vertex, you must first delete the attached edges.",
+ var_name)));
+ }
+ }
+
+ heap_endscan(scan_desc);
+ heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+ }
+
+ Decrement_Estate_CommandId(estate);
+}
diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c
index f0acede..1e6cead 100644
--- a/src/backend/executor/cypher_set.c
+++ b/src/backend/executor/cypher_set.c
@@ -53,7 +53,7 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, agt
static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo,
TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple);
-const CustomExecMethods cypher_set_exec_methods = {"Cypher Set",
+const CustomExecMethods cypher_set_exec_methods = {SET_SCAN_STATE_NAME,
begin_cypher_set,
exec_cypher_set,
end_cypher_set,
@@ -91,13 +91,24 @@ static void begin_cypher_set(CustomScanState *node, EState *estate,
ExecAssignProjectionInfo(&node->ss.ps, tupdesc);
}
- estate->es_output_cid++;
+ /*
+ * Postgres does not assign the es_output_cid in queries that do
+ * not write to disk, ie: SELECT commands. We need the command id
+ * for our clauses, and we may need to initialize it. We cannot use
+ * GetCurrentCommandId because there may be other cypher clauses
+ * that have modified the command id.
+ */
+ if (estate->es_output_cid == 0)
+ estate->es_output_cid = estate->es_snapshot->curcid;
+
+ CommandCounterIncrement();
+ Increment_Estate_CommandId(estate);
}
static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo,
TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple)
{
- HeapTuple tuple;
+ HeapTuple tuple = NULL;
LockTupleMode lockmode;
HeapUpdateFailureData hufd;
HTSU_Result lock_result;
@@ -107,40 +118,37 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo,
ResultRelInfo *saved_resultRelInfo = saved_resultRelInfo;;
estate->es_result_relation_info = resultRelInfo;
-
lockmode = ExecUpdateLockMode(estate, resultRelInfo);
- //XXX: Check lock_result
lock_result = heap_lock_tuple(resultRelInfo->ri_RelationDesc, old_tuple, estate->es_output_cid,
lockmode, LockWaitBlock, false, &buffer, &hufd);
- if (lock_result != HeapTupleMayBeUpdated)
- ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("Entity could not be locked for updating")));
-
+ if (lock_result == HeapTupleMayBeUpdated)
+ {
+ ExecStoreVirtualTuple(elemTupleSlot);
+ tuple = ExecMaterializeSlot(elemTupleSlot);
+ tuple->t_self = old_tuple->t_self;
- ExecStoreVirtualTuple(elemTupleSlot);
- tuple = ExecMaterializeSlot(elemTupleSlot);
- tuple->t_self = old_tuple->t_self;
+ // Check the constraints of the tuple
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL)
+ ExecConstraints(resultRelInfo, elemTupleSlot, estate);
- // Check the constraints of the tuple
- tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
- if (resultRelInfo->ri_RelationDesc->rd_att->constr != NULL)
- ExecConstraints(resultRelInfo, elemTupleSlot, estate);
+ // Insert the tuple normally
+ update_result = heap_update(resultRelInfo->ri_RelationDesc, &(tuple->t_self), tuple,
+ estate->es_output_cid, estate->es_crosscheck_snapshot,
+ true, &hufd, &lockmode);
- // Insert the tuple normally
- update_result = heap_update(resultRelInfo->ri_RelationDesc, &(tuple->t_self), tuple,
- estate->es_output_cid, estate->es_crosscheck_snapshot,
- true, &hufd, &lockmode);
+ if (update_result != HeapTupleMayBeUpdated)
+ ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("Entity failed to be updated: %i", update_result)));
- if (update_result != HeapTupleMayBeUpdated)
- ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("Entity failed to be updated")));
+ // Insert index entries for the tuple
+ if (resultRelInfo->ri_NumIndices > 0)
+ ExecInsertIndexTuples(elemTupleSlot, &(tuple->t_self), estate, false,
+ NULL, NIL);
- // Insert index entries for the tuple
- if (resultRelInfo->ri_NumIndices > 0)
- ExecInsertIndexTuples(elemTupleSlot, &(tuple->t_self), estate, false,
- NULL, NIL);
+ }
ReleaseBuffer(buffer);
@@ -158,14 +166,17 @@ static void process_all_tuples(CustomScanState *node)
cypher_set_custom_scan_state *css =
(cypher_set_custom_scan_state *)node;
TupleTableSlot *slot;
+ EState *estate = css->css.ss.ps.state;
do
{
css->tuple_info = NIL;
process_update_list(node);
-
+ Decrement_Estate_CommandId(estate)
slot = ExecProcNode(node->ss.ps.lefttree);
+ Increment_Estate_CommandId(estate)
+
} while (!TupIsNull(slot));
}
@@ -283,11 +294,9 @@ static void process_update_list(CustomScanState *node)
char *label_name;
cypher_update_item *update_item = (cypher_update_item *)lfirst(lc);
Datum new_entity;
- ResultRelInfo *resultRelInfo;
- TupleTableSlot *elemTupleSlot;
HeapTuple heap_tuple;
- HeapTuple tuple;
char *clause_name = css->set_list->clause_name;
+ bool is_deleted;
update_item = (cypher_update_item *)lfirst(lc);
@@ -333,17 +342,8 @@ static void process_update_list(CustomScanState *node)
altered_properties = alter_property_value(original_properties, update_item->prop_name,
new_property_value, remove_property);
- // create the resultRelInfo that is the on disc table we need to update
- resultRelInfo = create_entity_result_rel_info(node, css->set_list->graph_name, label_name);
-
- ExecOpenIndices(resultRelInfo, false);
-
- elemTupleSlot = ExecInitExtraTupleSlot(estate, RelationGetDescr(resultRelInfo->ri_RelationDesc));
-
if (original_entity_value->type == AGTV_VERTEX)
{
- elemTupleSlot = populate_vertex_tts(elemTupleSlot, id, altered_properties);
-
new_entity = make_vertex(GRAPHID_GET_DATUM(id->val.int_value),
CStringGetDatum(label_name),
AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties)));
@@ -353,8 +353,6 @@ static void process_update_list(CustomScanState *node)
agtype_value *startid = get_agtype_value_object_value(original_entity_value, "start_id");
agtype_value *endid = get_agtype_value_object_value(original_entity_value, "end_id");
- elemTupleSlot = populate_edge_tts(elemTupleSlot, id, startid, endid, altered_properties);
-
new_entity = make_edge(GRAPHID_GET_DATUM(id->val.int_value),
GRAPHID_GET_DATUM(startid->val.int_value),
GRAPHID_GET_DATUM(endid->val.int_value),
@@ -367,32 +365,47 @@ static void process_update_list(CustomScanState *node)
errmsg("age %s clause can only update vertex and edges", clause_name)));
}
- // update the on-disc table
- heap_tuple = get_heap_tuple(node, update_item->var_name);
+ // update the in-memory tuple slot
+ scanTupleSlot->tts_values[update_item->entity_position - 1] = new_entity;
+
+ update_all_paths(node, id->val.int_value, DATUM_GET_AGTYPE_P(new_entity));
- tuple = update_entity_tuple(resultRelInfo, elemTupleSlot, estate, heap_tuple);
+ // update the on-disc table
+ heap_tuple = get_heap_tuple(node, update_item->var_name, &is_deleted);
- if (update_item->var_name != NULL)
+ if (!is_deleted)
{
- clause_tuple_information *tuple_info;
+ ResultRelInfo *resultRelInfo;
+ TupleTableSlot *elemTupleSlot;
+ HeapTuple tuple;
- tuple_info = palloc(sizeof(clause_tuple_information));
+ resultRelInfo = create_entity_result_rel_info(estate, css->set_list->graph_name, label_name);
- tuple_info->tuple = tuple;
- tuple_info->name = update_item->var_name;
+ ExecOpenIndices(resultRelInfo, false);
- css->tuple_info = lappend(css->tuple_info, tuple_info);
- }
+ elemTupleSlot = ExecInitExtraTupleSlot(estate, RelationGetDescr(resultRelInfo->ri_RelationDesc));
+ if (original_entity_value->type == AGTV_VERTEX)
+ {
+ elemTupleSlot = populate_vertex_tts(elemTupleSlot, id, altered_properties);
+ }
+ else if (original_entity_value->type == AGTV_EDGE)
+ {
+ agtype_value *startid = get_agtype_value_object_value(original_entity_value, "start_id");
+ agtype_value *endid = get_agtype_value_object_value(original_entity_value, "end_id");
+ elemTupleSlot = populate_edge_tts(elemTupleSlot, id, startid, endid, altered_properties);
+ }
- ExecCloseIndices(resultRelInfo);
+ tuple = update_entity_tuple(resultRelInfo, elemTupleSlot, estate, heap_tuple);
- heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+ if (update_item->var_name != NULL)
+ css->tuple_info = add_tuple_info(css->tuple_info, tuple, update_item->var_name);
- // update the in-memory tuple slot
- scanTupleSlot->tts_values[update_item->entity_position - 1] = new_entity;
+ ExecCloseIndices(resultRelInfo);
- update_all_paths(node, id->val.int_value, DATUM_GET_AGTYPE_P(new_entity));
+ heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+
+ }
}
}
@@ -410,9 +423,9 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node)
css->tuple_info = NIL;
//Process the subtree first
- estate->es_output_cid--;
+ Decrement_Estate_CommandId(estate);
slot = ExecProcNode(node->ss.ps.lefttree);
- estate->es_output_cid++;
+ Increment_Estate_CommandId(estate);
if (TupIsNull(slot))
return NULL;
@@ -442,8 +455,6 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node)
static void end_cypher_set(CustomScanState *node)
{
ExecEndNode(node->ss.ps.lefttree);
-
- GetCurrentCommandId(true);
}
static void rescan_cypher_set(CustomScanState *node)
diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c
index 6bf283b..159ba0e 100644
--- a/src/backend/executor/cypher_utils.c
+++ b/src/backend/executor/cypher_utils.c
@@ -21,6 +21,8 @@
#include "access/htup_details.h"
#include "access/sysattr.h"
+#include "access/xact.h"
+#include "access/multixact.h"
#include "nodes/extensible.h"
#include "nodes/makefuncs.h"
#include "nodes/nodes.h"
@@ -28,10 +30,12 @@
#include "nodes/plannodes.h"
#include "parser/parsetree.h"
#include "parser/parse_relation.h"
+#include "storage/procarray.h"
#include "utils/rel.h"
#include "catalog/ag_label.h"
#include "commands/label_commands.h"
+#include "executor/cypher_executor.h"
#include "executor/cypher_utils.h"
#include "utils/agtype.h"
#include "utils/graphid.h"
@@ -43,13 +47,14 @@ typedef struct find_scan_state_context {
char *var_name;
PlanState *variable_plan_state;
EState *estate;
+ bool is_deleted;
} find_scan_state_context;
/*
* This function will find the scan state that a variable name
* is referencing.
*/
-PlanState *find_plan_state(CustomScanState *node, char *var_name)
+PlanState *find_plan_state(CustomScanState *node, char *var_name, bool *is_deleted)
{
PlanState *ps;
find_scan_state_context *context;
@@ -57,6 +62,8 @@ PlanState *find_plan_state(CustomScanState *node, char *var_name)
context = palloc(sizeof(find_scan_state_context));
context->var_name = var_name;
context->estate = node->ss.ps.state;
+ context->is_deleted = false;
+ context->variable_plan_state = NULL;
planstate_tree_walker((PlanState *)node, find_scan_state_walker, context);
@@ -66,6 +73,8 @@ PlanState *find_plan_state(CustomScanState *node, char *var_name)
ereport(ERROR,
(errmsg("cannot find plan state for variable '%s'", var_name)));
+ *is_deleted = context->is_deleted;
+
return ps;
}
@@ -83,7 +92,7 @@ static bool find_scan_state_walker(PlanState *p, void *context)
CustomScanState *css = (CustomScanState *)p;
const struct CustomExecMethods *methods = css->methods;
- if (!strcmp(methods->CustomName, "Cypher Create"))
+ if (!strcmp(methods->CustomName, CREATE_SCAN_STATE_NAME))
{
cypher_create_custom_scan_state *custom_state = (cypher_create_custom_scan_state *)css;
if (inspect_clause_tuple_info(custom_state->tuple_info, var_name))
@@ -92,7 +101,7 @@ static bool find_scan_state_walker(PlanState *p, void *context)
return true;
}
}
- else if (!strcmp(methods->CustomName, "Cypher Set"))
+ else if (!strcmp(methods->CustomName, SET_SCAN_STATE_NAME))
{
cypher_set_custom_scan_state *custom_state = (cypher_set_custom_scan_state *)css;
if (inspect_clause_tuple_info(custom_state->tuple_info, var_name))
@@ -101,6 +110,16 @@ static bool find_scan_state_walker(PlanState *p, void *context)
return true;
}
}
+ else if (!strcmp(methods->CustomName, DELETE_SCAN_STATE_NAME))
+ {
+ cypher_delete_custom_scan_state *custom_state = (cypher_delete_custom_scan_state *)css;
+ if (inspect_clause_tuple_info(custom_state->tuple_info, var_name))
+ {
+ cnxt->is_deleted = true;
+ cnxt->variable_plan_state = (PlanState *)p;
+ return true;
+ }
+ }
break;
}
case T_AppendState:
@@ -123,7 +142,7 @@ static bool find_scan_state_walker(PlanState *p, void *context)
RangeTblEntry *rte = rt_fetch(scan->scanrelid, estate->es_range_table);
Alias *alias = rte->alias;
- if (!strcmp(alias->aliasname, var_name) && seq->ss.ss_ScanTupleSlot->tts_tuple != NULL)
+ if (!strcmp(alias->aliasname, var_name))
{
cnxt->variable_plan_state = (PlanState *)seq;
return true;
@@ -138,10 +157,15 @@ static bool find_scan_state_walker(PlanState *p, void *context)
return planstate_tree_walker(p, find_scan_state_walker, context);
}
-HeapTuple get_heap_tuple(CustomScanState *node, char *var_name)
+/*
+ * In the custom scan states we need to find the heap tuple that we want to
+ * modify (CREATE, DELETE, SET, etc). Find the scan state that has most recently
+ * modified, if no clause have finds the heap tuple in the original scan state.
+ */
+HeapTuple get_heap_tuple(CustomScanState *node, char *var_name, bool *is_deleted)
{
- PlanState *ps = find_plan_state(node, var_name);
- HeapTuple heap_tuple = NULL;;
+ PlanState *ps = find_plan_state(node, var_name, is_deleted);
+ HeapTuple heap_tuple = NULL;
switch (ps->type)
{
@@ -152,16 +176,21 @@ HeapTuple get_heap_tuple(CustomScanState *node, char *var_name)
List *tuple_info;
ListCell *lc;
- if (!strcmp(methods->CustomName, "Cypher Create"))
+ if (!strcmp(methods->CustomName, CREATE_SCAN_STATE_NAME))
{
cypher_create_custom_scan_state *css = (cypher_create_custom_scan_state *)ps;
tuple_info = css->tuple_info;
}
- else if (!strcmp(methods->CustomName, "Cypher Set"))
+ else if (!strcmp(methods->CustomName, SET_SCAN_STATE_NAME))
{
cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)ps;
tuple_info = css->tuple_info;
}
+ else if (!strcmp(methods->CustomName, DELETE_SCAN_STATE_NAME))
+ {
+ cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)ps;
+ tuple_info = css->tuple_info;
+ }
else
{
ereport(ERROR, (errmsg("cannot extract tuple information from %s", methods->CustomName)));
@@ -185,7 +214,10 @@ HeapTuple get_heap_tuple(CustomScanState *node, char *var_name)
TupleTableSlot *ss_tts = ((SeqScanState *)ps)->ss.ss_ScanTupleSlot;
if (!ss_tts->tts_tuple)
- ereport(ERROR, (errmsg("cypher update clause needs physical tuples")));
+ {
+ heap_tuple = ss_tts->tts_tuple;
+ break;
+ }
heap_getsysattr(ss_tts->tts_tuple, SelfItemPointerAttributeNumber,
ss_tts->tts_tupleDescriptor, &isNull);
@@ -204,20 +236,6 @@ HeapTuple get_heap_tuple(CustomScanState *node, char *var_name)
return heap_tuple;
}
-/*static bool inspect_create_clause_tuple_info(cypher_create_custom_scan_state *css, char *var_name)
-{
- ListCell *lc;
-
- foreach(lc, css->tuple_info)
- {
- create_clause_tuple_information *info = lfirst(lc);
-
- if (!strcmp(info->name, var_name))
- return true;
- }
- return false;
-}*/
-
static bool inspect_clause_tuple_info(List *tuple_info, char *var_name)
{
ListCell *lc;
@@ -232,9 +250,8 @@ static bool inspect_clause_tuple_info(List *tuple_info, char *var_name)
return false;
}
-ResultRelInfo *create_entity_result_rel_info(CustomScanState *node, char *graph_name, char *label_name)
+ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, char *label_name)
{
- EState *estate = node->ss.ps.state;
Oid relid;
RangeVar *rv;
Relation label_relation, rel;
@@ -343,3 +360,20 @@ TupleTableSlot *populate_edge_tts(
return elemTupleSlot;
}
+
+/*
+ * Create a new clause_tuple_information with the given
+ * heap tuple and variable name and append it to the given
+ * list
+ */
+List *add_tuple_info(List *list, HeapTuple heap_tuple, char *var_name)
+{
+ clause_tuple_information *tuple_info;
+
+ tuple_info = palloc(sizeof(clause_tuple_information));
+
+ tuple_info->tuple = heap_tuple;
+ tuple_info->name = var_name;
+
+ return lappend(list, tuple_info);
+}
diff --git a/src/backend/optimizer/cypher_createplan.c b/src/backend/optimizer/cypher_createplan.c
index 93a748f..d11d9f3 100644
--- a/src/backend/optimizer/cypher_createplan.c
+++ b/src/backend/optimizer/cypher_createplan.c
@@ -34,6 +34,8 @@ const CustomScanMethods cypher_create_plan_methods = {
"Cypher Create", create_cypher_create_plan_state};
const CustomScanMethods cypher_set_plan_methods = {
"Cypher Set", create_cypher_set_plan_state};
+const CustomScanMethods cypher_delete_plan_methods = {
+ "Cypher Delete", create_cypher_delete_plan_state};
Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel,
CustomPath *best_path, List *tlist,
@@ -119,3 +121,65 @@ Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel,
return (Plan *)cs;
}
+
+/*
+ * Coverts the Scan node representing the delete clause
+ * to the delete Plan node
+ */
+Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
+ CustomPath *best_path, List *tlist,
+ List *clauses, List *custom_plans)
+{
+ CustomScan *cs;
+ Plan *subplan = linitial(custom_plans);
+
+ cs = makeNode(CustomScan);
+
+ cs->scan.plan.startup_cost = best_path->path.startup_cost;
+ cs->scan.plan.total_cost = best_path->path.total_cost;
+
+ cs->scan.plan.plan_rows = best_path->path.rows;
+ cs->scan.plan.plan_width = 0;
+
+ cs->scan.plan.parallel_aware = best_path->path.parallel_aware;
+ cs->scan.plan.parallel_safe = best_path->path.parallel_safe;
+
+ cs->scan.plan.plan_node_id = 0; // Set later in set_plan_refs
+ /*
+ * the scan list of the delete node, used for its ScanTupleSlot used
+ * by its parent in the execution phase.
+ */
+ cs->scan.plan.targetlist = tlist;
+ cs->scan.plan.qual = NIL;
+ cs->scan.plan.lefttree = NULL;
+ cs->scan.plan.righttree = NULL;
+ cs->scan.plan.initPlan = NIL;
+
+ cs->scan.plan.extParam = NULL;
+ cs->scan.plan.allParam = NULL;
+
+ /*
+ * We do not want Postgres to assume we are scanning a table, postgres'
+ * optimizer will make assumptions about our targetlist that are false
+ */
+ cs->scan.scanrelid = 0;
+
+ cs->flags = best_path->flags;
+
+ // child plan nodes are here, Postgres processed them for us.
+ cs->custom_plans = custom_plans;
+ cs->custom_exprs = NIL;
+ // transfer delete metadata needed by the delete clause.
+ cs->custom_private = best_path->custom_private;
+ /*
+ * the scan list of the delete node's children, used for ScanTupleSlot
+ * in execution.
+ */
+ cs->custom_scan_tlist = subplan->targetlist;
+
+ cs->custom_relids = NULL;
+ cs->methods = &cypher_delete_plan_methods;
+
+ return (Plan *)cs;
+}
+
diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c
index 872e95b..fce17a0 100644
--- a/src/backend/optimizer/cypher_pathnode.c
+++ b/src/backend/optimizer/cypher_pathnode.c
@@ -28,9 +28,11 @@
#include "optimizer/cypher_pathnode.h"
const CustomPathMethods cypher_create_path_methods = {
- "Cypher Create", plan_cypher_create_path, NULL};
+ CREATE_PATH_NAME, plan_cypher_create_path, NULL};
const CustomPathMethods cypher_set_path_methods = {
- "Cypher Set", plan_cypher_set_path, NULL};
+ SET_PATH_NAME, plan_cypher_set_path, NULL};
+const CustomPathMethods cypher_delete_path_methods = {
+ DELETE_PATH_NAME, plan_cypher_delete_path, NULL};
CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel,
@@ -104,3 +106,47 @@ CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel,
return cp;
}
+
+/*
+ * Creates a Delete Path. Makes the original path a child of the new
+ * path. We leave it to the caller to replace the pathlist of the rel.
+ */
+CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
+ List *custom_private)
+{
+ CustomPath *cp;
+
+ cp = makeNode(CustomPath);
+
+ cp->path.pathtype = T_CustomScan;
+
+ cp->path.parent = rel;
+ cp->path.pathtarget = rel->reltarget;
+
+ cp->path.param_info = NULL;
+
+ // Do not allow parallel methods
+ cp->path.parallel_aware = false;
+ cp->path.parallel_safe = false;
+ cp->path.parallel_workers = 0;
+
+ cp->path.rows = 0;
+ cp->path.startup_cost = 0;
+ cp->path.total_cost = 0;
+
+ // No output ordering for basic SET
+ cp->path.pathkeys = NULL;
+
+ // Disable all custom flags for now
+ cp->flags = 0;
+
+ // Make the original paths the children of the new path
+ cp->custom_paths = rel->pathlist;
+ // Store the metadata Delete will need in the execution phase.
+ cp->custom_private = custom_private;
+ // Tells Postgres how to turn this path to the correct CustomScan
+ cp->methods = &cypher_delete_path_methods;
+
+ return cp;
+}
+
diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c
index 0b85001..51bd67d 100644
--- a/src/backend/optimizer/cypher_paths.c
+++ b/src/backend/optimizer/cypher_paths.c
@@ -35,7 +35,8 @@ typedef enum cypher_clause_kind
{
CYPHER_CLAUSE_NONE,
CYPHER_CLAUSE_CREATE,
- CYPHER_CLAUSE_SET
+ CYPHER_CLAUSE_SET,
+ CYPHER_CLAUSE_DELETE
} cypher_clause_kind;
static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook;
@@ -47,6 +48,8 @@ static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte);
+static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel,
+ Index rti, RangeTblEntry *rte);
void set_rel_pathlist_init(void)
{
@@ -72,6 +75,10 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti,
break;
case CYPHER_CLAUSE_SET:
handle_cypher_set_clause(root, rel, rti, rte);
+ break;
+ case CYPHER_CLAUSE_DELETE:
+ handle_cypher_delete_clause(root, rel, rti, rte);
+ break;
case CYPHER_CLAUSE_NONE:
break;
default:
@@ -106,15 +113,46 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte)
fe = (FuncExpr *)te->expr;
- if (is_oid_ag_func(fe->funcid, "_cypher_create_clause"))
+ if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME))
return CYPHER_CLAUSE_CREATE;
- if (is_oid_ag_func(fe->funcid, "_cypher_set_clause"))
+ if (is_oid_ag_func(fe->funcid, SET_CLAUSE_FUNCTION_NAME))
return CYPHER_CLAUSE_SET;
+ if (is_oid_ag_func(fe->funcid, DELETE_CLAUSE_FUNCTION_NAME))
+ return CYPHER_CLAUSE_DELETE;
else
return CYPHER_CLAUSE_NONE;
}
// replace all possible paths with our CustomPath
+static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel,
+ Index rti, RangeTblEntry *rte)
+{
+ TargetEntry *te;
+ FuncExpr *fe;
+ Const *c;
+ List *custom_private;
+ CustomPath *cp;
+
+ // Add the pattern to the CustomPath
+ te = (TargetEntry *)llast(rte->subquery->targetList);
+ fe = (FuncExpr *)te->expr;
+ c = linitial(fe->args);
+ custom_private = list_make1(DatumGetPointer(c->constvalue));
+
+ cp = create_cypher_delete_path(root, rel, custom_private);
+
+ // Discard any pre-existing paths
+ rel->pathlist = NIL;
+ rel->partial_pathlist = NIL;
+
+ add_path(rel, (Path *)cp);
+}
+
+/*
+ * Take the paths possible for the RelOptInfo that represents our
+ * _cypher_delete_clause function replace them with our delete clause
+ * path. The original paths will be children to the new delete path.
+ */
static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte)
{
@@ -132,10 +170,11 @@ static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel,
cp = create_cypher_create_path(root, rel, custom_private);
- // Discard any pre-existing paths
+ // Discard any pre-existing paths, they should be under the cp path
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
+ // Add the new path to the rel.
add_path(rel, (Path *)cp);
}
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index f0bd7dd..04cc006 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -216,6 +216,12 @@ static cypher_update_information *transform_cypher_set_item_list(cypher_parsesta
static cypher_update_information *transform_cypher_remove_item_list(cypher_parsestate *cpstate,
List *remove_item_list,
Query *query);
+// delete
+static Query *transform_cypher_delete(cypher_parsestate *cpstate,
+ cypher_clause *clause);
+static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate,
+ List *delete_item_list,
+ Query *query);
// transform
#define PREV_CYPHER_CLAUSE_ALIAS "_"
#define transform_prev_cypher_clause(cpstate, prev_clause) \
@@ -270,7 +276,7 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate,
else if (is_ag_node(self, cypher_set))
return transform_cypher_set(cpstate, clause);
else if (is_ag_node(self, cypher_delete))
- return NULL;
+ return transform_cypher_delete(cpstate, clause);
else if (is_ag_node(self, cypher_sub_pattern))
result = transform_cypher_sub_pattern(cpstate, clause);
else
@@ -282,6 +288,126 @@ Query *transform_cypher_clause(cypher_parsestate *cpstate,
return result;
}
+/*
+ * Transform the Delete clause. Creates a _cypher_delete_clause
+ * and passes the necessary information that is needed in the
+ * execution phase.
+ */
+static Query *transform_cypher_delete(cypher_parsestate *cpstate,
+ cypher_clause *clause)
+{
+ ParseState *pstate = (ParseState *)cpstate;
+ cypher_delete *self = (cypher_delete *)clause->self;
+ Query *query;
+ TargetEntry *tle;
+ Oid func_set_oid;
+ Const *pattern_const;
+ Expr *func_expr;
+ RangeTblEntry *rte;
+ int rtindex;
+
+ cypher_delete_information *delete_data;
+
+ delete_data = palloc0(sizeof(cypher_delete_information));
+
+ query = makeNode(Query);
+ query->commandType = CMD_SELECT;
+ query->targetList = NIL;
+
+ if (!clause->prev)
+ {
+ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DELETE cannot be the first clause in a Cypher query"),
+ parser_errposition(pstate, self->location)));
+ }
+
+ rte = transform_prev_cypher_clause(cpstate, clause->prev);
+ rtindex = list_length(pstate->p_rtable);
+
+ // rte is the first RangeTblEntry in pstate
+ Assert(rtindex == 1);
+
+ query->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
+
+ func_set_oid = get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID);
+
+ delete_data->delete_items = transform_cypher_delete_item_list(cpstate, self->exprs, query);
+ delete_data->graph_name = cpstate->graph_name;
+ delete_data->graph_oid = cpstate->graph_oid;
+ delete_data->detach = self->detach;
+
+ if (!clause->next)
+ delete_data->flags |= CYPHER_CLAUSE_FLAG_TERMINAL;
+
+ pattern_const = makeConst(INTERNALOID, -1, InvalidOid, 1,
+ PointerGetDatum(delete_data), false, true);
+
+ func_expr = (Expr *)makeFuncExpr(func_set_oid, AGTYPEOID,
+ list_make1(pattern_const), InvalidOid,
+ InvalidOid, COERCE_EXPLICIT_CALL);
+
+ // Create the target entry
+ tle = makeTargetEntry(func_expr, pstate->p_next_resno++,
+ "cypher_delete_clause", false);
+ query->targetList = lappend(query->targetList, tle);
+
+ query->rtable = pstate->p_rtable;
+ query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ return query;
+}
+
+/*
+ * Iterate through the list of items to delete and extract the variable name.
+ * Then find the resno that the variable name belongs to.
+ */
+static List *transform_cypher_delete_item_list(cypher_parsestate *cpstate,
+ List *delete_item_list, Query *query)
+{
+ ParseState *pstate = (ParseState *)cpstate;
+ List *items = NIL;
+ ListCell *lc;
+
+ foreach(lc, delete_item_list)
+ {
+ Node *expr = lfirst(lc);
+ ColumnRef *col;
+ Value *val, *pos;
+ int resno;
+
+ cypher_delete_item *item = palloc(sizeof(cypher_delete_item));
+
+ if (!IsA(expr, ColumnRef))
+ ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause")));
+
+ col = (ColumnRef *)expr;
+
+ if(list_length(col->fields) != 1)
+ ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause")));
+
+ val = linitial(col->fields);
+
+ if(!IsA(val, String))
+ ereport(ERROR, (errmsg_internal("unexpected Node for cypher_clause")));
+
+ resno = get_target_entry_resno(query->targetList, val->val.str);
+
+ if (resno == -1)
+ ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("undefined reference to variable %s in DELETE clause", val->val.str),
+ parser_errposition(pstate, col->location)));
+
+ pos = makeInteger(resno);
+
+ item->var_name = val->val.str;
+ item->entity_position = pos;
+
+ items = lappend(items, item);
+ }
+
+ return items;
+}
+
static Query *transform_cypher_set(cypher_parsestate *cpstate,
cypher_clause *clause)
{
@@ -354,7 +480,7 @@ static Query *transform_cypher_set(cypher_parsestate *cpstate,
// Create the target entry
tle = makeTargetEntry(func_expr, pstate->p_next_resno++,
- "cypher_set_clause", false);
+ SET_CLAUSE_FUNCTION_NAME, false);
query->targetList = lappend(query->targetList, tle);
query->rtable = pstate->p_rtable;
@@ -2240,6 +2366,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate,
target_nodes = palloc(sizeof(cypher_create_target_nodes));
target_nodes->flags = CYPHER_CLAUSE_FLAG_NONE;
+ target_nodes->graph_oid = cpstate->graph_oid;
query = makeNode(Query);
query->commandType = CMD_SELECT;
@@ -2258,7 +2385,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate,
target_nodes->flags |= CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE;
}
- func_create_oid = get_ag_func_oid("_cypher_create_clause", 1, INTERNALOID);
+ func_create_oid = get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID);
null_const = makeNullConst(AGTYPEOID, -1, InvalidOid);
tle = makeTargetEntry((Expr *)null_const, pstate->p_next_resno++,
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 3c7121f..debedc7 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -652,12 +652,9 @@ delete:
n = make_ag_node(cypher_delete);
n->detach = $1;
n->exprs = $3;
+ n->location = @1;
$$ = (Node *)n;
-
- ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("DELETE clause not implemented"),
- ag_scanner_errposition(@1, scanner)));
}
;
diff --git a/src/backend/utils/adt/cypher_funcs.c b/src/backend/utils/adt/cypher_funcs.c
index d17aa8b..97ed35c 100644
--- a/src/backend/utils/adt/cypher_funcs.c
+++ b/src/backend/utils/adt/cypher_funcs.c
@@ -48,3 +48,10 @@ Datum _cypher_set_clause(PG_FUNCTION_ARGS)
{
PG_RETURN_NULL();
}
+
+PG_FUNCTION_INFO_V1(_cypher_delete_clause);
+
+Datum _cypher_delete_clause(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h
index c060450..d925b8d 100644
--- a/src/include/catalog/ag_label.h
+++ b/src/include/catalog/ag_label.h
@@ -22,6 +22,8 @@
#include "postgres.h"
+#include "nodes/execnodes.h"
+
#include "catalog/ag_catalog.h"
#define Anum_ag_label_vertex_table_id 1
@@ -76,6 +78,8 @@ char *get_label_relation_name(const char *label_name, Oid label_graph);
bool label_id_exists(Oid label_graph, int32 label_id);
RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, char *label_name);
+List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid);
+
#define label_exists(label_name, label_graph) \
OidIsValid(get_label_oid(label_name, label_graph))
diff --git a/src/include/executor/cypher_executor.h b/src/include/executor/cypher_executor.h
index 7288eb8..8801593 100644
--- a/src/include/executor/cypher_executor.h
+++ b/src/include/executor/cypher_executor.h
@@ -24,10 +24,17 @@
#include "nodes/nodes.h"
#include "nodes/plannodes.h"
+#define DELETE_SCAN_STATE_NAME "Cypher Delete"
+#define SET_SCAN_STATE_NAME "Cypher Set"
+#define CREATE_SCAN_STATE_NAME "Cypher Create"
+
Node *create_cypher_create_plan_state(CustomScan *cscan);
extern const CustomExecMethods cypher_create_exec_methods;
Node *create_cypher_set_plan_state(CustomScan *cscan);
extern const CustomExecMethods cypher_set_exec_methods;
+Node *create_cypher_delete_plan_state(CustomScan *cscan);
+extern const CustomExecMethods cypher_delete_exec_methods;
+
#endif
diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h
index 3301453..8e9eb5d 100644
--- a/src/include/executor/cypher_utils.h
+++ b/src/include/executor/cypher_utils.h
@@ -20,6 +20,7 @@
#ifndef AG_CYPHER_UTILS_H
#define AG_CYPHER_UTILS_H
+#include "nodes/execnodes.h"
#include "nodes/extensible.h"
#include "nodes/nodes.h"
#include "nodes/plannodes.h"
@@ -32,6 +33,21 @@
#define ItemPointerGetDatum(X) PointerGetDatum(X)
/*
+ * When executing the children of the CREATE, SET, REMOVE, and
+ * DELETE clasues, we need to alter the command id in the estate
+ * and the snapshot. That way we can hide the modified tuples from
+ * the sub clauses that should not know what their parent clauses are
+ * doing.
+ */
+#define Increment_Estate_CommandId(estate) \
+ estate->es_output_cid++; \
+ estate->es_snapshot->curcid++;
+
+#define Decrement_Estate_CommandId(estate) \
+ estate->es_output_cid--; \
+ estate->es_snapshot->curcid--;
+
+/*
* This holds information in clauses that create or alter tuples on
* disc, this is so future clause can manipulate those tuples if
* necessary.
@@ -50,6 +66,7 @@ typedef struct cypher_create_custom_scan_state
List *tuple_info;
uint32 flags;
TupleTableSlot *slot;
+ Oid graph_oid;
} cypher_create_custom_scan_state;
typedef struct cypher_set_custom_scan_state
@@ -61,15 +78,25 @@ typedef struct cypher_set_custom_scan_state
int flags;
} cypher_set_custom_scan_state;
-PlanState *find_plan_state(CustomScanState *node, char *var_name);
+typedef struct cypher_delete_custom_scan_state
+{
+ CustomScanState css;
+ CustomScan *cs;
+ cypher_delete_information *delete_data;
+ int flags;
+ List *tuple_info;
+ List *edge_labels;
+} cypher_delete_custom_scan_state;
+
+PlanState *find_plan_state(CustomScanState *node, char *var_name, bool *is_deleted);
TupleTableSlot *populate_vertex_tts(TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *properties);
TupleTableSlot *populate_edge_tts(
TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *startid,
agtype_value *endid, agtype_value *properties);
-ResultRelInfo *create_entity_result_rel_info(CustomScanState *node, char *graph_name, char *label_name);
+ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, char *label_name);
+List *add_tuple_info(List *list, HeapTuple heap_tuple, char *var_name);
ItemPointer get_self_item_pointer(TupleTableSlot *tts);
-HeapTuple get_heap_tuple(CustomScanState *node, char *var_name);
-
+HeapTuple get_heap_tuple(CustomScanState *node, char *var_name, bool *is_deleted);
#endif
diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h
index bf22df9..c4ef763 100644
--- a/src/include/nodes/cypher_nodes.h
+++ b/src/include/nodes/cypher_nodes.h
@@ -103,6 +103,7 @@ typedef struct cypher_delete
ExtensibleNode extensible;
bool detach; // true if DETACH is specified
List *exprs; // targets of this deletion
+ int location;
} cypher_delete;
/*
@@ -204,6 +205,7 @@ typedef struct cypher_create_target_nodes
{
List *paths;
uint32 flags;
+ Oid graph_oid;
} cypher_create_target_nodes;
typedef struct cypher_create_path
@@ -294,6 +296,21 @@ typedef struct cypher_update_item
bool remove_item;
} cypher_update_item;
+typedef struct cypher_delete_information
+{
+ List *delete_items;
+ int flags;
+ char *graph_name;
+ Oid graph_oid;
+ bool detach;
+} cypher_delete_information;
+
+typedef struct cypher_delete_item
+{
+ Value *entity_position;
+ char *var_name;
+} cypher_delete_item;
+
/* grammar node for typecasts */
typedef struct cypher_typecast
{
diff --git a/src/include/optimizer/cypher_createplan.h b/src/include/optimizer/cypher_createplan.h
index 5e5ebf5..b3bba9f 100644
--- a/src/include/optimizer/cypher_createplan.h
+++ b/src/include/optimizer/cypher_createplan.h
@@ -32,4 +32,8 @@ Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel,
CustomPath *best_path, List *tlist,
List *clauses, List *custom_plans);
+Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
+ CustomPath *best_path, List *tlist,
+ List *clauses, List *custom_plans);
+
#endif
diff --git a/src/include/optimizer/cypher_pathnode.h b/src/include/optimizer/cypher_pathnode.h
index 1dac249..79003ef 100644
--- a/src/include/optimizer/cypher_pathnode.h
+++ b/src/include/optimizer/cypher_pathnode.h
@@ -23,9 +23,15 @@
#include "nodes/pg_list.h"
#include "nodes/relation.h"
+#define CREATE_PATH_NAME "Cypher Create"
+#define SET_PATH_NAME "Cypher Set"
+#define DELETE_PATH_NAME "Cypher Delete"
+
CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel,
List *custom_private);
CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel,
List *custom_private);
+CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
+ List *custom_private);
#endif
diff --git a/src/include/utils/ag_func.h b/src/include/utils/ag_func.h
index 83349e7..c7898c7 100644
--- a/src/include/utils/ag_func.h
+++ b/src/include/utils/ag_func.h
@@ -22,6 +22,10 @@
#include "postgres.h"
+#define CREATE_CLAUSE_FUNCTION_NAME "_cypher_create_clause"
+#define SET_CLAUSE_FUNCTION_NAME "_cypher_set_clause"
+#define DELETE_CLAUSE_FUNCTION_NAME "_cypher_delete_clause"
+
bool is_oid_ag_func(Oid func_oid, const char *func_name);
Oid get_ag_func_oid(const char *func_name, const int nargs, ...);
Oid get_pg_func_oid(const char *func_name, const int nargs, ...);
diff --git a/src/include/utils/graphid.h b/src/include/utils/graphid.h
index d26807b..6327ce5 100644
--- a/src/include/utils/graphid.h
+++ b/src/include/utils/graphid.h
@@ -23,11 +23,13 @@
#include "postgres.h"
#include "fmgr.h"
+#include "utils/fmgroids.h"
#include "utils/syscache.h"
#include "catalog/ag_namespace.h"
typedef int64 graphid;
+#define F_GRAPHIDEQ F_INT8EQ
#define LABEL_ID_MIN 1
#define LABEL_ID_MAX PG_UINT16_MAX
@@ -58,6 +60,9 @@ typedef int64 graphid;
(GetSysCacheOid2(TYPENAMENSP, CStringGetDatum("_graphid"), \
ObjectIdGetDatum(ag_catalog_namespace_id())))
+#define GET_LABEL_ID(id) \
+ (((uint64)id) >> ENTRY_ID_BITS)
+
graphid make_graphid(const int32 label_id, const int64 entry_id);
int32 get_graphid_label_id(const graphid gid);
int64 get_graphid_entry_id(const graphid gid);