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);