Subject: Re: SQL Property Graph Queries (SQL/PGQ)

From: Ajay Pal <[email-masked]>
Date: Tue, 13 Aug 2024 15:22:43 +0530

Hello,

With the attached patch found below error when try to use "Any
directed edge" syntax.

postgres=# SELECT * FROM GRAPH_TABLE (students_graph
postgres(#   MATCH
postgres(#   (a IS person )  - [] - (b IS person)
postgres(#   COLUMNS (a.name AS person_a, b.name AS person_b)
postgres(# );
ERROR:  unsupported element pattern kind: undirected edge

If this syntax is supported then should behave as below,

PERSON_A   PERSON_B
---------- ----------
Bob    John
John    Mary
Alice    Mary
Mary    Bob
Mary    John
Bob    Mary
John    Bob
Mary    Alice

8 rows selected.

Attaching the sql file for reference.

Thanks
Ajay

On Sat, Aug 10, 2024 at 2:52 PM Imran Zaheer <[email-masked]> wrote:
>
> Hi Ashutosh,
>
> Thanks for the feedback.
>
> > Do you intend to support EXCEPT in the label expression as well or
> > just properties?
> >
>
> I only implemented it for the properties because I couldn't find any
> example for Label expression using EXCEPT clause. So I thought it was
> only meant to be for the properties.
> But if you can confirm that we do use EXCEPT clauses with label
> expressions as well then I can try supporting that too.
>
> >
> > Please do not top-post on hackers.
> >
> > Always sent the whole patchset. Otherwise, CI bot gets confused. It
> > doesn't pick up patchset from the previous emails.
> >
> Okay, I will take care of that.
>
> > About the functionality: It's not clear to me whether an EXCEPT should
> > be applicable only at the time of property graph creation or it should
> > be applicable always. I.e. when a property graph is dumped, should it
> > have EXCEPT in it or have a list of columns surviving except list?
> > What if a column in except list is dropped after creating a property
> > graph?
> >
>
> I did some testing on that,  for now we are just dumping the columns
> surviving the except list.
> If an exceptional table column is deleted afterwards it doesn't show
> any effect on the graph. I also tested this scenario with duckdb pgq
> extension [1], deleting the col doesn't affect the graph.
>
> > Some comments on the code
>
> I am attaching a new patch after trying to fix according to you comments
>
> > 1. You could use list_member() in insert_property_records() to check
> > whether a given column is in the list of exceptions after you have
> > enveloped in String node.
>
> * I have changed to code to use list_member(), but I have to make
> ResTarget->name from `pstrdup(NameStr(att->attname));` to `NULL`
> We are using `xml_attribute_list` for our columns list and while
> making this list in gram.y we are assigning `rt->name` as NULL [2],
> this causes list_member() func to fail while comparing except_list
> nodes. That's why I am changing rt->name from string value to NULL in
> propgraphcmds.c in this patch.
>
> * Also, in order to use list_member() func I have to add a separate
> for loop to iterate through the exceptional columns to generate the
> error message if col is not valid. My question is, is it ok to use two
> separate for loops (one to check except cols validity &
> other(list_memeber) to check existence of scanned col in except list).
> In the previous patch I was using single for loop to validate both
> things.
>
> > 2. The SELECT with GRAPH_TABLE queries are tested in graph_table.sql.
> > We don't include those in create_property_graph.sql
>
> * I have moved the graph_table queries from create_property_graph.sql
> to graph_table.sql.
> * But in graph_table.sql I didn't use the existing graphs because
> those graphs and tables look like there for some specific test
> scenario, so I created my separate graph and table for my test
> scenario. I didn't drop the graph and the table as we will be dropping
> the schema at the end but Peter E has this comment "-- leave for
> pg_upgrade/pg_dump tests".
>
> > 3. Instead of creating a new property graph in the test, you may
> > modify one of the existing property graphs to have a label with except
> > list and then query it.
> >
>
> * I have modified the graphs in create_property_graph.sql in order to
> test except list cols in the alter command and create graph command.
>
> > We are aiming a minimal set of features in the first version. I will
> > let Peter E. decide whether to consider this as minimal set feature or
> > not. The feature looks useful to me.
>
> Thanks if you find this patch useful. I am attaching the modified patch.
>
> > 0001 - same as previous one
> > 0002 - fixes pgperltidy complaints
> > 0003 - fixes compilation failure
> > 0004 - fixes issue seen on CI
> > 0005 - adds support for WHERE clause in graph pattern missing in the
> > first patch.
> > 0006 - adds full support for cyclic path patterns
>
> 0007 - adds support for except cols list in graph properties
>
> Thanks
> Imran Zaheer
>
> [1]: https://github.com/cwida/duckpgq-extension
> [2]: https://github.com/postgres/postgres/blob/f5a1311fccd2ed24a9fb42aa47a17d1df7126039/src/backend/parser/gram.y#L16166

---

From: Ajay Pal <[email-masked]>
Date: Tue, 13 Aug 2024 16:08:07 +0530

Hello,

Further testing found that using a property graph with the plpgsql
function crashed the server. Please take a look at the attached SQL
file for reference tables.

postgres=# create or replace function func() returns int as
postgres-# $$
postgres$# declare person_av varchar;
postgres$# begin
postgres$#
postgres$#         SELECT person_a into person_av FROM GRAPH_TABLE
(students_graph
postgres$#           MATCH
postgres$#           (a IS person) -[e IS friends]-> (b IS person
WHERE b.name = 'Bob')
postgres$#           WHERE a.name='John'
postgres$#           COLUMNS (a.name AS person_a, b.name AS person_b)
postgres$#         );
postgres$#
postgres$#         return person_av;
postgres$# end
postgres$# $$ language plpgsql;
CREATE FUNCTION
postgres=# select func();
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
The connection to the server was lost. Attempting reset: Failed.
!?>

Please let me know if you need more details.

Thanks
Ajay

---

From: Ajay Pal <[email-masked]>
Date: Tue, 20 Aug 2024 17:20:41 +0530

Hi All,

When we use a graph table and any local table, the server crashes.
Please note, It is happening when using the where clause for the local
table only.

postgres=# SELECT * FROM customers a, GRAPH_TABLE (myshop2 MATCH (c IS
customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders)
COLUMNS (c.name_redacted AS customer_name_redacted));
 customer_id |   name    | address | customer_name_redacted
-------------+-----------+---------+------------------------
           1 | customer1 | US      | redacted1
           2 | customer2 | CA      | redacted1
           3 | customer3 | GL      | redacted1
(3 rows)

postgres=# SELECT * FROM customers a, GRAPH_TABLE (myshop2 MATCH (c IS
customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders)
COLUMNS (c.name_redacted AS customer_name_redacted)) where
a.customer_id=1;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
The connection to the server was lost. Attempting reset: Failed.
!?> \q

Note:- I have referred to graph_table.sql to get the table structure
used in the above query.

Thanks
Ajay

---

From: Ashutosh Bapat <[email-masked]>
Date: Wed, 28 Aug 2024 15:48:46 +0530

> ERROR:  unsupported element pattern kind: undirected edge

Earlier patches treated syntax "-[]- " as undirected edge and didn't
support it. Per standard it is specifies an edge in either direction
which is equivalent of -[]-> OR <-[]-. Implemented in the attached
patches. Also added a test case in graph_table.sql.

> The connection to the server was lost. Attempting reset: Failed.

Nice catch. The crash happens because earlier patches implemented
parser hooks to resolve graph property references. Those
implementations conflicted with the same hooks implemented in plpgsql
code. The attached patches fix this by adding a member to ParseState
instead of using hooks. Once this was fixed, there was another
problem. Property graph referenced in GRAPH_TABLE was not being
locked. That problem is fixed in the attached patches as well.

>  customer_id |   name    | address | customer_name_redacted

This problem is not reproducible after fixing other problem. Please
let me know if it's reproduces for you. If it reproduces please
provide a patch adding the reproduction to graph_table.sql.

Along with this I have rebased the patches on the latest HEAD, fixed
some comments, code styles etc.

Patches 0001 - 0006 are same as the previous set.
0007 - fixes all the problems you reported till now and also the one I
found. The commit message describes the fixes in detail.

-- 
Best Wishes,
Ashutosh Bapat
From c0bd564e9d8d4a24f295dc1039121b46b2300eb9 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Fri, 19 Jul 2024 21:32:30 +0530
Subject: [PATCH 5/8] Fix spurious column not found error

Attribute name from a heap tuple needs to be copied before releasing the
resources held by the tuple and the table scan.

Ashutosh Bapat
---
 src/backend/commands/propgraphcmds.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index f204fc3125..6b15749c5e 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -726,7 +726,7 @@ insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGra
 			cr = makeNode(ColumnRef);
 			rt = makeNode(ResTarget);
 
-			cr->fields = list_make1(makeString(NameStr(att->attname)));
+			cr->fields = list_make1(makeString(pstrdup(NameStr(att->attname))));
 			cr->location = -1;
 
 			rt->name = pstrdup(NameStr(att->attname));
-- 
2.34.1

From d4d53dd68a1ef61890969bc402ffaf579ba9e4f9 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 8 Jul 2024 18:45:50 +0530
Subject: [PATCH 4/8] Fix compilation error

After 6e1c4a03a978ed3574124d8f2be22ba2e5a4b1e9
binary_upgrade_set_pg_class_oid() doesn't need the last argument. Fix the call.
---
 src/bin/pg_dump/pg_dump.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 528c61e5b4..e08e80b7df 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15831,7 +15831,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
 		if (dopt->binary_upgrade)
 			binary_upgrade_set_pg_class_oids(fout, q,
-											 tbinfo->dobj.catId.oid, false);
+											 tbinfo->dobj.catId.oid);
 
 		appendPQExpBuffer(query,
 						  "SELECT pg_catalog.pg_get_propgraphdef('%u'::pg_catalog.oid) AS pgdef",
-- 
2.34.1

From 6e6c8350c23248d7169e6daf9e9acb718e2b4e32 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 8 Jul 2024 11:53:25 +0530
Subject: [PATCH 6/8] support WHERE clause in graph pattern

---
 src/backend/rewrite/rewriteGraphTable.c   |  9 +++++++
 src/test/regress/expected/graph_table.out | 33 +++++++++++++++++++----
 src/test/regress/sql/graph_table.sql      | 14 ++++++++++
 3 files changed, 51 insertions(+), 5 deletions(-)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index ee11594d2c..4b787a2e88 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -310,6 +310,15 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 		qual_exprs = list_concat(qual_exprs, gpe->qual_exprs);
 	}
 
+	if (rte->graph_pattern->whereClause)
+	{
+		Node	   *path_quals = replace_property_refs(rte->relid,
+													   (Node *) rte->graph_pattern->whereClause,
+													   graph_path);
+
+		qual_exprs = lappend(qual_exprs, path_quals);
+	}
+
 	path_query->jointree = makeFromExpr(fromlist,
 										(Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1));
 
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 813fc9aa25..de0d163e83 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -237,6 +237,12 @@ create table e1_2 (id_1 int,
 					id_2_2 int,
 					ename varchar(10),
 					eprop1 int);
+-- edge connecting v2 and v1
+create table e2_1 (id_2_1 int,
+					id_2_2 int,
+                    id_1 int,
+					ename varchar(10),
+					eprop1 int);
 -- edge connecting v1 and v3
 create table e1_3 (id_1 int,
 					id_3 int,
@@ -268,6 +274,11 @@ edge tables (
 		destination key (id_2_1, id_2_2) references v2 (id1, id2)
 		label el1 properties (eprop1, ename)
         label l1 properties (ename as elname),
+	e2_1 key (id_2_1, id_2_2, id_1)
+		source key (id_2_1, id_2_2) references v2 (id1, id2)
+		destination key (id_1) references v1 (id)
+		label el1 properties (eprop1, ename)
+        label l1 properties (ename as elname),
 	e1_3
 		source key (id_1) references v1 (id)
 		destination key (id_3) references v3 (id)
@@ -296,17 +307,18 @@ insert into e1_2 values (1, 1000, 2, 'e121', 10001),
 insert into e1_3 values (1, 2003, 'e131', 10003),
                         (1, 2001, 'e132', 10004);
 insert into e2_3 values (1000, 2, 2002, 'e231', 10005);
+insert into e2_1 values (1000, 1, 2, 'e211', 10006);
 -- empty element path pattern, counts number of edges in the graph
 SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 as one));
  count 
 -------
-     5
+     6
 (1 row)
 
 SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()->() COLUMNS (1 as one));
  count 
 -------
-     5
+     6
 (1 row)
 
 -- Vertex element v2 has label vl3 which exposes property vprop1. But vl3 is
@@ -337,6 +349,14 @@ select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is
  v11 | e132 | v31  | vl3_prop |        |   2010
 (4 rows)
 
+-- WHERE clause in graph pattern
+SELECT self, through FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname and a.vname <> b.vname COLUMNS (a.vname as self, b.vname as through));
+ self | through 
+------+---------
+ v12  | v21
+ v21  | v12
+(2 rows)
+
 -- Errors
 -- vl1 is not associated with property vprop2
 select src, src_vprop2, conn, dest from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, a.vprop2 as src_vprop2, b.ename as conn, c.vname as dest));
@@ -362,8 +382,9 @@ select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as s
  v11    | e121   | v22    |   10 |      |          | 1020 | 1200 | vl2_prop | 10001 |       
  v11    | e131   | v33    |   10 |      |          | 2030 |      | vl3_prop | 10003 |       
  v11    | e132   | v31    |   10 |      |          | 2010 |      | vl3_prop | 10004 |       
+ v21    | e211   | v12    | 1010 | 1100 | vl2_prop |   20 |      |          | 10006 |       
  v22    | e231   | v32    | 1020 | 1200 | vl2_prop | 2020 |      | vl3_prop |       | 100050
-(5 rows)
+(6 rows)
 
 -- three label disjunction
 select * from graph_table (g1 match (src IS vl1 | vl2 | vl3)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname));
@@ -373,8 +394,9 @@ select * from graph_table (g1 match (src IS vl1 | vl2 | vl3)-[conn]->(dest) colu
  v11    | e121   | v22
  v11    | e131   | v33
  v11    | e132   | v31
+ v21    | e211   | v12
  v22    | e231   | v32
-(5 rows)
+(6 rows)
 
 -- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination.
 with all_connected_vertices as (select svn, dvn from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svn, dest.vname as dvn))),
@@ -394,8 +416,9 @@ select sn, cn, dn from graph_table (g1 match (src : l1)-[conn : l1]->(dest : l1)
  v11 | e121 | v22
  v11 | e131 | v33
  v11 | e132 | v31
+ v21 | e211 | v12
  v22 | e231 | v32
-(5 rows)
+(6 rows)
 
 -- property graph with some of the elements, labels and properties same as the
 -- previous one. Test whether components from the specified property graph are
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 2b43b136ea..486594a993 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -169,6 +169,12 @@ create table e1_2 (id_1 int,
 					id_2_2 int,
 					ename varchar(10),
 					eprop1 int);
+-- edge connecting v2 and v1
+create table e2_1 (id_2_1 int,
+					id_2_2 int,
+                    id_1 int,
+					ename varchar(10),
+					eprop1 int);
 
 -- edge connecting v1 and v3
 create table e1_3 (id_1 int,
@@ -203,6 +209,11 @@ edge tables (
 		destination key (id_2_1, id_2_2) references v2 (id1, id2)
 		label el1 properties (eprop1, ename)
         label l1 properties (ename as elname),
+	e2_1 key (id_2_1, id_2_2, id_1)
+		source key (id_2_1, id_2_2) references v2 (id1, id2)
+		destination key (id_1) references v1 (id)
+		label el1 properties (eprop1, ename)
+        label l1 properties (ename as elname),
 	e1_3
 		source key (id_1) references v1 (id)
 		destination key (id_3) references v3 (id)
@@ -236,6 +247,7 @@ insert into e1_2 values (1, 1000, 2, 'e121', 10001),
 insert into e1_3 values (1, 2003, 'e131', 10003),
                         (1, 2001, 'e132', 10004);
 insert into e2_3 values (1000, 2, 2002, 'e231', 10005);
+insert into e2_1 values (1000, 1, 2, 'e211', 10006);
 
 -- empty element path pattern, counts number of edges in the graph
 SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 as one));
@@ -250,6 +262,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname,
 a.vprop1));
 -- vprop2 is associated with vl2 but not vl3
 select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, b.ename as conn, c.vname as dest, c.lprop1, c.vprop2, c.vprop1));
+-- WHERE clause in graph pattern
+SELECT self, through FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname and a.vname <> b.vname COLUMNS (a.vname as self, b.vname as through));
 
 -- Errors
 -- vl1 is not associated with property vprop2
-- 
2.34.1

From 3f889c25e3574b555757d53635c2aa42028544e7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Thu, 27 Jun 2024 14:10:44 +0200
Subject: [PATCH 2/8] WIP: SQL Property Graph Queries (SQL/PGQ)

Implementation of SQL property graph queries, according to SQL/PGQ
standard (ISO 9075-16:2023).

Author: Peter Eisentraut <[email-masked]>
Author: Ashutosh Bapat <[email-masked]>
Discussion: https://www.postgresql.org/message-id/flat/[email-masked]
---
 doc/src/sgml/catalogs.sgml                    |  471 ++++-
 doc/src/sgml/ddl.sgml                         |  225 ++-
 doc/src/sgml/features.sgml                    |    4 +-
 doc/src/sgml/func.sgml                        |   15 +
 doc/src/sgml/information_schema.sgml          | 1092 +++++++++++
 .../sgml/keywords/sql2023-16-nonreserved.txt  |   27 +
 doc/src/sgml/keywords/sql2023-16-reserved.txt |   12 +
 doc/src/sgml/queries.sgml                     |  168 ++
 doc/src/sgml/ref/allfiles.sgml                |    3 +
 doc/src/sgml/ref/alter_extension.sgml         |    3 +-
 doc/src/sgml/ref/alter_property_graph.sgml    |  299 +++
 doc/src/sgml/ref/comment.sgml                 |    1 +
 doc/src/sgml/ref/create_property_graph.sgml   |  310 +++
 doc/src/sgml/ref/drop_property_graph.sgml     |  111 ++
 doc/src/sgml/ref/grant.sgml                   |    7 +-
 doc/src/sgml/ref/psql-ref.sgml                |   13 +-
 doc/src/sgml/ref/revoke.sgml                  |    7 +
 doc/src/sgml/ref/security_label.sgml          |    1 +
 doc/src/sgml/ref/select.sgml                  |   43 +
 doc/src/sgml/reference.sgml                   |    3 +
 src/backend/catalog/aclchk.c                  |   24 +
 src/backend/catalog/dependency.c              |   11 +
 src/backend/catalog/information_schema.sql    |  364 ++++
 src/backend/catalog/objectaddress.c           |  306 +++
 src/backend/catalog/pg_class.c                |    2 +
 src/backend/catalog/sql_features.txt          |  100 +
 src/backend/commands/Makefile                 |    1 +
 src/backend/commands/alter.c                  |   28 +-
 src/backend/commands/dropcmds.c               |    1 +
 src/backend/commands/event_trigger.c          |    2 +
 src/backend/commands/meson.build              |    1 +
 src/backend/commands/propgraphcmds.c          | 1706 +++++++++++++++++
 src/backend/commands/seclabel.c               |    1 +
 src/backend/commands/tablecmds.c              |   16 +
 src/backend/executor/execMain.c               |   15 +-
 src/backend/nodes/nodeFuncs.c                 |   72 +
 src/backend/nodes/outfuncs.c                  |    5 +
 src/backend/nodes/readfuncs.c                 |    5 +
 src/backend/optimizer/path/allpaths.c         |    4 +
 src/backend/optimizer/prep/prepjointree.c     |    8 +
 src/backend/parser/Makefile                   |    1 +
 src/backend/parser/analyze.c                  |  298 +--
 src/backend/parser/gram.y                     |  704 ++++++-
 src/backend/parser/meson.build                |    1 +
 src/backend/parser/parse_agg.c                |   11 +
 src/backend/parser/parse_clause.c             |   89 +
 src/backend/parser/parse_collate.c            |    7 +
 src/backend/parser/parse_expr.c               |    6 +
 src/backend/parser/parse_func.c               |    3 +
 src/backend/parser/parse_graphtable.c         |  209 ++
 src/backend/parser/parse_relation.c           |   94 +
 src/backend/parser/parse_target.c             |    5 +
 src/backend/parser/scan.l                     |   13 +-
 src/backend/rewrite/Makefile                  |    1 +
 src/backend/rewrite/meson.build               |    1 +
 src/backend/rewrite/rewriteGraphTable.c       | 1098 +++++++++++
 src/backend/rewrite/rewriteHandler.c          |   11 +
 src/backend/tcop/utility.c                    |   34 +
 src/backend/utils/adt/ruleutils.c             |  537 ++++++
 src/backend/utils/cache/lsyscache.c           |   38 +
 src/bin/pg_dump/common.c                      |    3 +-
 src/bin/pg_dump/pg_backup_archiver.c          |    1 +
 src/bin/pg_dump/pg_dump.c                     |   67 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |   11 +
 src/bin/psql/command.c                        |    3 +-
 src/bin/psql/describe.c                       |   43 +-
 src/bin/psql/help.c                           |    1 +
 src/bin/psql/tab-complete.c                   |   67 +-
 src/fe_utils/psqlscan.l                       |    8 +-
 src/include/catalog/Makefile                  |    7 +-
 src/include/catalog/meson.build               |    5 +
 src/include/catalog/pg_class.h                |    1 +
 src/include/catalog/pg_proc.dat               |    3 +
 src/include/catalog/pg_propgraph_element.h    |  103 +
 .../catalog/pg_propgraph_element_label.h      |   51 +
 src/include/catalog/pg_propgraph_label.h      |   51 +
 .../catalog/pg_propgraph_label_property.h     |   59 +
 src/include/catalog/pg_propgraph_property.h   |   54 +
 src/include/commands/propgraphcmds.h          |   23 +
 src/include/nodes/parsenodes.h                |  139 ++
 src/include/nodes/primnodes.h                 |   22 +
 src/include/parser/analyze.h                  |    3 +
 src/include/parser/kwlist.h                   |    9 +
 src/include/parser/parse_graphtable.h         |   31 +
 src/include/parser/parse_node.h               |    1 +
 src/include/parser/parse_relation.h           |    8 +
 src/include/rewrite/rewriteGraphTable.h       |   21 +
 src/include/tcop/cmdtaglist.h                 |    3 +
 src/include/utils/acl.h                       |    1 +
 src/include/utils/lsyscache.h                 |    3 +
 src/interfaces/ecpg/preproc/parse.pl          |    9 +-
 src/interfaces/ecpg/preproc/pgc.l             |   12 +-
 src/pl/plpgsql/src/pl_gram.y                  |    1 +
 src/test/regress/expected/alter_generic.out   |   51 +-
 .../expected/create_property_graph.out        |  496 +++++
 src/test/regress/expected/graph_table.out     |  484 +++++
 src/test/regress/expected/object_address.out  |   14 +-
 src/test/regress/expected/oidjoins.out        |   11 +
 src/test/regress/parallel_schedule            |    4 +-
 src/test/regress/sql/alter_generic.sql        |   34 +
 .../regress/sql/create_property_graph.sql     |  190 ++
 src/test/regress/sql/graph_table.sql          |  336 ++++
 src/test/regress/sql/object_address.sql       |    4 +-
 src/tools/pgindent/typedefs.list              |   21 +
 104 files changed, 10915 insertions(+), 202 deletions(-)
 create mode 100644 doc/src/sgml/keywords/sql2023-16-nonreserved.txt
 create mode 100644 doc/src/sgml/keywords/sql2023-16-reserved.txt
 create mode 100644 doc/src/sgml/ref/alter_property_graph.sgml
 create mode 100644 doc/src/sgml/ref/create_property_graph.sgml
 create mode 100644 doc/src/sgml/ref/drop_property_graph.sgml
 create mode 100644 src/backend/commands/propgraphcmds.c
 create mode 100644 src/backend/parser/parse_graphtable.c
 create mode 100644 src/backend/rewrite/rewriteGraphTable.c
 create mode 100644 src/include/catalog/pg_propgraph_element.h
 create mode 100644 src/include/catalog/pg_propgraph_element_label.h
 create mode 100644 src/include/catalog/pg_propgraph_label.h
 create mode 100644 src/include/catalog/pg_propgraph_label_property.h
 create mode 100644 src/include/catalog/pg_propgraph_property.h
 create mode 100644 src/include/commands/propgraphcmds.h
 create mode 100644 src/include/parser/parse_graphtable.h
 create mode 100644 src/include/rewrite/rewriteGraphTable.h
 create mode 100644 src/test/regress/expected/create_property_graph.out
 create mode 100644 src/test/regress/expected/graph_table.out
 create mode 100644 src/test/regress/sql/create_property_graph.sql
 create mode 100644 src/test/regress/sql/graph_table.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b654fae1b2..778807c2f1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,31 @@
       <entry>functions and procedures</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link></entry>
+      <entry>property graph elements (vertices and edges)</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-element-label"><structname>pg_propgraph_element_label</structname></link></entry>
+      <entry>property graph links between elements and labels</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-label"><structname>pg_propgraph_label</structname></link></entry>
+      <entry>property graph labels</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-label-property"><structname>pg_propgraph_label_property</structname></link></entry>
+      <entry>property graph label-specific property definitions</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-property"><structname>pg_propgraph_property</structname></link></entry>
+      <entry>property graph properties</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-publication"><structname>pg_publication</structname></link></entry>
       <entry>publications for logical replication</entry>
@@ -2122,7 +2147,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>c</literal> = composite type,
        <literal>f</literal> = foreign table,
        <literal>p</literal> = partitioned table,
-       <literal>I</literal> = partitioned index
+       <literal>I</literal> = partitioned index,
+       <literal>g</literal> = property graph
       </para></entry>
      </row>
 
@@ -6272,6 +6298,449 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
  </sect1>
 
+ <sect1 id="catalog-pg-propgraph-element">
+  <title><structname>pg_propgraph_element</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-element">
+   <primary>pg_propgraph_element</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_element</structname> stores
+   information about the vertices and edges of a property graph, collectively
+   called the elements of the property graph.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_element</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgepgid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property graph that this element belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgerelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the table to contains the data for this property graph element
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgealias</structfield> <type>name</type>
+      </para>
+      <para>
+       The alias of the element.  This is a unique identifier for the element
+       within the graph.  It is set when the property graph is defined and
+       defaults to the name of the underlying element table.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgekind</structfield> <type>char</type>
+      </para>
+      <para>
+       <literal>v</literal> for a vertex, <literal>e</literal> for an edge
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrcvertexid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, a link to the source vertex.  (Zero for a vertex.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedestvertexid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, a link to the destination vertex.  (Zero for a vertex.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgekey</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       An array of column numbers in the table referenced by
+       <structname>pgerelid</structname> that defines the key to use for this
+       element table.  (This defaults to the primary key when the property
+       graph is created.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrckey</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table referenced by
+       <structname>pgerelid</structname> that defines the source key to use
+       for this element table.  (Null for a vertex.)  The combination of
+       <structfield>pgesrckey</structfield> and
+       <structfield>pgesrcref</structfield> creates the link between the edge
+       and the source vertex.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrcref</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table reached via
+       <structname>pgesrcvertexid</structname>.  (Null for a vertex.)  The
+       combination of <structfield>pgesrckey</structfield> and
+       <structfield>pgesrcref</structfield> creates the link between the edge
+       and the source vertex.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedestkey</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table referenced by
+       <structname>pgerelid</structname> that defines the destination key to use
+       for this element table.  (Null for a vertex.)  The combination of
+       <structfield>pgedestkey</structfield> and
+       <structfield>pgedestref</structfield> creates the link between the edge
+       and the destination vertex.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedestref</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table reached via
+       <structname>pgedestvertexid</structname>.  (Null for a vertex.)  The
+       combination of <structfield>pgedestkey</structfield> and
+       <structfield>pgedestref</structfield> creates the link between the edge
+       and the destination vertex.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-element-label">
+  <title><structname>pg_propgraph_element_label</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-element-label">
+   <primary>pg_propgraph_element_label</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_element_label</structname> stores
+   information about which labels apply to which elements.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_element_label</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgellabelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-label"><structname>pg_propgraph_label</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the label
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgelelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the element
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-label">
+  <title><structname>pg_propgraph_label</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-label">
+   <primary>pg_propgraph_label</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_label</structname> stores
+   information about the labels in a property graph.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_label</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pglpgid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property graph that this label belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgllabel</structfield> <type>name</type>
+      </para>
+      <para>
+       The name of the label.  This is unique among the labels in a graph.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-label-property">
+  <title><structname>pg_propgraph_label_property</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-label-property">
+   <primary>pg_propgraph_label_property</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_label_property</structname> stores
+   information about the properties in a property graph that are specific to a
+   label.  In particular, this stores the expression that defines the
+   property.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_label_property</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>plppropid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-property"><structname>pg_propgraph_property</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>plpellabelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element-label"><structname>pg_propgraph_element_label</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the label (indirectly via
+       <structname>pg_propgraph_element_label</structname>, which then links
+       to <structname>pg_propgraph_label</structname>)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>plpexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       Expression tree (in <function>nodeToString()</function> representation)
+       for the property's definition.  The expression references the table
+       reached via <structname>pg_propgraph_element_label</structname> and
+       <structname>pg_propgraph_element</structname>.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-property">
+  <title><structname>pg_propgraph_property</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-property">
+   <primary>pg_propgraph_property</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_property</structname> stores
+   information about the properties in a property graph.  This only stores
+   information that applies to a property throughout the graph, independent of
+   what label or element it is on.  Additional information, including the
+   actual expressions that define the properties are in the catalog <link
+   linkend="catalog-pg-propgraph-label-property"><structname>pg_propgraph_label_property</structname></link>.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_property</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgppgid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property graph that this property belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgpname</structfield> <type>name</type>
+      </para>
+      <para>
+       The name of the property.  This is unique among the properties in a
+       graph.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgptypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The data type of this property.  (This is required to be fixed for a
+       given property in a property graph, even if the property is defined
+       multiple times in different elements and labels.)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-publication">
   <title><structname>pg_publication</structname></title>
 
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index b671858627..867bbc2f8d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1924,7 +1924,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <para>
        Allows <command>SELECT</command> from
        any column, or specific column(s), of a table, view, materialized
-       view, or other table-like object.
+       view, property graph, or other table-like object.
        Also allows use of <command>COPY TO</command>.
        This privilege is also needed to reference existing column values in
        <command>UPDATE</command>, <command>DELETE</command>,
@@ -5260,6 +5260,229 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-property-graphs">
+  <title>Property Graphs</title>
+
+  <indexterm zone="ddl-property-graphs">
+   <primary>property graph</primary>
+  </indexterm>
+
+  <para>
+   A property graph is a way to represent database contents, instead of using
+   relational structures such as tables.  A property graph can then be queried
+   using graph pattern matching syntax, instead of join queries typical of
+   relational databases.  PostgreSQL implements SQL/PGQ<footnote><para>Here,
+   PGQ stands for <quote>property graph query</quote>.  In the jargon of graph
+   databases, <quote>property graph</quote> is normally abbreviated as PG,
+   which is clearly confusing for practioners of PostgreSQL, also usually
+   abbreviated as PG.</para></footnote>, which is part of the SQL standard,
+   where a property graph is defined as a kind of read-only view over
+   relational tables.  So the actual data is still in tables or table-like
+   objects, but is exposed as a graph for graph querying operations.  (This is
+   in contrast to native graph databases, where the data is stored directly in
+   a graph structure.)  Underneath, both relational queries and graph queries
+   use the same query planning and execution infrastucture, and in fact
+   relational and graph queries can be combined and mixed in single queries.
+  </para>
+
+  <para>
+   A graph is a set of vertices and edges.  Each edge has two distinguishable
+   associated vertices called the source and destination vertices.  (So in
+   this model, all edges are directed.)  Vertices and edges together are
+   called the elements of the graph.  A property graph extends this well-known
+   mathematical structure with a way to represent user data.  In a property
+   graph, each vertex or edge has one or more associated labels, and each
+   label has zero or more properties.  The labels are similar to table row
+   types in that they define the kind of the contained data and its structure.
+   The properties are similar to columns in that they contain the actual data.
+   In fact, by default, a property graph definition exposes the underlying
+   tables and columns as labels and properties, but more complicated
+   definitions are possible.
+  </para>
+
+  <para>
+   Consider the following table definitions:
+<programlisting>
+CREATE TABLE products (
+    product_no integer PRIMARY KEY,
+    name varchar,
+    price numeric
+);
+
+CREATE TABLE customers (
+    customer_id integer PRIMARY KEY,
+    name varchar,
+    address varchar
+);
+
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    ordered_when date
+);
+
+CREATE TABLE order_items (
+    order_items_id integer PRIMARY KEY,
+    order_id integer REFERENCES orders (order_id),
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+CREATE TABLE customer_orders (
+    customer_orders_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    order_id integer REFERENCES orders (order_id)
+);
+</programlisting>
+   When mapping this to a graph, the first three tables would be the vertices
+   and the last two tables would be the edges.  The foreign-key definitions
+   correspond to the fact that edges link two vertices.  (Graph definitions
+   work more naturally with many-to-many relationships, so this example is
+   organized like that, even though one-to-many relationships might be used
+   here in a pure relational approach.)
+  </para>
+
+  <para>
+   Here is an example how a property graph could be defined on top of these
+   tables:
+<programlisting>
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products,
+        customers,
+        orders
+    )
+    EDGE TABLES (
+        order_items SOURCE orders DESTINATION products,
+        customer_orders SOURCE customers DESTINATION orders
+    );
+</programlisting>
+  </para>
+
+  <para>
+   This graph could then be queried like this:
+<programlisting>
+-- get list of customers active today
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders]->(o IS orders WHERE ordered_when = current_date) COLUMNS (c.name AS customer_name));
+</programlisting>
+   corresponding approximately to this relational query:
+<programlisting>
+-- get list of customers active today
+SELECT customers.name FROM customers JOIN customer_orders USING (customer_id) JOIN orders USING (order_id) WHERE orders.ordered_when = current_date;
+</programlisting>
+  </para>
+
+  <para>
+   The above definition requires that all tables have primary keys and that
+   for each edge there is an appropriate foreign key.  Otherwise, additional
+   clauses have to be specified to identify the key columns.  For example,
+   this would be the fully verbose definition that does not rely on primary
+   and foreign keys:
+<programlisting>
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products KEY (product_no),
+        customers KEY (customer_id),
+        orders KEY (order_id)
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+    );
+</programlisting>
+  </para>
+
+  <para>
+   As mentioned above, by default, the names of the tables and columns are
+   exposed as labels and properties, respectively.  The clauses <literal>IS
+   customer</literal>, <literal>IS order</literal>, etc. in the
+   <literal>MATCH</literal> clause in fact refer to labels, not table names.
+  </para>
+
+  <para>
+   One use of labels is to expose a table through a different name in the
+   graph.  For example, in graphs, vertices typically have singular nouns as
+   labels and edges typically have verbs as labels, such as <quote>is</quote>,
+   <quote>has</quote>, <quote>contains</quote>, or something specific like
+   <quote>approves</quote>.  We can introduce such labels into our example
+   like this:
+<programlisting>
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products LABEL product,
+        customers LABEL customer,
+        orders LABEL order
+    )
+    EDGE TABLES (
+        order_items SOURCE orders DESTINATION products LABEL contains,
+        customer_orders SOURCE customers DESTINATION orders LABEL has
+    );
+</programlisting>
+  </para>
+
+  <para>
+   With this definition, we can write a query like this:
+<programlisting>
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c:customer)-[:has]->(o:order WHERE ordered_when = current_date) COLUMNS (c.name AS customer_name));
+</programlisting>
+   With the new labels and using the colon instead of <literal>IS</literal>,
+   which are equivalent, the <literal>MATCH</literal> clause is now more
+   compact and intuitive.
+  </para>
+
+  <para>
+   Another use is to apply the same label to multiple element tables.  For
+   example, consider this additional table:
+<programlisting>
+CREATE TABLE employees (
+    employee_id integer PRIMARY KEY,
+    employee_name varchar,
+    ...
+);
+</programlisting>
+and the following graph definition:
+<programlisting>
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products LABEL product,
+        customers LABEL customer LABEL person PROPERTIES (name),
+        orders LABEL order,
+        employees LABEL employee LABEL person PROPERTIES (employee_name AS name)
+    )
+    EDGE TABLES (
+        order_items SOURCE orders DESTINATION products LABEL contains,
+        customer_orders SOURCE customers DESTINATION orders LABEL has
+    );
+</programlisting>
+   (In practice, there ought to be an edge linking the
+   <literal>employees</literal> table to something, but it is allowed like
+   this.)  Then we can run a query like this (incomplete):
+<programlisting>
+SELECT ... FROM GRAPH_TABLE (myshop MATCH (:person WHERE name = '...')-[]->... COLUMNS (...));
+</programlisting>
+   This would automatically consider both the <literal>customers</literal> and
+   the <literal>employees</literal> tables when looking for an edge with the
+   <literal>person</literal> label.
+  </para>
+
+  <para>
+   When more than one element table has the same label, it is required that
+   the properties match in number, name, and type.  In the example, we specify
+   an explicit property list and in one case override the name of the column
+   to achieve this.
+  </para>
+
+  <para>
+   For more details on the syntax for creating property graphs, see <link
+   linkend="sql-create-property-graph"><command>CREATE PROPERTY
+   GRAPH</command></link>.  More details about the graph query syntax is in
+   <xref linkend="queries-graph"/>.
+  </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/features.sgml b/doc/src/sgml/features.sgml
index 966fd39882..1abe6ccd3d 100644
--- a/doc/src/sgml/features.sgml
+++ b/doc/src/sgml/features.sgml
@@ -70,10 +70,10 @@
 
  <para>
   The <productname>PostgreSQL</productname> core covers parts 1, 2, 9,
-  11, and 14.  Part 3 is covered by the ODBC driver, and part 13 is
+  11, 14, and 16.  Part 3 is covered by the ODBC driver, and part 13 is
   covered by the PL/Java plug-in, but exact conformance is currently
   not being verified for these components.  There are currently no
-  implementations of parts 4, 10, 15, and 16
+  implementations of parts 4, 10, and 15
   for <productname>PostgreSQL</productname>.
  </para>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 461fc3f437..2e18871132 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25996,6 +25996,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_get_propgraphdef</primary>
+        </indexterm>
+        <function>pg_get_propgraphdef</function> ( <parameter>propgraph</parameter> <type>oid</type> )
+        <returnvalue>text</returnvalue>
+       </para>
+       <para>
+        Reconstructs the creating command for a property graph.
+        (This is a decompiled reconstruction, not the original text
+        of the command.)
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index 9442b0718c..c92b65b03a 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -4171,6 +4171,1098 @@ ORDER BY c.ordinal_position;
   </table>
  </sect1>
 
+ <sect1 id="infoschema-pg-edge-table-components">
+  <title><literal>pg_edge_table_components</literal></title>
+
+  <para>
+   The view <literal>pg_edge_table_components</literal> identifies which
+   columns are part of the source or destination vertex keys, as well as their
+   corresponding columns in the vertex tables being linked to, in the edge
+   tables of property graphs defined in the current database.  Only those
+   property graphs are shown that the current user has access to (by way of
+   being the owner or having some privilege).
+  </para>
+
+  <para>
+   The source and destination vertex links of edge tables are specified in
+   <command>CREATE PROPERTY GRAPH</command> and default to foreign keys in
+   certain cases.
+  </para>
+
+  <table>
+   <title><structname>pg_edge_table_components</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>edge_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       The element table alias of the edge table being described
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vertex_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       The element table alias of the source or destination vertex table being linked to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>edge_end</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Either <literal>SOURCE</literal> or <literal>DESTINATION</literal>;
+       specifies which edge link is being described.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>edge_table_column_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the column that is part of the source or destination vertex key in this edge table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vertex_table_column_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the column that is part of the key in the source or destination
+       vertex table being linked to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ordinal_position</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Ordinal position of the columns within the key (count starts at 1)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-key-columns">
+  <title><literal>pg_element_table_key_columns</literal></title>
+
+  <para>
+   The view <literal>pg_element_key_columns</literal> identifies which columns
+   are part of the keys of the element tables of property graphs defined in
+   the current database.  Only those property graphs are shown that the
+   current user has access to (by way of being the owner or having some
+   privilege).
+  </para>
+
+  <para>
+   The key of an element table uniquely identifies the rows in it.  It is
+   either specified using the <literal>KEY</literal> clause in <command>CREATE
+   PROPERTY GRAPH</command> or defaults to the primary key.
+  </para>
+
+  <table>
+   <title><structname>pg_element_table_key_columns</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>column_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the column that is part of the key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ordinal_position</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Ordinal position of the column within the key (count starts at 1)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-labels">
+  <title><literal>pg_element_table_labels</literal></title>
+
+  <para>
+   The view <literal>pg_element_table_labels</literal> shows which labels are
+   defined on the element tables of property graphs defined in the current
+   database.  Only those property graphs are shown that the current user has
+   access to (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_element_table_labels</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>label_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the label
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-properties">
+  <title><literal>pg_element_table_properties</literal></title>
+
+  <para>
+   The view <literal>pg_element_table_labels</literal> shows the definitions
+   of the properties for the element tables of property graphs defined in the
+   current database.  Only those property graphs are shown that the current
+   user has access to (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_element_table_properties</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_expression</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Expression of the property definition for this element table
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-tables">
+  <title><literal>pg_element_tables</literal></title>
+
+  <para>
+   The view <literal>pg_element_tables</literal> contains information about
+   the element tables of property graphs defined in the current database.
+   Only those property graphs are shown that the current user has access to
+   (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_element_tables</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_kind</structfield> <type>character_data</type>
+      </para>
+      <para>
+       The kind of the element table: <literal>EDGE</literal> or <literal>VERTEX</literal>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>table_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the referenced table (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>table_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the referenced table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>table_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the table being referenced by the element table definition
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_definition</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-label-properties">
+  <title><literal>pg_label_properties</literal></title>
+
+  <para>
+   The view <literal>pg_label_properties</literal> shows which properties are
+   defined on labels defined in property graphs defined in the current
+   database.  Only those property graphs are shown that the current user has
+   access to (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_label_properties</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>label_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the label
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-labels">
+  <title><literal>pg_labels</literal></title>
+
+  <para>
+   The view <literal>pg_labels</literal> contains all the labels defined in
+   property graphs defined in the current database.  Only those property
+   graphs are shown that the current user has access to (by way of being the
+   owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_labels</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>label_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the label
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-property-data-types">
+  <title><literal>pg_property_data_types</literal></title>
+
+  <para>
+   The view <literal>pg_property_data_types</literal> shows the data types of
+   the properties in property graphs defined in the current database.  Only
+   those property graphs are shown that the current user has access to (by way
+   of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_property_data_types</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>data_type</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Data type of the property, if it is a built-in type, or
+       <literal>ARRAY</literal> if it is some array (in that case, see the
+       view <literal>element_types</literal>), else
+       <literal>USER-DEFINED</literal> (in that case, the type is identified
+       in <literal>attribute_udt_name</literal> and associated columns).
+       </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_maximum_length</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a character or bit
+       string type, the declared maximum length; null for all other
+       data types or if no maximum length was declared.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_octet_length</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a character type,
+       the maximum possible length in octets (bytes) of a datum; null
+       for all other data types.  The maximum octet length depends on
+       the declared character maximum length (see above) and the
+       server encoding.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_set_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_set_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_set_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collation_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database containing the collation of the property (always
+       the current database), null if default or the data type of the
+       property is not collatable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collation_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema containing the collation of the property, null if
+       default or the data type of the property is not collatable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collation_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the collation of the property, null if default or the data type
+       of the property is not collatable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>numeric_precision</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a numeric type, this
+       column contains the (declared or implicit) precision of the
+       type for this attribute.  The precision indicates the number of
+       significant digits.  It can be expressed in decimal (base 10)
+       or binary (base 2) terms, as specified in the column
+       <literal>numeric_precision_radix</literal>.  For all other data
+       types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>numeric_precision_radix</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a numeric type, this
+       column indicates in which base the values in the columns
+       <literal>numeric_precision</literal> and
+       <literal>numeric_scale</literal> are expressed.  The value is
+       either 2 or 10.  For all other data types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>numeric_scale</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies an exact numeric
+       type, this column contains the (declared or implicit) scale of
+       the type for this attribute.  The scale indicates the number of
+       significant digits to the right of the decimal point.  It can
+       be expressed in decimal (base 10) or binary (base 2) terms, as
+       specified in the column
+       <literal>numeric_precision_radix</literal>.  For all other data
+       types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>datetime_precision</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a date, time,
+       timestamp, or interval type, this column contains the (declared
+       or implicit) fractional seconds precision of the type for this
+       attribute, that is, the number of decimal digits maintained
+       following the decimal point in the seconds value.  For all
+       other data types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>interval_type</structfield> <type>character_data</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies an interval type,
+       this column contains the specification which fields the
+       intervals include for this attribute, e.g., <literal>YEAR TO
+       MONTH</literal>, <literal>DAY TO SECOND</literal>, etc.  If no
+       field restrictions were specified (that is, the interval
+       accepts all fields), and for all other data types, this field
+       is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>interval_precision</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Applies to a feature not available
+       in <productname>PostgreSQL</productname>
+       (see <literal>datetime_precision</literal> for the fractional
+       seconds precision of interval type properties)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_defined_type_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that the property data type is defined in
+       (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_defined_type_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that the property data type is defined in
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_defined_type_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property data type
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>scope_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>scope_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>scope_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>maximum_cardinality</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Always null, because arrays always have unlimited maximum cardinality in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>dtd_identifier</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       An identifier of the data type descriptor of the property, unique
+       among the data type descriptors pertaining to the property graph.  This
+       is mainly useful for joining with other instances of such
+       identifiers.  (The specific format of the identifier is not
+       defined and not guaranteed to remain the same in future
+       versions.)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-property-graph-privileges">
+  <title><literal>pg_property_graph_privileges</literal></title>
+
+  <para>
+   The view <literal>property_graph_privileges</literal> identifies all
+   privileges granted on property graphs to a currently enabled role or by a
+   currently enabled role.  There is one row for each combination of property
+   graph, grantor, and grantee.
+  </para>
+
+  <table>
+   <title><structname>property_graph_privileges</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>grantor</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the role that granted the privilege
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>grantee</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the role that the privilege was granted to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>privilege_type</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Type of the privilege: <literal>SELECT</literal> is the only privilege
+       type applicable to property graphs.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>is_grantable</structfield> <type>yes_or_no</type>
+      </para>
+      <para>
+       <literal>YES</literal> if the privilege is grantable, <literal>NO</literal> if not
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-property-graphs">
+  <title><literal>property_graphs</literal></title>
+
+  <para>
+   The view <literal>property_graphs</literal> contains all property graphs
+   defined in the current database.  Only those property graphs are shown that
+   the current user has access to (by way of being the owner or having some
+   privilege).
+  </para>
+
+  <table>
+   <title><structname>property_graphs</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property_graph
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="infoschema-referential-constraints">
   <title><literal>referential_constraints</literal></title>
 
diff --git a/doc/src/sgml/keywords/sql2023-16-nonreserved.txt b/doc/src/sgml/keywords/sql2023-16-nonreserved.txt
new file mode 100644
index 0000000000..39756c6067
--- /dev/null
+++ b/doc/src/sgml/keywords/sql2023-16-nonreserved.txt
@@ -0,0 +1,27 @@
+ACYCLIC
+BINDINGS
+BOUND
+DESTINATION
+DIFFERENT
+DIRECTED
+EDGE
+EDGES
+ELEMENTS
+LABEL
+LABELED
+NODE
+PATHS
+PROPERTIES
+PROPERTY
+PROPERTY_GRAPH_CATALOG
+PROPERTY_GRAPH_NAME
+PROPERTY_GRAPH_SCHEMA
+RELATIONSHIP
+RELATIONSHIPS
+SHORTEST
+SINGLETONS
+STEP
+TABLES
+TRAIL
+VERTEX
+WALK
diff --git a/doc/src/sgml/keywords/sql2023-16-reserved.txt b/doc/src/sgml/keywords/sql2023-16-reserved.txt
new file mode 100644
index 0000000000..3bdd7e2b27
--- /dev/null
+++ b/doc/src/sgml/keywords/sql2023-16-reserved.txt
@@ -0,0 +1,12 @@
+ALL_DIFFERENT
+BINDING_COUNT
+ELEMENT_ID
+ELEMENT_NUMBER
+EXPORT
+GRAPH
+GRAPH_TABLE
+MATCHNUM
+PATH_LENGTH
+PATH_NAME
+PROPERTY_EXISTS
+SAME
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 372cce1a48..41a6f423c6 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -863,6 +863,11 @@ ORDER BY p;
      to columns provided by preceding <literal>FROM</literal> items in any case.
     </para>
 
+    <para>
+     A <literal>GRAPH_TABLE</literal> <literal>FROM</literal> item can also
+     always contain lateral references.
+    </para>
+
     <para>
      A <literal>LATERAL</literal> item can appear at the top level in the
      <literal>FROM</literal> list, or within a <literal>JOIN</literal> tree.  In the latter
@@ -2745,4 +2750,167 @@ SELECT * FROM t;
 
  </sect1>
 
+ <sect1 id="queries-graph">
+  <title>Graph Queries</title>
+
+  <para>
+   This section describes the sublanguage for querying property graphs,
+   defined as described in <xref linkend="ddl-property-graphs"/>.
+  </para>
+
+  <sect2 id="queries-graph-overview">
+   <title>Overview</title>
+
+   <para>
+    Consider this example from <xref linkend="ddl-property-graphs"/>:
+<programlisting>
+-- get list of customers active today
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders]->(o IS orders WHERE o.ordered_when = current_date) COLUMNS (c.name AS customer_name));
+</programlisting>
+    The graph query part happens inside the <literal>GRAPH_TABLE</literal>
+    construct.  As far as the rest of the query is concerned, this acts like a
+    table function in that it produces a computed table as output.  Like other
+    <literal>FROM</literal> clause elements, table alias and column alias
+    names can be assigned to the result, and the result can be joined with
+    other tables, subsequently filtered, and so on, for example:
+<programlisting>
+SELECT ... FROM GRAPH_TABLE (mygraph MATCH ... COLUMNS (...)) AS myresult (a, b, c) JOIN othertable USING (a) WHERE b > 0 ORDER BY c;
+</programlisting>
+   </para>
+
+   <para>
+    The <literal>GRAPH_TABLE</literal> clause consists of the graph name,
+    followed by the keyword <literal>MATCH</literal>, followed by a graph
+    pattern expression (see below), followed by the keyword
+    <literal>COLUMNS</literal> and a column list.
+   </para>
+  </sect2>
+
+  <sect2 id="queries-graph-patterns">
+   <title>Graph Patterns</title>
+
+   <para>
+    The core of the graph querying functionality is the graph pattern, which
+    appears after the keyword <literal>MATCH</literal>.  Formally, a graph
+    pattern consists of one or more path patterns.  A path is a sequence of
+    graph elements, starting and ending with a vertex and alternating between
+    vertices and edges.  A path pattern is a syntactic expressions that
+    matches paths.
+   </para>
+
+   <para>
+    A path pattern thus matches a sequence of vertices and edges.  The
+    simplest possible path pattern is
+<programlisting>
+()
+</programlisting>
+    which matches a single vertex.  The next simplest pattern would be
+<programlisting>
+()-[]->()
+</programlisting>
+    which matches a vertex followed by an edge followed by a vertex.  The
+    characters <literal>()</literal> are a vertex pattern and the characters
+    <literal>-[]-></literal> are an edge pattern.
+   </para>
+
+   <para>
+    These characters can also be separated by whitespace, for example:
+<programlisting>
+( ) - [ ] - > ( )
+</programlisting>
+   </para>
+
+   <tip>
+    <para>
+     A way to remember these symbols is that in visual representations of
+     property graphs, vertices are usually circles (like
+     <literal>()</literal>) and edges have rectangular labels (like
+     <literal>[]</literal>).
+    </para>
+   </tip>
+
+   <para>
+    The above patterns would match any vertex, or any two vertices connected
+    by any edge, which isn't very interesting.  Normally, we want to search
+    for elements (vertices and edges) that have certain characteristics.
+    These characteristics are written in between the parentheses or brackets.
+    (This is also called an element pattern filler.)  Typically, we would
+    search for elements with a certain label.  This is written either by
+    <literal>IS <replaceable>labelname</replaceable></literal> or equivalently
+    <literal>:<replaceable>labelname</replaceable></literal>.  For example,
+    this would match all vertices with the label <literal>person</literal>:
+<programlisting>
+(IS person)
+</programlisting>
+    or
+<programlisting>
+(:person)
+</programlisting>
+    (From now on, we will just use the colon syntax, for simplicity.  But it
+    helps to read it as <quote>is</quote> for understanding.)  The next
+    example would match a vertex with the label <literal>person</literal>
+    connected to a vertex with the label <literal>account</literal> connected
+    by an edge with the label <literal>has</literal>.
+<programlisting>
+(:person)-[:has]->(:account)
+</programlisting>
+    Multiple labels can also be matched, using <quote>or</quote> semantics:
+<programlisting>
+(:person)-[:has]->(:account|creditcard)
+</programlisting>
+   </para>
+
+   <para>
+    Recall that edges are directed.  The other direction is also possible in a
+    path pattern, for example:
+<programlisting>
+(:account)&lt;-[:has]-(:person)
+</programlisting>
+    It is also possible to match both directions:
+<programlisting>
+(:person)-[:is_friend_of]-(:person)
+</programlisting>
+    This has a meaning of <quote>or</quote>: An edge in either direction would
+    match.
+   </para>
+
+   <para>
+    In many cases, the edge patterns don't need a filler.  (All the filtering
+    then happens on the vertices.)  For these cases, an abbreviated edge
+    pattern syntax is available that omits the brackets, for example:
+<programlisting>
+(:person)->(:account)
+(:acount)&lt;-(:person)
+(:person)-(:person)
+</programlisting>
+    As is often the case, abbreviated syntax can make expressions more compact
+    but also sometimes harder to understand.
+   </para>
+
+   <para>
+    Furthermore, it is possible to define graph pattern variables in the path
+    pattern expressions.  These are bound to the matched elements and can be
+    used to refer to the property values from those elements.  The most
+    important use is to use them in the <literal>COLUMNS</literal> clause to
+    define the tabular result of the <literal>GRAPH_TABLE</literal> clause.
+    For example (assuming appropriate definitions of the property graph as
+    well as the underlying tables):
+<programlisting>
+GRAPH_TABLE (mygraph MATCH (p:person)-[h:has]->(a:account)
+             COLUMNS (p.name AS person_name, h.since AS has_account_since, a.num AS account_number)
+</programlisting>
+    <literal>WHERE</literal> clauses can be used inside element patterns to
+    filter matches:
+<programlisting>
+(:person)-[:has]->(a:account WHERE a.type = 'savings')
+</programlisting>
+   </para>
+
+   <!-- TODO: multiple path patterns in a graph pattern (comma-separated) -->
+
+   <!-- TODO: quantifiers -->
+
+  </sect2>
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867..efbbf283c4 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -27,6 +27,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
 <!ENTITY alterPolicy        SYSTEM "alter_policy.sgml">
 <!ENTITY alterProcedure     SYSTEM "alter_procedure.sgml">
+<!ENTITY alterPropertyGraph SYSTEM "alter_property_graph.sgml">
 <!ENTITY alterPublication   SYSTEM "alter_publication.sgml">
 <!ENTITY alterRole          SYSTEM "alter_role.sgml">
 <!ENTITY alterRoutine       SYSTEM "alter_routine.sgml">
@@ -79,6 +80,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createOperatorFamily SYSTEM "create_opfamily.sgml">
 <!ENTITY createPolicy       SYSTEM "create_policy.sgml">
 <!ENTITY createProcedure    SYSTEM "create_procedure.sgml">
+<!ENTITY createPropertyGraph SYSTEM "create_property_graph.sgml">
 <!ENTITY createPublication  SYSTEM "create_publication.sgml">
 <!ENTITY createRole         SYSTEM "create_role.sgml">
 <!ENTITY createRule         SYSTEM "create_rule.sgml">
@@ -127,6 +129,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropOwned          SYSTEM "drop_owned.sgml">
 <!ENTITY dropPolicy         SYSTEM "drop_policy.sgml">
 <!ENTITY dropProcedure      SYSTEM "drop_procedure.sgml">
+<!ENTITY dropPropertyGraph  SYSTEM "drop_property_graph.sgml">
 <!ENTITY dropPublication    SYSTEM "drop_publication.sgml">
 <!ENTITY dropRole           SYSTEM "drop_role.sgml">
 <!ENTITY dropRoutine        SYSTEM "drop_routine.sgml">
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index c819c7bb4e..60218fcd01 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -46,6 +46,7 @@ ALTER EXTENSION <replaceable class="parameter">name</replaceable> DROP <replacea
   OPERATOR FAMILY <replaceable class="parameter">object_name</replaceable> USING <replaceable class="parameter">index_method</replaceable> |
   [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">object_name</replaceable> |
   PROCEDURE <replaceable class="parameter">procedure_name</replaceable> [ ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) ] |
+  PROPERTY GRAPH <replaceable class="parameter">object_name</replaceable> |
   ROUTINE <replaceable class="parameter">routine_name</replaceable> [ ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) ] |
   SCHEMA <replaceable class="parameter">object_name</replaceable> |
   SEQUENCE <replaceable class="parameter">object_name</replaceable> |
@@ -179,7 +180,7 @@ ALTER EXTENSION <replaceable class="parameter">name</replaceable> DROP <replacea
        The name of an object to be added to or removed from the extension.
        Names of tables,
        aggregates, domains, foreign tables, functions, operators,
-       operator classes, operator families, procedures, routines, sequences, text search objects,
+       operator classes, operator families, procedures, property graphs, routines, sequences, text search objects,
        types, and views can be schema-qualified.
       </para>
      </listitem>
diff --git a/doc/src/sgml/ref/alter_property_graph.sgml b/doc/src/sgml/ref/alter_property_graph.sgml
new file mode 100644
index 0000000000..604c518011
--- /dev/null
+++ b/doc/src/sgml/ref/alter_property_graph.sgml
@@ -0,0 +1,299 @@
+<!--
+doc/src/sgml/ref/alter_property_graph.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-alter-property-graph">
+ <indexterm zone="sql-alter-property-graph">
+  <primary>ALTER PROPERTY GRAPH</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER PROPERTY GRAPH</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER PROPERTY GRAPH</refname>
+  <refpurpose>change the definition of an SQL-property graph</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ADD
+    [ {VERTEX|NODE} TABLES ( <replaceable class="parameter">vertex_table_definition</replaceable> [, ...] ) ]
+    [ {EDGE|RELATIONSHIP} TABLES ( <replaceable class="parameter">edge_table_definition</replaceable> [, ...] ) ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> DROP
+    {VERTEX|NODE} TABLES ( <replaceable class="parameter">vertex_table_alias</replaceable> [, ...] ) [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> DROP
+    {EDGE|RELATIONSHIP} TABLES ( <replaceable class="parameter">edge_table_alias</replaceable> [, ...] ) [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    { ADD LABEL <replaceable class="parameter">label_name</replaceable> [ NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] ) ] } [ ... ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    DROP LABEL <replaceable class="parameter">label_name</replaceable> [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    ALTER LABEL <replaceable class="parameter">label_name</replaceable> ADD PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] )
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    ALTER LABEL <replaceable class="parameter">label_name</replaceable> DROP PROPERTIES ( <replaceable class="parameter">property_name</replaceable> [, ...] ) [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ALTER PROPERTY GRAPH [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER PROPERTY GRAPH</command> changes the definition of an
+   existing property graph.  There are several subforms:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>ADD {VERTEX|NODE|EDGE|RELATIONSHIP} TABLES</literal></term>
+     <listitem>
+      <para>
+       This form adds new vertex or edge tables, using the same syntax as
+       <link linkend="sql-create-property-graph"><command>CREATE PROPERTY
+       GRAPH</command></link>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>DROP {VERTEX|NODE|EDGE|RELATIONSHIP} TABLES</literal></term>
+     <listitem>
+      <para>
+       This form removes a vertex or edge table from the property graph.
+       (Only the association of the table with the graph removed.  The table
+       itself is not dropped.)
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ADD LABEL</literal></term>
+     <listitem>
+      <para>
+       This form adds a new label to an existing vertex or edge table, using
+       the same syntax as <link
+       linkend="sql-create-property-graph"><command>CREATE PROPERTY
+       GRAPH</command></link>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... DROP LABEL</literal></term>
+     <listitem>
+      <para>
+       This form removes a new label from an existing vertex or edge table.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ALTER LABEL ... ADD PROPERTIES</literal></term>
+     <listitem>
+      <para>
+       This form adds new properties to an existing label on an existing
+       vertex or edge table.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ALTER LABEL ... DROP PROPERTIES</literal></term>
+     <listitem>
+      <para>
+       This form removes properties from an existing label on an existing
+       vertex or edge table.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>OWNER</literal></term>
+     <listitem>
+      <para>
+       This form changes the owner of the property graph to the specified user.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>RENAME</literal></term>
+     <listitem>
+      <para>
+       This form changes the name of a property graph.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>SET SCHEMA</literal></term>
+     <listitem>
+      <para>
+       This form moves the property graph into another schema.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </para>
+
+  <para>
+   You must own the property graph to use <command>ALTER PROPERTY
+   GRAPH</command>.  To change a property graph's schema, you must also have
+   <literal>CREATE</literal> privilege on the new schema.  To alter the owner,
+   you must be able to <literal>SET ROLE</literal> to the new owning role, and
+   that role must have <literal>CREATE</literal> privilege on the property
+   graph's schema.  (These restrictions enforce that altering the owner
+   doesn't do anything you couldn't do by dropping and recreating the property
+   graph.  However, a superuser can alter ownership of any property graph
+   anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of a property graph to be altered.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the property graph does not exist.  A notice is
+      issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">vertex_table_definition</replaceable></term>
+    <term><replaceable class="parameter">edge_table_definition</replaceable></term>
+    <listitem>
+     <para>
+      See <link linkend="sql-create-property-graph"><command>CREATE PROPERTY
+      GRAPH</command></link>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">vertex_table_alias</replaceable></term>
+    <term><replaceable class="parameter">edge_table_alias</replaceable></term>
+    <listitem>
+     <para>
+      The alias of an existing vertex or edge table to operate on.  (Note that
+      the alias is potentially different from the name of the underlying
+      table, if the vertex or edge table was created with <literal>AS
+      <replaceable class="parameter">alias</replaceable></literal>.)
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">label_name</replaceable></term>
+    <term><replaceable class="parameter">property_name</replaceable></term>
+    <term><replaceable class="parameter">expression</replaceable></term>
+    <listitem>
+     <para>
+      See <link linkend="sql-create-property-graph"><command>CREATE PROPERTY
+      GRAPH</command></link>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The user name of the new owner of the property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name for the property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_schema</replaceable></term>
+    <listitem>
+     <para>
+      The new schema for the property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The consistency checks on a property graph described at <xref
+   linkend="sql-create-property-graph-notes"/> must be maintained by
+   <command>ALTER PROPERTY GRAPH</command> operations.  In some cases, it
+   might be necessary to make multiple alterations in a single command to
+   satisfy the checks.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (v2);
+
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL foo;
+
+ALTER PROPERTY GRAPH g1 RENAME TO g2;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE PROPERTY GRAPH</command> conforms to ISO/IEC 9075-16
+   (SQL/PGQ).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-create-property-graph"/></member>
+   <member><xref linkend="sql-drop-property-graph"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index 5b43c56b13..7b251476e2 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -47,6 +47,7 @@ COMMENT ON
   POLICY <replaceable class="parameter">policy_name</replaceable> ON <replaceable class="parameter">table_name</replaceable> |
   [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">object_name</replaceable> |
   PROCEDURE <replaceable class="parameter">procedure_name</replaceable> [ ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) ] |
+  PROPERTY GRAPH <replaceable class="parameter">object_name</replaceable>
   PUBLICATION <replaceable class="parameter">object_name</replaceable> |
   ROLE <replaceable class="parameter">object_name</replaceable> |
   ROUTINE <replaceable class="parameter">routine_name</replaceable> [ ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) ] |
diff --git a/doc/src/sgml/ref/create_property_graph.sgml b/doc/src/sgml/ref/create_property_graph.sgml
new file mode 100644
index 0000000000..f88d1194cb
--- /dev/null
+++ b/doc/src/sgml/ref/create_property_graph.sgml
@@ -0,0 +1,310 @@
+<!--
+doc/src/sgml/ref/create_property_graph.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-create-property-graph">
+ <indexterm zone="sql-create-property-graph">
+  <primary>CREATE PROPERTY GRAPH</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE PROPERTY GRAPH</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE PROPERTY GRAPH</refname>
+  <refpurpose>define an SQL-property graph</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE [ TEMP | TEMPORARY ] PROPERTY GRAPH <replaceable class="parameter">name</replaceable>
+    [ {VERTEX|NODE} TABLES ( <replaceable class="parameter">vertex_table_definition</replaceable> [, ...] ) ]
+    [ {EDGE|RELATIONSHIP} TABLES ( <replaceable class="parameter">edge_table_definition</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">vertex_table_definition</replaceable> is:</phrase>
+
+    <replaceable class="parameter">vertex_table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ] [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] [ <replaceable class="parameter">element_table_label_and_properties</replaceable> ]
+
+<phrase>and <replaceable class="parameter">edge_table_definition</replaceable> is:</phrase>
+
+    <replaceable class="parameter">edge_table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ] [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
+        SOURCE [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) REFERENCES ] <replaceable class="parameter">source_table</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
+        DESTINATION [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) REFERENCES ] <replaceable class="parameter">dest_table</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
+        [ <replaceable class="parameter">element_table_label_and_properties</replaceable> ]
+
+<phrase>and <replaceable class="parameter">element_table_label_and_properties</replaceable> is either:</phrase>
+
+    NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] )
+
+<phrase>or:</phrase>
+
+   { { LABEL <replaceable class="parameter">label_name</replaceable> | DEFAULT LABEL } [ NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] ) ] } [...]
+</synopsis>
+</refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE PROPERTY GRAPH</command> defines a property graph.  A
+   property graph consists of vertices and edges, together called elements,
+   each with associated labels and properties, and can be queried using the
+   <literal>GRAPH_TABLE</literal> clause of <xref linkend="sql-select"/> with
+   a special path matching syntax.  The data in the graph is stored in regular
+   tables (or views, foreign tables, etc.).  Each vertex or edge corresponds
+   to a table.  The property graph definition links these tables together into
+   a graph structure that can be queried using graph query techniques.
+  </para>
+
+  <para>
+   <command>CREATE PROPERTY GRAPH</command> does not physically materialize a
+   graph.  It is thus similar to <command>CREATE VIEW</command> in that it
+   records a structure that is used only when the defined object is queried.
+  </para>
+
+  <para>
+   If a schema name is given (for example, <literal>CREATE PROPERTY GRAPH
+   myschema.mygraph ...</literal>) then the property graph is created in the
+   specified schema.  Otherwise it is created in the current schema.
+   Temporary property graphs exist in a special schema, so a schema name
+   cannot be given when creating a temporary property graph.  Property graphs
+   share a namespace with tables and other relation types, so the name of the
+   property graph must be distinct from the name of any other relation (table,
+   sequence, index, view, materialized view, or foreign table) in the same
+   schema.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the new property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>VERTEX</literal>/<literal>NODE</literal></term>
+    <term><literal>EDGE</literal>/<literal>RELATIONSHIP</literal></term>
+    <listitem>
+     <para>
+      These keywords are synonyms, respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">vertex_table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a table that will contain vertices in the new property
+      graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">edge_table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a table that will contain edges in the new property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">alias</replaceable></term>
+    <listitem>
+     <para>
+      A unique identifier for the vertex or edge table.  This defaults to the
+      name of the table.  Aliases must be unique in a property graph
+      definition (across all vertex table and edge table definitions).
+      (Therefore, if a table is used more than once as a vertex or edge table,
+      then an explicit alias must be specified for at least one of them to
+      distinguish them.)
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      A set of columns that uniquely identifies a row in the vertex or edge
+      table.  Defaults to the primary key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">source_table</replaceable></term>
+    <term><replaceable class="parameter">dest_table</replaceable></term>
+    <listitem>
+     <para>
+      The vertex tables that the edge table is linked to.  These refer to the
+      aliases of a the vertex table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) REFERENCES ... ( <replaceable class="parameter">column_name</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      Two sets of columns that connect the edge table and the source or
+      destination vertex table, like in a foreign-key relationship.  If a
+      foreign-key constraint between the two tables exists, it is used by
+      default.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">element_table_label_and_properties</replaceable></term>
+    <listitem>
+     <para>
+      Defines the labels and properties for the element (vertex or edge)
+      table.  Each element has at least one label.  By default, the label is
+      the same as the element table alias.  This can be specified explicitly
+      as <literal>DEFAULT LABEL</literal>.  Alternatively, one or more freely
+      chosen label names can be specified.  (Label names do not have to be
+      unique across a property graph.  It is can be useful to assign the same
+      label to different elements.)  Each label has a list (possibly empty) of
+      properties.  By default, all columns of a table are automatically
+      exposed as properties.  This can be specified explicitly as
+      <literal>PROPERTIES ALL COLUMNS</literal>.  Alternatively, a list of
+      expressions, which can refer to the columns of the underlying table, can
+      be specified as properties.  If the expressions are not a plain column
+      reference, then an explicit property name must also be specified.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1 id="sql-create-property-graph-notes">
+  <title>Notes</title>
+
+  <para>
+   The following consistency checks must be satisfied by a property graph definition:
+
+   <itemizedlist>
+    <listitem>
+     <para>
+      In a property graph, labels with the same name applied to different
+      property graph elements must have the same number of properties and
+      those properties must have the same names.  For example, the following
+      would be allowed:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (x, y),
+        v2 LABEL foo PROPERTIES (x, y)
+    ) ...
+</programlisting>
+      but this would not:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (x, y),
+        v2 LABEL foo PROPERTIES (z)
+    ) ...
+</programlisting></para>
+    </listitem>
+
+    <listitem>
+     <para>
+      In a property graph, all properties with the same name must have the
+      same data type, independent of which label they are on.  For example,
+      this would be allowed:
+<programlisting>
+CREATE TABLE v1 (a int, b int);
+CREATE TABLE v2 (a int, b int);
+
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (a, b),
+        v2 LABEL bar PROPERTIES (a, b)
+    ) ...
+</programlisting>
+      but this would not:
+<programlisting>
+CREATE TABLE v1 (a int, b int);
+CREATE TABLE v2 (a int, b varchar);
+
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (a, b),
+        v2 LABEL bar PROPERTIES (a, b)
+    ) ...
+</programlisting></para>
+    </listitem>
+
+    <listitem>
+     <para>
+      For each property graph element, all properties with the same name must
+      have the same expression for each label.  For example, this would be
+      allowed:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (x AS a * 2) LABEL bar PROPERTIES (x AS a * 2)
+    ) ...
+</programlisting>
+      but this would not:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (x AS a * 2) LABEL bar PROPERTIES (x AS a * 10)
+    ) ...
+</programlisting></para>
+    </listitem>
+
+   </itemizedlist>
+  </para>
+
+  <para>
+   Property graphs are queried using the <literal>GRAPH_TABLE</literal> clause
+   of <xref linkend="sql-select"/>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (v1, v2, v3)
+    EDGE TABLES (e1 SOURCE v1 DESTINATION v2,
+                 e2 SOURCE v1 DESTINATION v3);
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE PROPERTY GRAPH</command> conforms to ISO/IEC 9075-16
+   (SQL/PGQ).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alter-property-graph"/></member>
+   <member><xref linkend="sql-drop-property-graph"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/drop_property_graph.sgml b/doc/src/sgml/ref/drop_property_graph.sgml
new file mode 100644
index 0000000000..31cb77a2af
--- /dev/null
+++ b/doc/src/sgml/ref/drop_property_graph.sgml
@@ -0,0 +1,111 @@
+<!--
+doc/src/sgml/ref/drop_property_graph.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-drop-property-graph">
+ <indexterm zone="sql-drop-property-graph">
+  <primary>DROP PROPERTY GRAPH</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP PROPERTY GRAPH</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP PROPERTY GRAPH</refname>
+  <refpurpose>remove an SQL-property graph</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP PROPERTY GRAPH [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP PROPERTY GRAPH</command> drops an existing property graph.
+   To execute this command you must be the owner of the property graph.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the property graph does not exist.  A notice is
+      issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the property graph to remove.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the property graph, and in
+      turn all objects that depend on those objects (see <xref
+      linkend="ddl-depend"/>).
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the property graph if any objects depend on it.  This is
+      the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+DROP PROPERTY GRAPH g1;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP PROPERTY GRAPH</command> conforms to ISO/IEC 9075-16
+   (SQL/PGQ), except that the standard only allows one property graph to be
+   dropped per command, and apart from the <literal>IF EXISTS</literal>
+   option, which is a <productname>PostgreSQL</productname> extension..
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-create-property-graph"/></member>
+   <member><xref linkend="sql-alter-property-graph"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 999f657d5c..1132c1608c 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -82,6 +82,11 @@ GRANT { { SET | ALTER SYSTEM } [, ... ] | ALL [ PRIVILEGES ] }
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
 
+GRANT { SELECT | ALL [ PRIVILEGES ] }
+    ON PROPERTY GRAPH <replaceable>graph_name</replaceable> [, ...]
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+
 GRANT { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] }
     ON SCHEMA <replaceable>schema_name</replaceable> [, ...]
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
@@ -119,7 +124,7 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
    that grants privileges on a database object (table, column, view,
    foreign table, sequence, database, foreign-data wrapper, foreign server,
    function, procedure, procedural language, large object, configuration
-   parameter, schema, tablespace, or type), and one that grants
+   parameter, property graph, schema, tablespace, or type), and one that grants
    membership in a role.  These variants are similar in many ways, but
    they are different enough to be described separately.
   </para>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 3fd9959ed1..f6b7ccd643 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1278,7 +1278,7 @@ SELECT $1 \parse stmt1
 
         <listitem>
         <para>
-        For each relation (table, view, materialized view, index, sequence,
+        For each relation (table, view, materialized view, index, property graph, sequence,
         or foreign table)
         or composite type matching the
         <replaceable class="parameter">pattern</replaceable>, show all
@@ -1318,9 +1318,9 @@ SELECT $1 \parse stmt1
         <para>
         If <command>\d</command> is used without a
         <replaceable class="parameter">pattern</replaceable> argument, it is
-        equivalent to <command>\dtvmsE</command> which will show a list of
-        all visible tables, views, materialized views, sequences and
-        foreign tables.
+        equivalent to <command>\dtvmsEG</command> which will show a list of
+        all visible tables, views, materialized views, sequences,
+        foreign tables, and property graphs.
         This is purely a convenience measure.
         </para>
         </note>
@@ -1588,6 +1588,7 @@ SELECT $1 \parse stmt1
 
       <varlistentry id="app-psql-meta-command-de">
         <term><literal>\dE[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <term><literal>\dG[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <term><literal>\di[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <term><literal>\dm[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <term><literal>\ds[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
@@ -1596,10 +1597,10 @@ SELECT $1 \parse stmt1
 
         <listitem>
         <para>
-        In this group of commands, the letters <literal>E</literal>,
+        In this group of commands, the letters <literal>E</literal>, <literal>G</literal>,
         <literal>i</literal>, <literal>m</literal>, <literal>s</literal>,
         <literal>t</literal>, and <literal>v</literal>
-        stand for foreign table, index, materialized view,
+        stand for foreign table, index, property graph, materialized view,
         sequence, table, and view,
         respectively.
         You can specify any or all of
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 8df492281a..948ac53444 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -104,6 +104,13 @@ REVOKE [ GRANT OPTION FOR ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
     [ CASCADE | RESTRICT ]
 
+REVOKE [ GRANT OPTION FOR ]
+    { SELECT | ALL [ PRIVILEGES ] }
+    ON PROPERTY GRAPH <replaceable>graph_name</replaceable> [, ...]
+    FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+    [ CASCADE | RESTRICT ]
+
 REVOKE [ GRANT OPTION FOR ]
     { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] }
     ON SCHEMA <replaceable>schema_name</replaceable> [, ...]
diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml
index e5e5fb483e..9b97085a3f 100644
--- a/doc/src/sgml/ref/security_label.sgml
+++ b/doc/src/sgml/ref/security_label.sgml
@@ -35,6 +35,7 @@ SECURITY LABEL [ FOR <replaceable class="parameter">provider</replaceable> ] ON
   MATERIALIZED VIEW <replaceable class="parameter">object_name</replaceable> |
   [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">object_name</replaceable> |
   PROCEDURE <replaceable class="parameter">procedure_name</replaceable> [ ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) ] |
+  PROPERTY GRAPH <replaceable class="parameter">object_name</replaceable>
   PUBLICATION <replaceable class="parameter">object_name</replaceable> |
   ROLE <replaceable class="parameter">object_name</replaceable> |
   ROUTINE <replaceable class="parameter">routine_name</replaceable> [ ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) ] |
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index d7089eac0b..1c6ee99841 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -59,6 +59,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] )
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
+    GRAPH_TABLE ( <replaceable class="parameter">graph_name</replaceable> MATCH <replaceable class="parameter">graph_pattern</replaceable> COLUMNS ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">name</replaceable> ] } [, ...] ) ) [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> { ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) [ AS <replaceable class="parameter">join_using_alias</replaceable> ] }
     <replaceable class="parameter">from_item</replaceable> NATURAL <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable>
     <replaceable class="parameter">from_item</replaceable> CROSS JOIN <replaceable class="parameter">from_item</replaceable>
@@ -587,6 +588,48 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>GRAPH_TABLE ( <replaceable class="parameter">graph_name</replaceable> MATCH <replaceable class="parameter">graph_pattern</replaceable> COLUMNS ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">name</replaceable> ] } [, ...] ) )</literal></term>
+      <listitem>
+       <para>
+        This clause produces output from matching the specifying graph pattern
+        against a property graph.  See <xref linkend="ddl-property-graphs"/>
+        and <xref linkend="queries-graph"/> for more information.
+       </para>
+
+       <para>
+        <replaceable class="parameter">graph_name</replaceable> is the name
+        (optionally schema-qualified) of an existing property graph (defined
+        with <xref linkend="sql-create-property-graph"/>).
+       </para>
+
+       <para>
+        <replaceable class="parameter">graph_pattern</replaceable> is a graph
+        pattern in a special graph pattern sublanguage.  See <xref
+        linkend="queries-graph-patterns"/>.
+       </para>
+
+       <para>
+        The <literal>COLUMNS</literal> clause defines the output columns of
+        the <literal>GRAPH_TABLE</literal> clause.  <replaceable
+        class="parameter">expression</replaceable> is a scalar expression
+        using the graph pattern variables defined in the <replaceable
+        class="parameter">graph_pattern</replaceable>.  The name of the output
+        columns are specified using the <literal>AS</literal> clauses.  If the
+        expressions are simple property references, the property names are
+        used as the output names, otherwise an explicit name must be
+        specified.
+       </para>
+
+       <para>
+        Like for other <literal>FROM</literal> clause items, a table alias
+        name and column alias names may follow the <literal>GRAPH_TABLE
+        (...)</literal> clause.  (A column alias list would be redundant with
+        the <literal>COLUMNS</literal> clause.)
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83f..6de3afa7b1 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -55,6 +55,7 @@
    &alterOperatorFamily;
    &alterPolicy;
    &alterProcedure;
+   &alterPropertyGraph;
    &alterPublication;
    &alterRole;
    &alterRoutine;
@@ -107,6 +108,7 @@
    &createOperatorFamily;
    &createPolicy;
    &createProcedure;
+   &createPropertyGraph;
    &createPublication;
    &createRole;
    &createRule;
@@ -155,6 +157,7 @@
    &dropOwned;
    &dropPolicy;
    &dropProcedure;
+   &dropPropertyGraph;
    &dropPublication;
    &dropRole;
    &dropRoutine;
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a44ccee3b6..5c3f00b726 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -290,6 +290,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_PARAMETER_ACL:
 			whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			break;
+		case OBJECT_PROPGRAPH:
+			whole_mask = ACL_ALL_RIGHTS_PROPGRAPH;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", objtype);
 			/* not reached, but keep compiler quiet */
@@ -534,6 +537,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL;
 			errormsg = gettext_noop("invalid privilege type %s for parameter");
 			break;
+		case OBJECT_PROPGRAPH:
+			all_privileges = ACL_ALL_RIGHTS_PROPGRAPH;
+			errormsg = gettext_noop("invalid privilege type %s for property graph");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -604,6 +611,7 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 	{
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_PROPGRAPH:
 			ExecGrant_Relation(istmt);
 			break;
 		case OBJECT_DATABASE:
@@ -676,6 +684,7 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 	{
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_PROPGRAPH:
 			foreach(cell, objnames)
 			{
 				RangeVar   *relvar = (RangeVar *) lfirst(cell);
@@ -876,6 +885,10 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
 				objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE);
 				objects = list_concat(objects, objs);
 				break;
+			case OBJECT_PROPGRAPH:
+				objs = getRelationsInNamespace(namespaceId, RELKIND_PROPGRAPH);
+				objects = list_concat(objects, objs);
+				break;
 			case OBJECT_FUNCTION:
 			case OBJECT_PROCEDURE:
 			case OBJECT_ROUTINE:
@@ -1077,6 +1090,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
 			all_privileges = ACL_ALL_RIGHTS_SCHEMA;
 			errormsg = gettext_noop("invalid privilege type %s for schema");
 			break;
+		case OBJECT_PROPGRAPH:
+			all_privileges = ACL_ALL_RIGHTS_PROPGRAPH;
+			errormsg = gettext_noop("invalid privilege type %s for property graph");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) action->objtype);
@@ -2777,6 +2794,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_PROCEDURE:
 						msg = gettext_noop("permission denied for procedure %s");
 						break;
+					case OBJECT_PROPGRAPH:
+						msg = gettext_noop("permission denied for property graph %s");
+						break;
 					case OBJECT_PUBLICATION:
 						msg = gettext_noop("permission denied for publication %s");
 						break;
@@ -2903,6 +2923,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_PROCEDURE:
 						msg = gettext_noop("must be owner of procedure %s");
 						break;
+					case OBJECT_PROPGRAPH:
+						msg = gettext_noop("must be owner of property graph %s");
+						break;
 					case OBJECT_PUBLICATION:
 						msg = gettext_noop("must be owner of publication %s");
 						break;
@@ -3039,6 +3062,7 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
 				pg_attribute_aclmask(object_oid, attnum, roleid, mask, how);
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_PROPGRAPH:
 			return pg_class_aclmask(object_oid, roleid, mask, how);
 		case OBJECT_DATABASE:
 			return object_aclmask(DatabaseRelationId, object_oid, roleid, mask, how);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0489cbabcb..d7070d62c1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,11 @@
 #include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
@@ -1453,6 +1458,11 @@ doDeletion(const ObjectAddress *object, int flags)
 		case AccessMethodRelationId:
 		case AccessMethodOperatorRelationId:
 		case AccessMethodProcedureRelationId:
+		case PropgraphElementRelationId:
+		case PropgraphElementLabelRelationId:
+		case PropgraphLabelRelationId:
+		case PropgraphLabelPropertyRelationId:
+		case PropgraphPropertyRelationId:
 		case NamespaceRelationId:
 		case TSParserRelationId:
 		case TSDictionaryRelationId:
@@ -2163,6 +2173,7 @@ find_expr_references_walker(Node *node,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
+				case RTE_GRAPH_TABLE:
 					add_object_address(RelationRelationId, rte->relid, 0,
 									   context->addrs);
 					break;
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index c4145131ce..3dc8f0539f 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -3044,3 +3044,367 @@ CREATE VIEW user_mappings AS
     FROM _pg_user_mappings;
 
 GRANT SELECT ON user_mappings TO PUBLIC;
+
+
+-- SQL/PGQ views; these use section numbers from part 16 of the standard.
+
+/*
+ * 15.2
+ * PG_DEFINED_LABEL_SETS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.3
+ * PG_DEFINED_LABEL_SET_LABELS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.4
+ * PG_EDGE_DEFINED_LABEL_SETS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.5
+ * PG_EDGE_TABLE_COMPONENTS view
+ */
+
+CREATE VIEW pg_edge_table_components AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(eg.pgealias AS sql_identifier) AS edge_table_alias,
+           CAST(v.pgealias AS sql_identifier) AS vertex_table_alias,
+           CAST(CASE eg.end WHEN 'src' THEN 'SOURCE' WHEN 'dest' THEN 'DESTINATION' END AS character_data) AS edge_end,
+           CAST(ae.attname AS sql_identifier) AS edge_table_column_name,
+           CAST(av.attname AS sql_identifier) AS vertex_table_column_name,
+           CAST((eg.egkey).n AS cardinal_number) AS ordinal_position
+    FROM pg_namespace npg
+         JOIN
+         (SELECT * FROM pg_class WHERE relkind = 'g') AS pg
+           ON npg.oid = pg.relnamespace
+         JOIN
+         (SELECT pgepgid, pgealias, pgerelid, 'src' AS end, pgesrcvertexid AS vertexid, _pg_expandarray(pgesrckey) AS egkey, _pg_expandarray(pgesrcref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e'
+          UNION ALL
+          SELECT pgepgid, pgealias, pgerelid, 'dest' AS end, pgedestvertexid AS vertexid, _pg_expandarray(pgedestkey) AS egkey, _pg_expandarray(pgedestref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e'
+         ) AS eg
+           ON pg.oid = eg.pgepgid
+         JOIN
+         (SELECT * FROM pg_propgraph_element WHERE pgekind = 'v') AS v
+           ON eg.vertexid = v.oid
+         JOIN
+         (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS ae
+           ON eg.pgerelid = ae.attrelid AND (eg.egkey).x = ae.attnum
+         JOIN
+         (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS av
+           ON v.pgerelid = av.attrelid AND (eg.egref).x = av.attnum
+    WHERE NOT pg_is_other_temp_schema(npg.oid)
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_edge_table_components TO PUBLIC;
+
+
+/*
+ * 15.6
+ * PG_EDGE_TRIPLETS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.7
+ * PG_ELEMENT_TABLE_KEY_COLUMNS view
+ */
+
+CREATE VIEW pg_element_table_key_columns AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(pgealias AS sql_identifier) AS element_table_alias,
+           CAST(a.attname AS sql_identifier) AS column_name,
+           CAST((el.ekey).n AS cardinal_number) AS ordinal_position
+    FROM pg_namespace npg
+         JOIN
+         (SELECT * FROM pg_class WHERE relkind = 'g') AS pg
+           ON npg.oid = pg.relnamespace
+         JOIN
+         (SELECT pgepgid, pgealias, pgerelid, _pg_expandarray(pgekey) AS ekey FROM pg_propgraph_element) AS el
+           ON pg.oid = el.pgepgid
+         JOIN
+         (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS a
+           ON el.pgerelid = a.attrelid AND (el.ekey).x = a.attnum
+    WHERE NOT pg_is_other_temp_schema(npg.oid)
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_table_key_columns TO PUBLIC;
+
+
+/*
+ * 15.8
+ * PG_ELEMENT_TABLE_LABELS view
+ */
+
+CREATE VIEW pg_element_table_labels AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(e.pgealias AS sql_identifier) AS element_table_alias,
+           CAST(l.pgllabel AS sql_identifier) AS label_name
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_element_label el, pg_propgraph_label l
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND el.pgelelid = e.oid
+          AND el.pgellabelid = l.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_table_labels TO PUBLIC;
+
+
+/*
+ * 15.9
+ * PG_ELEMENT_TABLE_PROPERTIES view
+ */
+
+CREATE VIEW pg_element_table_properties AS
+    SELECT DISTINCT
+           CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(e.pgealias AS sql_identifier) AS element_table_alias,
+           CAST(pr.pgpname AS sql_identifier) AS property_name,
+           CAST(pg_get_expr(plp.plpexpr, e.pgerelid) AS character_data) AS property_expression
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_element_label el, pg_propgraph_label_property plp, pg_propgraph_property pr
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND el.pgelelid = e.oid
+          AND plp.plpellabelid = el.oid
+          AND pr.oid = plp.plppropid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_table_properties TO PUBLIC;
+
+
+/*
+ * 15.10
+ * PG_ELEMENT_TABLES view
+ */
+
+CREATE VIEW pg_element_tables AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(e.pgealias AS sql_identifier) AS element_table_alias,
+           CAST(CASE e.pgekind WHEN 'e' THEN 'EDGE' WHEN 'v' THEN 'VERTEX' END AS character_data) AS element_table_kind,
+           CAST(current_database() AS sql_identifier) AS table_catalog,
+           CAST(nt.nspname AS sql_identifier) AS table_schema,
+           CAST(t.relname AS sql_identifier) AS table_name,
+           CAST(NULL AS character_data) AS element_table_definition
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_class t, pg_namespace nt
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND e.pgerelid = t.oid
+          AND t.relnamespace = nt.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_tables TO PUBLIC;
+
+
+/*
+ * 15.11
+ * PG_LABEL_PROPERTIES view
+ */
+
+CREATE VIEW pg_label_properties AS
+    SELECT DISTINCT
+           CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(l.pgllabel AS sql_identifier) AS label_name,
+           CAST(pr.pgpname AS sql_identifier) AS property_name
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l, pg_propgraph_element_label el, pg_propgraph_label_property plp, pg_propgraph_property pr
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND el.pgelelid = e.oid
+          AND plp.plpellabelid = el.oid
+          AND pr.oid = plp.plppropid
+          AND el.pgellabelid = l.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_label_properties TO PUBLIC;
+
+
+/*
+ * 15.12
+ * PG_LABELS view
+ */
+
+CREATE VIEW pg_labels AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(l.pgllabel AS sql_identifier) AS label_name
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_label l
+    WHERE pg.relnamespace = npg.oid
+          AND l.pglpgid = pg.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_labels TO PUBLIC;
+
+
+/*
+ * 15.13
+ * PG_PROPERTY_DATA_TYPES view
+ */
+
+CREATE VIEW pg_property_data_types AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(pgp.pgpname AS sql_identifier) AS property_name,
+
+           CAST(
+             CASE WHEN t.typtype = 'd' THEN
+               CASE WHEN bt.typelem <> 0 AND bt.typlen = -1 THEN 'ARRAY'
+                    WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, null)
+                    ELSE 'USER-DEFINED' END
+             ELSE
+               CASE WHEN t.typelem <> 0 AND t.typlen = -1 THEN 'ARRAY'
+                    WHEN nt.nspname = 'pg_catalog' THEN format_type(pgp.pgptypid, null)
+                    ELSE 'USER-DEFINED' END
+             END
+             AS character_data)
+             AS data_type,
+
+           CAST(null AS cardinal_number) AS character_maximum_length,
+           CAST(null AS cardinal_number) AS character_octet_length,
+           CAST(null AS sql_identifier) AS character_set_catalog,
+           CAST(null AS sql_identifier) AS character_set_schema,
+           CAST(null AS sql_identifier) AS character_set_name,
+           CAST(null AS sql_identifier) AS collation_catalog, -- FIXME
+           CAST(null AS sql_identifier) AS collation_schema, -- FIXME
+           CAST(null AS sql_identifier) AS collation_name, -- FIXME
+           CAST(null AS cardinal_number) AS numeric_precision,
+           CAST(null AS cardinal_number) AS numeric_precision_radix,
+           CAST(null AS cardinal_number) AS numeric_scale,
+           CAST(null AS cardinal_number) AS datetime_precision,
+           CAST(null AS character_data) AS interval_type,
+           CAST(null AS cardinal_number) AS interval_precision,
+
+           CAST(current_database() AS sql_identifier) AS user_defined_type_catalog,
+           CAST(coalesce(nbt.nspname, nt.nspname) AS sql_identifier) AS user_defined_type_schema,
+           CAST(coalesce(bt.typname, t.typname) AS sql_identifier) AS user_defined_type_name,
+
+           CAST(null AS sql_identifier) AS scope_catalog,
+           CAST(null AS sql_identifier) AS scope_schema,
+           CAST(null AS sql_identifier) AS scope_name,
+
+           CAST(null AS cardinal_number) AS maximum_cardinality,
+           CAST(pgp.pgpname AS sql_identifier) AS dtd_identifier
+
+    FROM pg_propgraph_property pgp
+         JOIN (pg_class pg JOIN pg_namespace npg ON (pg.relnamespace = npg.oid)) ON pgp.pgppgid = pg.oid
+         JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON pgp.pgptypid = t.oid
+         LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON (bt.typnamespace = nbt.oid))
+           ON (t.typtype = 'd' AND t.typbasetype = bt.oid)
+
+    WHERE pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_property_data_types TO PUBLIC;
+
+
+/*
+ * 15.14
+ * PG_PROPERTY_GRAPH_PRIVILEGES view
+ */
+
+CREATE VIEW pg_property_graph_privileges AS
+    SELECT CAST(u_grantor.rolname AS sql_identifier) AS grantor,
+           CAST(grantee.rolname AS sql_identifier) AS grantee,
+           CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(nc.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(c.relname AS sql_identifier) AS property_graph_name,
+           CAST(c.prtype AS character_data) AS privilege_type,
+           CAST(
+             CASE WHEN
+                  -- object owner always has grant options
+                  pg_has_role(grantee.oid, c.relowner, 'USAGE')
+                  OR c.grantable
+                  THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_grantable
+
+    FROM (
+            SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class
+         ) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable),
+         pg_namespace nc,
+         pg_authid u_grantor,
+         (
+           SELECT oid, rolname FROM pg_authid
+           UNION ALL
+           SELECT 0::oid, 'PUBLIC'
+         ) AS grantee (oid, rolname)
+
+    WHERE c.relnamespace = nc.oid
+          AND c.relkind IN ('g')
+          AND c.grantee = grantee.oid
+          AND c.grantor = u_grantor.oid
+          AND c.prtype IN ('SELECT')
+          AND (pg_has_role(u_grantor.oid, 'USAGE')
+               OR pg_has_role(grantee.oid, 'USAGE')
+               OR grantee.rolname = 'PUBLIC');
+
+GRANT SELECT ON pg_property_graph_privileges TO PUBLIC;
+
+
+/*
+ * 15.15
+ * PG_VERTEX_DEFINED_LABEL_SETS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.16
+ * PROPERTY_GRAPHS view
+ */
+
+CREATE VIEW property_graphs AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(nc.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(c.relname AS sql_identifier) AS property_graph_name
+    FROM pg_namespace nc, pg_class c
+    WHERE c.relnamespace = nc.oid
+          AND c.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(nc.oid))
+          AND (pg_has_role(c.relowner, 'USAGE')
+               OR has_table_privilege(c.oid, 'SELECT'));
+
+GRANT SELECT ON property_graphs TO PUBLIC;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 85a7b7e641..20ffcf5c28 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -47,6 +47,11 @@
 #include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
@@ -370,6 +375,76 @@ static const ObjectPropertyType ObjectProperty[] =
 		OBJECT_OPFAMILY,
 		true
 	},
+	{
+		"property graph element",
+		PropgraphElementRelationId,
+		PropgraphElementObjectIndexId,
+		PROPGRAPHELOID,
+		PROPGRAPHELALIAS,
+		Anum_pg_propgraph_element_oid,
+		Anum_pg_propgraph_element_pgealias,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
+	{
+		"property graph element label",
+		PropgraphElementLabelRelationId,
+		PropgraphElementLabelObjectIndexId,
+		-1,
+		-1,
+		Anum_pg_propgraph_element_label_oid,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
+	{
+		"property graph label",
+		PropgraphLabelRelationId,
+		PropgraphLabelObjectIndexId,
+		PROPGRAPHLABELOID,
+		PROPGRAPHLABELNAME,
+		Anum_pg_propgraph_label_oid,
+		Anum_pg_propgraph_label_pgllabel,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
+	{
+		"property graph label property",
+		PropgraphLabelPropertyRelationId,
+		PropgraphLabelPropertyObjectIndexId,
+		-1,
+		-1,
+		Anum_pg_propgraph_label_property_oid,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
+	{
+		"property graph property",
+		PropgraphPropertyRelationId,
+		PropgraphPropertyObjectIndexId,
+		-1,
+		PROPGRAPHPROPNAME,
+		Anum_pg_propgraph_property_oid,
+		Anum_pg_propgraph_property_pgpname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
 	{
 		"role",
 		AuthIdRelationId,
@@ -679,6 +754,9 @@ static const struct object_type_map
 	{
 		"foreign table", OBJECT_FOREIGN_TABLE
 	},
+	{
+		"property graph", OBJECT_PROPGRAPH
+	},
 	{
 		"table column", OBJECT_COLUMN
 	},
@@ -814,6 +892,15 @@ static const struct object_type_map
 	{
 		"policy", OBJECT_POLICY
 	},
+	{
+		"property graph element", -1
+	},
+	{
+		"property graph label", -1
+	},
+	{
+		"property graph property", -1
+	},
 	{
 		"publication", OBJECT_PUBLICATION
 	},
@@ -948,6 +1035,7 @@ get_object_address(ObjectType objtype, Node *object,
 			case OBJECT_VIEW:
 			case OBJECT_MATVIEW:
 			case OBJECT_FOREIGN_TABLE:
+			case OBJECT_PROPGRAPH:
 				address =
 					get_relation_by_qualified_name(objtype, castNode(List, object),
 												   &relation, lockmode,
@@ -1356,6 +1444,13 @@ get_relation_by_qualified_name(ObjectType objtype, List *object,
 						 errmsg("\"%s\" is not an index",
 								RelationGetRelationName(relation))));
 			break;
+		case OBJECT_PROPGRAPH:
+			if (relation->rd_rel->relkind != RELKIND_PROPGRAPH)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("\"%s\" is not a property graph",
+								RelationGetRelationName(relation))));
+			break;
 		case OBJECT_SEQUENCE:
 			if (relation->rd_rel->relkind != RELKIND_SEQUENCE)
 				ereport(ERROR,
@@ -2271,6 +2366,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_MATVIEW:
 		case OBJECT_INDEX:
 		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_COLUMN:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_COLLATION:
@@ -2390,6 +2486,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
 		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_COLUMN:
 		case OBJECT_RULE:
 		case OBJECT_TRIGGER:
@@ -3920,6 +4017,182 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case PropgraphElementRelationId:
+			{
+				HeapTuple	tup;
+				Form_pg_propgraph_element pgeform;
+
+				tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for property graph element %u",
+						 object->objectId);
+
+				pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup);
+
+				if (pgeform->pgekind == PGEKIND_VERTEX)
+					/* translator: followed by, e.g., "property graph %s" */
+					appendStringInfo(&buffer, _("vertex %s of "), NameStr(pgeform->pgealias));
+				else if (pgeform->pgekind == PGEKIND_EDGE)
+					/* translator: followed by, e.g., "property graph %s" */
+					appendStringInfo(&buffer, _("edge %s of "), NameStr(pgeform->pgealias));
+				else
+					appendStringInfo(&buffer, "??? element %s of ", NameStr(pgeform->pgealias));
+				getRelationDescription(&buffer, pgeform->pgepgid, false);
+
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case PropgraphElementLabelRelationId:
+			{
+				Relation	rel;
+				SysScanDesc scan;
+				ScanKeyData key[1];
+				HeapTuple	tuple;
+				Form_pg_propgraph_element_label pgelform;
+				ObjectAddress oa;
+
+				rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+				ScanKeyInit(&key[0],
+							Anum_pg_propgraph_element_label_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key);
+				tuple = systable_getnext(scan);
+				if (!HeapTupleIsValid(tuple))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for element label %u", object->objectId);
+
+					systable_endscan(scan);
+					table_close(rel, AccessShareLock);
+					break;
+				}
+
+				pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+				appendStringInfo(&buffer, _("label %s of "), get_propgraph_label_name(pgelform->pgellabelid));
+				ObjectAddressSet(oa, PropgraphElementRelationId, pgelform->pgelelid);
+				appendStringInfoString(&buffer, getObjectDescription(&oa, false));
+
+				systable_endscan(scan);
+				table_close(rel, AccessShareLock);
+				break;
+			}
+
+		case PropgraphLabelRelationId:
+			{
+				Relation	rel;
+				SysScanDesc scan;
+				ScanKeyData key[1];
+				HeapTuple	tuple;
+				Form_pg_propgraph_label pglform;
+
+				rel = table_open(PropgraphLabelRelationId, AccessShareLock);
+				ScanKeyInit(&key[0],
+							Anum_pg_propgraph_label_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				scan = systable_beginscan(rel, PropgraphLabelObjectIndexId, true, NULL, 1, key);
+				tuple = systable_getnext(scan);
+				if (!HeapTupleIsValid(tuple))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for label %u", object->objectId);
+
+					systable_endscan(scan);
+					table_close(rel, AccessShareLock);
+					break;
+				}
+
+				pglform = (Form_pg_propgraph_label) GETSTRUCT(tuple);
+
+				/* translator: followed by, e.g., "property graph %s" */
+				appendStringInfo(&buffer, _("label %s of "), NameStr(pglform->pgllabel));
+				getRelationDescription(&buffer, pglform->pglpgid, false);
+
+				systable_endscan(scan);
+				table_close(rel, AccessShareLock);
+				break;
+			}
+
+		case PropgraphLabelPropertyRelationId:
+			{
+				Relation	rel;
+				SysScanDesc scan;
+				ScanKeyData key[1];
+				HeapTuple	tuple;
+				Form_pg_propgraph_label_property plpform;
+				ObjectAddress oa;
+
+				rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+				ScanKeyInit(&key[0],
+							Anum_pg_propgraph_label_property_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				scan = systable_beginscan(rel, PropgraphLabelPropertyObjectIndexId, true, NULL, 1, key);
+				tuple = systable_getnext(scan);
+				if (!HeapTupleIsValid(tuple))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for label property %u", object->objectId);
+
+					systable_endscan(scan);
+					table_close(rel, AccessShareLock);
+					break;
+				}
+
+				plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple);
+
+				appendStringInfo(&buffer, _("property %s of "), get_propgraph_property_name(plpform->plppropid));
+				ObjectAddressSet(oa, PropgraphElementLabelRelationId, plpform->plpellabelid);
+				appendStringInfoString(&buffer, getObjectDescription(&oa, false));
+
+				systable_endscan(scan);
+				table_close(rel, AccessShareLock);
+				break;
+			}
+
+		case PropgraphPropertyRelationId:
+			{
+				Relation	rel;
+				SysScanDesc scan;
+				ScanKeyData key[1];
+				HeapTuple	tuple;
+				Form_pg_propgraph_property pgpform;
+
+				rel = table_open(PropgraphPropertyRelationId, AccessShareLock);
+				ScanKeyInit(&key[0],
+							Anum_pg_propgraph_property_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				scan = systable_beginscan(rel, PropgraphPropertyObjectIndexId, true, NULL, 1, key);
+				tuple = systable_getnext(scan);
+				if (!HeapTupleIsValid(tuple))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for property %u", object->objectId);
+
+					systable_endscan(scan);
+					table_close(rel, AccessShareLock);
+					break;
+				}
+
+				pgpform = (Form_pg_propgraph_property) GETSTRUCT(tuple);
+
+				/* translator: followed by, e.g., "property graph %s" */
+				appendStringInfo(&buffer, _("property %s of "), NameStr(pgpform->pgpname));
+				getRelationDescription(&buffer, pgpform->pgppgid, false);
+
+				systable_endscan(scan);
+				table_close(rel, AccessShareLock);
+				break;
+			}
+
 		case PublicationRelationId:
 			{
 				char	   *pubname = get_publication_name(object->objectId,
@@ -4105,6 +4378,10 @@ getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 			appendStringInfo(buffer, _("foreign table %s"),
 							 relname);
 			break;
+		case RELKIND_PROPGRAPH:
+			appendStringInfo(buffer, _("property graph %s"),
+							 relname);
+			break;
 		default:
 			/* shouldn't get here */
 			appendStringInfo(buffer, _("relation %s"),
@@ -4594,6 +4871,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "policy");
 			break;
 
+		case PropgraphElementRelationId:
+			appendStringInfoString(&buffer, "property graph element");
+			break;
+
+		case PropgraphLabelRelationId:
+			appendStringInfoString(&buffer, "property graph label");
+			break;
+
+		case PropgraphPropertyRelationId:
+			appendStringInfoString(&buffer, "property graph property");
+			break;
+
 		case PublicationRelationId:
 			appendStringInfoString(&buffer, "publication");
 			break;
@@ -4675,6 +4964,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
 		case RELKIND_FOREIGN_TABLE:
 			appendStringInfoString(buffer, "foreign table");
 			break;
+		case RELKIND_PROPGRAPH:
+			appendStringInfoString(buffer, "property graph");
+			break;
 		default:
 			/* shouldn't get here */
 			appendStringInfoString(buffer, "relation");
@@ -5835,6 +6127,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case PropgraphElementRelationId:
+			appendStringInfo(&buffer, "%u TODO", object->objectId);
+			break;
+
+		case PropgraphLabelRelationId:
+			appendStringInfo(&buffer, "%u TODO", object->objectId);
+			break;
+
+		case PropgraphPropertyRelationId:
+			appendStringInfo(&buffer, "%u TODO", object->objectId);
+			break;
+
 		case PublicationRelationId:
 			{
 				char	   *pubname;
@@ -6141,6 +6445,8 @@ get_relkind_objtype(char relkind)
 			return OBJECT_MATVIEW;
 		case RELKIND_FOREIGN_TABLE:
 			return OBJECT_FOREIGN_TABLE;
+		case RELKIND_PROPGRAPH:
+			return OBJECT_PROPGRAPH;
 		case RELKIND_TOASTVALUE:
 			return OBJECT_TABLE;
 		default:
diff --git a/src/backend/catalog/pg_class.c b/src/backend/catalog/pg_class.c
index e05b0bbb2e..748f06b3ff 100644
--- a/src/backend/catalog/pg_class.c
+++ b/src/backend/catalog/pg_class.c
@@ -45,6 +45,8 @@ errdetail_relkind_not_supported(char relkind)
 			return errdetail("This operation is not supported for partitioned tables.");
 		case RELKIND_PARTITIONED_INDEX:
 			return errdetail("This operation is not supported for partitioned indexes.");
+		case RELKIND_PROPGRAPH:
+			return errdetail("This operation is not supported for property graphs.");
 		default:
 			elog(ERROR, "unrecognized relkind: '%c'", relkind);
 			return 0;
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index c002f37202..b49aad541a 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -348,6 +348,106 @@ F866	FETCH FIRST clause: PERCENT option			NO
 F867	FETCH FIRST clause: WITH TIES option			YES	
 F868	ORDER BY in grouped table			YES	
 F869	SQL implementation info population			YES	
+G000	Graph pattern			YES	SQL/PGQ required
+G001	Repeatable-elements match mode			YES	SQL/PGQ required
+G002	Different-edges match mode			NO	
+G003	Explicit REPEATABLE ELEMENTS keyword			NO	
+G004	Path variables			NO	
+G005	Path search prefix in a path pattern			NO	
+G006	Graph pattern KEEP clause: path mode prefix			NO	
+G007	Graph pattern KEEP clause: path search prefix			NO	
+G008	Graph pattern WHERE clause			YES	SQL/PGQ required
+G010	Explicit WALK keyword			NO	
+G011	Advanced path modes: TRAIL			NO	
+G012	Advanced path modes: SIMPLE			NO	
+G013	Advanced path modes: ACYCLIC			NO	
+G014	Explicit PATH/PATHS keywords			NO	
+G015	All path search: explicit ALL keyword			NO	
+G016	Any path search			NO	
+G017	All shortest path search			NO	
+G018	Any shortest path search			NO	
+G019	Counted shortest path search			NO	
+G020	Counted shortest group search			NO	
+G030	Path multiset alternation			NO	
+G031	Path multiset alternation: variable length path operands			NO	
+G032	Path pattern union			NO	
+G033	Path pattern union: variable length path operands			NO	
+G034	Path concatenation			YES	SQL/PGQ required
+G035	Quantified paths			NO	
+G036	Quantified edges			NO	
+G037	Questioned paths			NO	
+G038	Parenthesized path pattern expression			NO	
+G039	Simplified path pattern expression: full defaulting			NO	
+G040	Vertex pattern			YES	SQL/PGQ required
+G041	Non-local element pattern predicates			NO	
+G042	Basic full edge patterns			YES	SQL/PGQ required
+G043	Complete full edge patterns			NO	
+G044	Basic abbreviated edge patterns			YES	
+G045	Complete abbreviated edge patterns			NO	
+G046	Relaxed topological consistency: adjacent vertex patterns			NO	
+G047	Relaxed topological consistency: concise edge patterns			NO	
+G048	Parenthesized path pattern: subpath variable declaration			NO	
+G049	Parenthesized path pattern: path mode prefix			NO	
+G050	Parenthesized path pattern: WHERE clause			NO	
+G051	Parenthesized path pattern: non-local predicates			NO	
+G060	Bounded graph pattern quantifiers			NO	
+G061	Unbounded graph pattern quantifiers			NO	
+G070	Label expression: label disjunction			NO	SQL/PGQ required
+G071	Label expression: label conjunction			NO	
+G072	Label expression: label negation			NO	
+G073	Label expression: individual label name			YES	SQL/PGQ required
+G074	Label expression: wildcard label			NO	
+G075	Parenthesized label expression			NO	
+G080	Simplified path pattern expression: basic defaulting			NO	
+G081	Simplified path pattern expression: full overrides			NO	
+G082	Simplified path pattern expression: basic overrides			NO	
+G090	Property reference			YES	SQL/PGQ required
+G100	ELEMENT_ID function			NO	
+G110	IS DIRECTED predicate			NO	
+G111	IS LABELED predicate			NO	
+G112	IS SOURCE and IS DESTINATION predicate			NO	
+G113	ALL_DIFFERENT predicate			NO	
+G114	SAME predicate			NO	
+G115	PROPERTY_EXISTS predicate			NO	
+G120	Within-match aggregates			NO	
+G800	PATH_NAME function			NO	
+G801	ELEMENT_NUMBER function			NO	
+G802	PATH_LENGTH function			NO	
+G803	MATCHNUM function			NO	
+G810	IS BOUND predicate			NO	
+G811	IS BOUND predicate: AS option			NO	
+G820	BINDING_COUNT			NO	
+G830	Colon in 'is label' expression			YES	
+G840	Path-ordered aggregates			NO	
+G850	SQL/PGQ Information Schema views			YES	
+G860	GET DIAGNOSTICS enhancements for SQL-property graphs			NO	
+G900	GRAPH_TABLE			YES	SQL/PGQ required
+G901	GRAPH_TABLE: ONE ROW PER VERTEX			NO	
+G902	GRAPH_TABLE: ONE ROW PER STEP			NO	
+G903	GRAPH_TABLE: explicit ONE ROW PER MATCH keywords			NO	
+G904	All properties reference			NO	
+G905	GRAPH_TABLE: optional COLUMNS clause			NO	
+G906	GRAPH_TABLE: explicit EXPORT ALL			NO	
+G907	GRAPH_TABLE: EXPORT ALL EXCEPT			NO	
+G908	GRAPH_TABLE: EXPORT SINGLETONS list			NO	
+G909	GRAPH_TABLE: explicit EXPORT NO SINGLETONS			NO	
+G910	GRAPH_TABLE: 'in paths clause'			NO	
+G920	DDL-based SQL-property graphs			YES	SQL/PGQ required
+G921	Empty SQL-property graph			YES	
+G922	Views as element tables			YES	
+G923	In-line views as element tables			NO	
+G924	Explicit key clause for element tables			YES	SQL/PGQ required
+G925	Explicit label and properties clause for element tables			YES	SQL/PGQ required
+G926	More than one label for vertex tables			YES	
+G927	More than one label for edge tables			YES	
+G928	Value expressions as properties and renaming of properties			YES	
+G929	Labels and properties: EXCEPT list			NO	
+G940	Multi-sourced/destined edges			YES	
+G941	Implicit removal of incomplete edges			YES	
+G950	Alter property graph statement: ADD/DROP element table			YES	
+G960	Alter element table definition: ADD/DROP LABEL			YES	
+G970	Alter element table definition: ALTER LABEL			YES	
+G980	DROP PROPERTY GRAPH: CASCADE drop behavior			YES	
 R010	Row pattern recognition: FROM clause			NO	
 R020	Row pattern recognition: WINDOW clause			NO	
 R030	Row pattern recognition: full aggregate support			NO	
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cede90c3b9..72714b7d8c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -46,6 +46,7 @@ OBJS = \
 	portalcmds.o \
 	prepare.o \
 	proclang.o \
+	propgraphcmds.o \
 	publicationcmds.o \
 	schemacmds.o \
 	seclabel.o \
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 4f99ebb447..14deca5f54 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -379,6 +379,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_MATVIEW:
 		case OBJECT_INDEX:
 		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_PROPGRAPH:
 			return RenameRelation(stmt);
 
 		case OBJECT_COLUMN:
@@ -535,6 +536,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
+		case OBJECT_PROPGRAPH:
 			address = AlterTableNamespace(stmt,
 										  oldSchemaAddr ? &oldNspOid : NULL);
 			break;
@@ -870,6 +872,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_OPCLASS:
 		case OBJECT_OPFAMILY:
 		case OBJECT_PROCEDURE:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_ROUTINE:
 		case OBJECT_STATISTIC_EXT:
 		case OBJECT_TABLESPACE:
@@ -879,16 +882,29 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 				Relation	relation;
 				ObjectAddress address;
 
-				address = get_object_address(stmt->objectType,
-											 stmt->object,
-											 &relation,
-											 AccessExclusiveLock,
-											 false);
-				Assert(relation == NULL);
+				if (stmt->relation)
+					address = get_object_address_rv(stmt->objectType,
+													stmt->relation,
+													NIL,
+													&relation,
+													AccessExclusiveLock,
+													false);
+				else
+				{
+					address = get_object_address(stmt->objectType,
+												 stmt->object,
+												 &relation,
+												 AccessExclusiveLock,
+												 false);
+					Assert(relation == NULL);
+				}
 
 				AlterObjectOwner_internal(address.classId, address.objectId,
 										  newowner);
 
+				if (relation)
+					relation_close(relation, NoLock);
+
 				return address;
 			}
 			break;
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 85eec7e394..8d6faf1edd 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -482,6 +482,7 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 		case OBJECT_FOREIGN_TABLE:
 		case OBJECT_INDEX:
 		case OBJECT_MATVIEW:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_ROLE:
 		case OBJECT_SEQUENCE:
 		case OBJECT_SUBSCRIPTION:
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 7a5ed6b985..16fda78381 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2177,6 +2177,7 @@ stringify_grant_objtype(ObjectType objtype)
 		case OBJECT_OPERATOR:
 		case OBJECT_OPFAMILY:
 		case OBJECT_POLICY:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_PUBLICATION:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
@@ -2261,6 +2262,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
 		case OBJECT_OPFAMILY:
 		case OBJECT_PARAMETER_ACL:
 		case OBJECT_POLICY:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_PUBLICATION:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index 7549be5dc3..ad363d61e4 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -34,6 +34,7 @@ backend_sources += files(
   'portalcmds.c',
   'prepare.c',
   'proclang.c',
+  'propgraphcmds.c',
   'publicationcmds.c',
   'schemacmds.c',
   'seclabel.c',
diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
new file mode 100644
index 0000000000..f204fc3125
--- /dev/null
+++ b/src/backend/commands/propgraphcmds.c
@@ -0,0 +1,1706 @@
+/*-------------------------------------------------------------------------
+ *
+ * propgraphcmds.c
+ *	  property graph manipulation
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/commands/propgraphcmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
+#include "commands/propgraphcmds.h"
+#include "commands/tablecmds.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_target.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+struct element_info
+{
+	Oid			elementid;
+	char		kind;
+	Oid			relid;
+	char	   *aliasname;
+	ArrayType  *key;
+
+	char	   *srcvertex;
+	Oid			srcvertexid;
+	Oid			srcrelid;
+	ArrayType  *srckey;
+	ArrayType  *srcref;
+
+	char	   *destvertex;
+	Oid			destvertexid;
+	Oid			destrelid;
+	ArrayType  *destkey;
+	ArrayType  *destref;
+
+	List	   *labels;
+};
+
+
+static ArrayType *propgraph_element_get_key(ParseState *pstate, const List *keycols, Relation element_rel,
+											const char *aliasname, int location);
+static void propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
+										Relation edge_rel, Relation ref_rel,
+										const char *aliasname, int location, const char *type,
+										ArrayType **outkey, ArrayType **outref);
+static ArrayType *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
+static ArrayType *array_from_attnums(int numattrs, const AttrNumber *attnums);
+static Oid	insert_element_record(ObjectAddress pgaddress, struct element_info *einfo);
+static Oid	insert_label_record(Oid graphid, Oid peoid, const char *label);
+static void insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties);
+static void insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr);
+static void check_element_properties(Oid peoid);
+static void check_element_label_properties(Oid ellabeloid);
+static void check_all_labels_properties(Oid pgrelid);
+static Oid	get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location);
+static Oid	get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location);
+static Oid	get_element_relid(Oid peid);
+static List *get_graph_label_ids(Oid graphid);
+static List *get_label_element_label_ids(Oid labelid);
+static List *get_element_label_property_names(Oid ellabeloid);
+static List *get_graph_property_ids(Oid graphid);
+
+
+/*
+ * CREATE PROPERTY GRAPH
+ */
+ObjectAddress
+CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt)
+{
+	CreateStmt *cstmt = makeNode(CreateStmt);
+	char		components_persistence;
+	ListCell   *lc;
+	ObjectAddress pgaddress;
+	List	   *vertex_infos = NIL;
+	List	   *edge_infos = NIL;
+	List	   *element_aliases = NIL;
+	List	   *element_oids = NIL;
+
+	if (stmt->pgname->relpersistence == RELPERSISTENCE_UNLOGGED)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("property graphs cannot be unlogged because they do not have storage")));
+
+	components_persistence = RELPERSISTENCE_PERMANENT;
+
+	foreach(lc, stmt->vertex_tables)
+	{
+		PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc);
+		struct element_info *vinfo;
+		Relation	rel;
+
+		vinfo = palloc0_object(struct element_info);
+		vinfo->kind = PGEKIND_VERTEX;
+
+		vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(vinfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+			components_persistence = RELPERSISTENCE_TEMP;
+
+		if (vertex->vtable->alias)
+			vinfo->aliasname = vertex->vtable->alias->aliasname;
+		else
+			vinfo->aliasname = vertex->vtable->relname;
+
+		if (list_member(element_aliases, makeString(vinfo->aliasname)))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+					 errmsg("alias \"%s\" used more than once as element table", vinfo->aliasname),
+					 parser_errposition(pstate, vertex->location)));
+
+		vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location);
+
+		vinfo->labels = vertex->labels;
+
+		table_close(rel, NoLock);
+
+		vertex_infos = lappend(vertex_infos, vinfo);
+
+		element_aliases = lappend(element_aliases, makeString(vinfo->aliasname));
+	}
+
+	foreach(lc, stmt->edge_tables)
+	{
+		PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc);
+		struct element_info *einfo;
+		Relation	rel;
+		ListCell   *lc2;
+		Oid			srcrelid;
+		Oid			destrelid;
+		Relation	srcrel;
+		Relation	destrel;
+
+		einfo = palloc0_object(struct element_info);
+		einfo->kind = PGEKIND_EDGE;
+
+		einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(einfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+			components_persistence = RELPERSISTENCE_TEMP;
+
+		if (edge->etable->alias)
+			einfo->aliasname = edge->etable->alias->aliasname;
+		else
+			einfo->aliasname = edge->etable->relname;
+
+		if (list_member(element_aliases, makeString(einfo->aliasname)))
+			ereport(ERROR,
+					(errcode(ERRCODE_DUPLICATE_TABLE),
+					 errmsg("alias \"%s\" used more than once as element table", einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location);
+
+		einfo->srcvertex = edge->esrcvertex;
+		einfo->destvertex = edge->edestvertex;
+
+		srcrelid = 0;
+		destrelid = 0;
+		foreach(lc2, vertex_infos)
+		{
+			struct element_info *vinfo = lfirst(lc2);
+
+			if (strcmp(vinfo->aliasname, edge->esrcvertex) == 0)
+				srcrelid = vinfo->relid;
+
+			if (strcmp(vinfo->aliasname, edge->edestvertex) == 0)
+				destrelid = vinfo->relid;
+
+			if (srcrelid && destrelid)
+				break;
+		}
+		if (!srcrelid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("source vertex \"%s\" of edge \"%s\" does not exist",
+							edge->esrcvertex, einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+		if (!destrelid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("destination vertex \"%s\" of edge \"%s\" does not exist",
+							edge->edestvertex, einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		srcrel = table_open(srcrelid, NoLock);
+		destrel = table_open(destrelid, NoLock);
+
+		propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
+									einfo->aliasname, edge->location, "SOURCE",
+									&einfo->srckey, &einfo->srcref);
+		propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
+									einfo->aliasname, edge->location, "DESTINATION",
+									&einfo->destkey, &einfo->destref);
+
+		einfo->labels = edge->labels;
+
+		table_close(destrel, NoLock);
+		table_close(srcrel, NoLock);
+
+		table_close(rel, NoLock);
+
+		edge_infos = lappend(edge_infos, einfo);
+
+		element_aliases = lappend(element_aliases, makeString(einfo->aliasname));
+	}
+
+	cstmt->relation = stmt->pgname;
+	cstmt->oncommit = ONCOMMIT_NOOP;
+
+	/*
+	 * Automatically make it temporary if any component tables are temporary
+	 * (see also DefineView()).
+	 */
+	if (stmt->pgname->relpersistence == RELPERSISTENCE_PERMANENT
+		&& components_persistence == RELPERSISTENCE_TEMP)
+	{
+		cstmt->relation = copyObject(cstmt->relation);
+		cstmt->relation->relpersistence = RELPERSISTENCE_TEMP;
+		ereport(NOTICE,
+				(errmsg("property graph \"%s\" will be temporary",
+						stmt->pgname->relname)));
+	}
+
+	pgaddress = DefineRelation(cstmt, RELKIND_PROPGRAPH, InvalidOid, NULL, NULL);
+
+	foreach(lc, vertex_infos)
+	{
+		struct element_info *vinfo = lfirst(lc);
+		Oid			peoid;
+
+		peoid = insert_element_record(pgaddress, vinfo);
+		element_oids = lappend_oid(element_oids, peoid);
+	}
+
+	foreach(lc, edge_infos)
+	{
+		struct element_info *einfo = lfirst(lc);
+		Oid			peoid;
+		ListCell   *lc2;
+
+		/*
+		 * Look up the vertices again.  Now the vertices have OIDs assigned,
+		 * which we need.
+		 */
+		foreach(lc2, vertex_infos)
+		{
+			struct element_info *vinfo = lfirst(lc2);
+
+			if (strcmp(vinfo->aliasname, einfo->srcvertex) == 0)
+			{
+				einfo->srcvertexid = vinfo->elementid;
+				einfo->srcrelid = vinfo->relid;
+			}
+			if (strcmp(vinfo->aliasname, einfo->destvertex) == 0)
+			{
+				einfo->destvertexid = vinfo->elementid;
+				einfo->destrelid = vinfo->relid;
+			}
+			if (einfo->srcvertexid && einfo->destvertexid)
+				break;
+		}
+		Assert(einfo->srcvertexid);
+		Assert(einfo->destvertexid);
+		Assert(einfo->srcrelid);
+		Assert(einfo->destrelid);
+		peoid = insert_element_record(pgaddress, einfo);
+		element_oids = lappend_oid(element_oids, peoid);
+	}
+
+	CommandCounterIncrement();
+
+	foreach_oid(peoid, element_oids)
+		check_element_properties(peoid);
+	check_all_labels_properties(pgaddress.objectId);
+
+	return pgaddress;
+}
+
+/*
+ * Process the key clause specified for an element.  If key_clause is non-NIL,
+ * then it is a list of column names.  Otherwise, the primary key of the
+ * relation is used.  The return value is an array of column numbers.
+ */
+static ArrayType *
+propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation element_rel, const char *aliasname, int location)
+{
+	ArrayType  *a;
+
+	if (key_clause == NIL)
+	{
+		Oid			pkidx = RelationGetPrimaryKeyIndex(element_rel);
+
+		if (!pkidx)
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					errmsg("no key specified and no suitable primary key exists for definition of element \"%s\"", aliasname),
+					parser_errposition(pstate, location));
+		else
+		{
+			Relation	indexDesc;
+
+			indexDesc = index_open(pkidx, AccessShareLock);
+			a = array_from_attnums(indexDesc->rd_index->indkey.dim1, indexDesc->rd_index->indkey.values);
+			index_close(indexDesc, NoLock);
+		}
+	}
+	else
+	{
+		a = array_from_column_list(pstate, key_clause, location, element_rel);
+	}
+
+	return a;
+}
+
+/*
+ * Process the source or destination link of an edge.
+ *
+ * keycols and refcols are column names representing the local and referenced
+ * (vertex) columns.  If they are both NIL, a matching foreign key is looked
+ * up.
+ *
+ * edge_rel and ref_rel are the local and referenced element tables.
+ *
+ * aliasname, location, and type are for error messages.  type is either
+ * "SOURCE" or "DESTINATION".
+ *
+ * The outputs are arrays of column numbers in outkey and outref.
+ */
+static void
+propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
+							Relation edge_rel, Relation ref_rel,
+							const char *aliasname, int location, const char *type,
+							ArrayType **outkey, ArrayType **outref)
+{
+	Assert((keycols && refcols) || (!keycols && !refcols));
+
+	if (keycols)
+	{
+		if (list_length(keycols) != list_length(refcols))
+			ereport(ERROR,
+					errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					errmsg("mismatching number of columns in %s vertex definition of edge \"%s\"", type, aliasname),
+					parser_errposition(pstate, location));
+
+		*outkey = array_from_column_list(pstate, keycols, location, edge_rel);
+		*outref = array_from_column_list(pstate, refcols, location, ref_rel);
+	}
+	else
+	{
+		List	   *fkeys;
+		ListCell   *lc;
+		int			count = 0;
+		ForeignKeyCacheInfo *fk = NULL;
+
+		fkeys = RelationGetFKeyList(edge_rel);
+		foreach(lc, fkeys)
+		{
+			fk = lfirst_node(ForeignKeyCacheInfo, lc);
+
+			if (fk->confrelid == RelationGetRelid(ref_rel))
+				count++;
+		}
+
+		if (count == 0)
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("no %s key specified and no suitable foreign key exists for definition of edge \"%s\"", type, aliasname),
+					parser_errposition(pstate, location));
+		else if (count > 1)
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("more than one suitable foreign key exists for %s key of edge \"%s\"", type, aliasname),
+					parser_errposition(pstate, location));
+
+		Assert(fk);
+
+		*outkey = array_from_attnums(fk->nkeys, fk->conkey);
+		*outref = array_from_attnums(fk->nkeys, fk->confkey);
+	}
+}
+
+/*
+ * Convert list of column names in the specified relation into an array of
+ * column numbers.
+ */
+static ArrayType *
+array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel)
+{
+	int			numattrs;
+	Datum	   *attnumsd;
+	int			i;
+	ListCell   *lc;
+
+	numattrs = list_length(colnames);
+	attnumsd = palloc_array(Datum, numattrs);
+
+	i = 0;
+	foreach(lc, colnames)
+	{
+		char	   *colname = strVal(lfirst(lc));
+		Oid			relid = RelationGetRelid(element_rel);
+		AttrNumber	attnum;
+
+		attnum = get_attnum(relid, colname);
+		if (!attnum)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" of relation \"%s\" does not exist",
+							colname, get_rel_name(relid)),
+					 parser_errposition(pstate, location)));
+		attnumsd[i++] = Int16GetDatum(attnum);
+	}
+
+	for (int j = 0; j < numattrs; j++)
+	{
+		for (int k = j + 1; k < numattrs; k++)
+		{
+			if (DatumGetInt16(attnumsd[j]) == DatumGetInt16(attnumsd[k]))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("graph key columns list must not contain duplicates"),
+						 parser_errposition(pstate, location)));
+		}
+	}
+
+	return construct_array_builtin(attnumsd, numattrs, INT2OID);
+}
+
+static ArrayType *
+array_from_attnums(int numattrs, const AttrNumber *attnums)
+{
+	Datum	   *attnumsd;
+
+	attnumsd = palloc_array(Datum, numattrs);
+
+	for (int i = 0; i < numattrs; i++)
+		attnumsd[i] = Int16GetDatum(attnums[i]);
+
+	return construct_array_builtin(attnumsd, numattrs, INT2OID);
+}
+
+static void
+array_of_attnums_to_objectaddrs(Oid relid, ArrayType *arr, ObjectAddresses *addrs)
+{
+	Datum	   *attnumsd;
+	int			numattrs;
+
+	deconstruct_array_builtin(arr, INT2OID, &attnumsd, NULL, &numattrs);
+
+	for (int i = 0; i < numattrs; i++)
+	{
+		ObjectAddress referenced;
+
+		ObjectAddressSubSet(referenced, RelationRelationId, relid, DatumGetInt16(attnumsd[i]));
+		add_exact_object_address(&referenced, addrs);
+	}
+}
+
+/*
+ * Insert a record for an element into the pg_propgraph_element catalog.  Also
+ * inserts labels and properties into their respective catalogs.
+ */
+static Oid
+insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
+{
+	Oid			graphid = pgaddress.objectId;
+	Relation	rel;
+	NameData	aliasname;
+	Oid			peoid;
+	Datum		values[Natts_pg_propgraph_element] = {0};
+	bool		nulls[Natts_pg_propgraph_element] = {0};
+	HeapTuple	tup;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+	ObjectAddresses *addrs;
+
+	rel = table_open(PropgraphElementRelationId, RowExclusiveLock);
+
+	peoid = GetNewOidWithIndex(rel, PropgraphElementObjectIndexId, Anum_pg_propgraph_element_oid);
+	einfo->elementid = peoid;
+	values[Anum_pg_propgraph_element_oid - 1] = ObjectIdGetDatum(peoid);
+	values[Anum_pg_propgraph_element_pgepgid - 1] = ObjectIdGetDatum(graphid);
+	values[Anum_pg_propgraph_element_pgerelid - 1] = ObjectIdGetDatum(einfo->relid);
+	namestrcpy(&aliasname, einfo->aliasname);
+	values[Anum_pg_propgraph_element_pgealias - 1] = NameGetDatum(&aliasname);
+	values[Anum_pg_propgraph_element_pgekind - 1] = CharGetDatum(einfo->kind);
+	values[Anum_pg_propgraph_element_pgesrcvertexid - 1] = ObjectIdGetDatum(einfo->srcvertexid);
+	values[Anum_pg_propgraph_element_pgedestvertexid - 1] = ObjectIdGetDatum(einfo->destvertexid);
+	values[Anum_pg_propgraph_element_pgekey - 1] = PointerGetDatum(einfo->key);
+
+	if (einfo->srckey)
+		values[Anum_pg_propgraph_element_pgesrckey - 1] = PointerGetDatum(einfo->srckey);
+	else
+		nulls[Anum_pg_propgraph_element_pgesrckey - 1] = true;
+	if (einfo->srcref)
+		values[Anum_pg_propgraph_element_pgesrcref - 1] = PointerGetDatum(einfo->srcref);
+	else
+		nulls[Anum_pg_propgraph_element_pgesrcref - 1] = true;
+	if (einfo->destkey)
+		values[Anum_pg_propgraph_element_pgedestkey - 1] = PointerGetDatum(einfo->destkey);
+	else
+		nulls[Anum_pg_propgraph_element_pgedestkey - 1] = true;
+	if (einfo->destref)
+		values[Anum_pg_propgraph_element_pgedestref - 1] = PointerGetDatum(einfo->destref);
+	else
+		nulls[Anum_pg_propgraph_element_pgedestref - 1] = true;
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PropgraphElementRelationId, peoid);
+
+	/* Add dependency on the property graph */
+	recordDependencyOn(&myself, &pgaddress, DEPENDENCY_AUTO);
+
+	addrs = new_object_addresses();
+
+	/* Add dependency on the relation */
+	ObjectAddressSet(referenced, RelationRelationId, einfo->relid);
+	add_exact_object_address(&referenced, addrs);
+	array_of_attnums_to_objectaddrs(einfo->relid, einfo->key, addrs);
+
+	/* Add dependencies on vertices */
+	if (einfo->srcvertexid)
+	{
+		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid);
+		add_exact_object_address(&referenced, addrs);
+		array_of_attnums_to_objectaddrs(einfo->relid, einfo->srckey, addrs);
+		array_of_attnums_to_objectaddrs(einfo->srcrelid, einfo->srcref, addrs);
+	}
+	if (einfo->destvertexid)
+	{
+		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->destvertexid);
+		add_exact_object_address(&referenced, addrs);
+		array_of_attnums_to_objectaddrs(einfo->relid, einfo->destkey, addrs);
+		array_of_attnums_to_objectaddrs(einfo->destrelid, einfo->destref, addrs);
+	}
+
+	/* TODO: dependencies on equality operators, like for foreign keys */
+
+	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+
+	table_close(rel, NoLock);
+
+	if (einfo->labels)
+	{
+		ListCell   *lc;
+
+		foreach(lc, einfo->labels)
+		{
+			PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+			Oid			ellabeloid;
+
+			if (lp->label)
+				ellabeloid = insert_label_record(graphid, peoid, lp->label);
+			else
+				ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+			insert_property_records(graphid, ellabeloid, einfo->relid, lp->properties);
+
+			CommandCounterIncrement();
+		}
+	}
+	else
+	{
+		Oid			ellabeloid;
+		PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+		pr->all = true;
+		pr->location = -1;
+
+		ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+		insert_property_records(graphid, ellabeloid, einfo->relid, pr);
+	}
+
+	return peoid;
+}
+
+/*
+ * Insert records for a label into the pg_propgraph_label and
+ * pg_propgraph_element_label catalogs, and register dependencies.
+ *
+ * Returns the OID of the new pg_propgraph_element_label record.
+ */
+static Oid
+insert_label_record(Oid graphid, Oid peoid, const char *label)
+{
+	Oid			labeloid;
+	Oid			ellabeloid;
+
+	/*
+	 * Insert into pg_propgraph_label if not already existing.
+	 */
+	labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(graphid), CStringGetDatum(label));
+	if (!labeloid)
+	{
+		Relation	rel;
+		Datum		values[Natts_pg_propgraph_label] = {0};
+		bool		nulls[Natts_pg_propgraph_label] = {0};
+		NameData	labelname;
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		rel = table_open(PropgraphLabelRelationId, RowExclusiveLock);
+
+		labeloid = GetNewOidWithIndex(rel, PropgraphLabelObjectIndexId, Anum_pg_propgraph_label_oid);
+		values[Anum_pg_propgraph_label_oid - 1] = ObjectIdGetDatum(labeloid);
+		values[Anum_pg_propgraph_label_pglpgid - 1] = ObjectIdGetDatum(graphid);
+		namestrcpy(&labelname, label);
+		values[Anum_pg_propgraph_label_pgllabel - 1] = NameGetDatum(&labelname);
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphLabelRelationId, labeloid);
+
+		ObjectAddressSet(referenced, RelationRelationId, graphid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		table_close(rel, NoLock);
+	}
+
+	/*
+	 * Insert into pg_propgraph_element_label
+	 */
+	{
+		Relation	rel;
+		Datum		values[Natts_pg_propgraph_element_label] = {0};
+		bool		nulls[Natts_pg_propgraph_element_label] = {0};
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		rel = table_open(PropgraphElementLabelRelationId, RowExclusiveLock);
+
+		ellabeloid = GetNewOidWithIndex(rel, PropgraphElementLabelObjectIndexId, Anum_pg_propgraph_element_label_oid);
+		values[Anum_pg_propgraph_element_label_oid - 1] = ObjectIdGetDatum(ellabeloid);
+		values[Anum_pg_propgraph_element_label_pgellabelid - 1] = ObjectIdGetDatum(labeloid);
+		values[Anum_pg_propgraph_element_label_pgelelid - 1] = ObjectIdGetDatum(peoid);
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphElementLabelRelationId, ellabeloid);
+
+		ObjectAddressSet(referenced, PropgraphLabelRelationId, labeloid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+		ObjectAddressSet(referenced, PropgraphElementRelationId, peoid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		table_close(rel, NoLock);
+	}
+
+	return ellabeloid;
+}
+
+/*
+ * Insert records for properties into the pg_propgraph_property catalog.
+ */
+static void
+insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties)
+{
+	List	   *proplist = NIL;
+	ParseState *pstate;
+	ParseNamespaceItem *nsitem;
+	List	   *tp;
+	Relation	rel;
+	ListCell   *lc;
+
+	if (properties->all)
+	{
+		Relation	attRelation;
+		SysScanDesc scan;
+		ScanKeyData key[1];
+		HeapTuple	attributeTuple;
+
+		attRelation = table_open(AttributeRelationId, RowShareLock);
+		ScanKeyInit(&key[0],
+					Anum_pg_attribute_attrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(pgerelid));
+		scan = systable_beginscan(attRelation, AttributeRelidNumIndexId,
+								  true, NULL, 1, key);
+		while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
+		{
+			Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
+			ColumnRef  *cr;
+			ResTarget  *rt;
+
+			if (att->attnum <= 0 || att->attisdropped)
+				continue;
+
+			cr = makeNode(ColumnRef);
+			rt = makeNode(ResTarget);
+
+			cr->fields = list_make1(makeString(NameStr(att->attname)));
+			cr->location = -1;
+
+			rt->name = pstrdup(NameStr(att->attname));
+			rt->val = (Node *) cr;
+			rt->location = -1;
+
+			proplist = lappend(proplist, rt);
+		}
+		systable_endscan(scan);
+		table_close(attRelation, RowShareLock);
+	}
+	else
+	{
+		proplist = properties->properties;
+
+		foreach(lc, proplist)
+		{
+			ResTarget  *rt = lfirst_node(ResTarget, lc);
+
+			if (!rt->name && !IsA(rt->val, ColumnRef))
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("property name required"),
+						parser_errposition(NULL, rt->location));
+		}
+	}
+
+	rel = table_open(pgerelid, AccessShareLock);
+
+	pstate = make_parsestate(NULL);
+	nsitem = addRangeTableEntryForRelation(pstate,
+										   rel,
+										   AccessShareLock,
+										   NULL,
+										   false,
+										   true);
+	addNSItemToQuery(pstate, nsitem, true, true, true);
+
+	table_close(rel, NoLock);
+
+	tp = transformTargetList(pstate, proplist, EXPR_KIND_PROPGRAPH_PROPERTY);
+
+	foreach(lc, tp)
+	{
+		TargetEntry *te = lfirst_node(TargetEntry, lc);
+
+		insert_property_record(graphid, ellabeloid, pgerelid, te->resname, te->expr);
+	}
+}
+
+/*
+ * Insert records for a property into the pg_propgraph_property and
+ * pg_propgraph_label_property catalogs, and register dependencies.
+ */
+static void
+insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr)
+{
+	Oid			propoid;
+	Oid			exprtypid;
+	Oid			proptypid;
+
+	exprtypid = exprType((const Node *) expr);
+
+	/*
+	 * Insert into pg_propgraph_property if not already existing.
+	 */
+	propoid = GetSysCacheOid2(PROPGRAPHPROPNAME, Anum_pg_propgraph_property_oid, ObjectIdGetDatum(graphid), CStringGetDatum(propname));
+	if (!propoid)
+	{
+		Relation	rel;
+		NameData	propnamedata;
+		Datum		values[Natts_pg_propgraph_property] = {0};
+		bool		nulls[Natts_pg_propgraph_property] = {0};
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		proptypid = exprtypid;
+
+		rel = table_open(PropgraphPropertyRelationId, RowExclusiveLock);
+
+		propoid = GetNewOidWithIndex(rel, PropgraphPropertyObjectIndexId, Anum_pg_propgraph_property_oid);
+		values[Anum_pg_propgraph_property_oid - 1] = ObjectIdGetDatum(propoid);
+		values[Anum_pg_propgraph_property_pgppgid - 1] = ObjectIdGetDatum(graphid);
+		namestrcpy(&propnamedata, propname);
+		values[Anum_pg_propgraph_property_pgpname - 1] = NameGetDatum(&propnamedata);
+		values[Anum_pg_propgraph_property_pgptypid - 1] = ObjectIdGetDatum(proptypid);
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphPropertyRelationId, propoid);
+
+		ObjectAddressSet(referenced, RelationRelationId, graphid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+		ObjectAddressSet(referenced, TypeRelationId, proptypid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+		table_close(rel, NoLock);
+	}
+	else
+	{
+		proptypid = GetSysCacheOid1(PROPGRAPHPROPOID, Anum_pg_propgraph_property_pgptypid, ObjectIdGetDatum(propoid));
+	}
+
+	/*
+	 * Check that in the graph, all properties with the same name have the
+	 * same type (independent of which label they are on).  (See SQL/PGQ
+	 * subclause "Consistency check of a tabular property graph descriptor".)
+	 */
+	if (proptypid != exprtypid)
+	{
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("property \"%s\" data type mismatch: %s vs. %s",
+					   propname, format_type_be(proptypid), format_type_be(exprtypid)),
+				errdetail("In a property graph, a property of the same name has to have the same data type in each label."));
+	}
+
+	/*
+	 * Insert into pg_propgraph_label_property
+	 */
+	{
+		Relation	rel;
+		Datum		values[Natts_pg_propgraph_label_property] = {0};
+		bool		nulls[Natts_pg_propgraph_label_property] = {0};
+		Oid			plpoid;
+		HeapTuple	tup;
+		ObjectAddress myself;
+		ObjectAddress referenced;
+
+		rel = table_open(PropgraphLabelPropertyRelationId, RowExclusiveLock);
+
+		plpoid = GetNewOidWithIndex(rel, PropgraphLabelPropertyObjectIndexId, Anum_pg_propgraph_label_property_oid);
+		values[Anum_pg_propgraph_label_property_oid - 1] = ObjectIdGetDatum(plpoid);
+		values[Anum_pg_propgraph_label_property_plppropid - 1] = ObjectIdGetDatum(propoid);
+		values[Anum_pg_propgraph_label_property_plpellabelid - 1] = ObjectIdGetDatum(ellabeloid);
+		values[Anum_pg_propgraph_label_property_plpexpr - 1] = CStringGetTextDatum(nodeToString(expr));
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+		CatalogTupleInsert(rel, tup);
+		heap_freetuple(tup);
+
+		ObjectAddressSet(myself, PropgraphLabelPropertyRelationId, plpoid);
+
+		ObjectAddressSet(referenced, PropgraphPropertyRelationId, propoid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		ObjectAddressSet(referenced, PropgraphElementLabelRelationId, ellabeloid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		recordDependencyOnSingleRelExpr(&myself, (Node *) copyObject(expr), pgerelid, DEPENDENCY_NORMAL, DEPENDENCY_NORMAL, false);
+
+		table_close(rel, NoLock);
+	}
+}
+
+/*
+ * Check that for the given graph element, all properties with the same name
+ * have the same expression for each label.  (See SQL/PGQ subclause "Creation
+ * of an element table descriptor".)
+ *
+ * We check this after all the catalog records are already inserted.  This
+ * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER
+ * PROPERTY GRAPH.  We pass in the element OID so that ALTER PROPERTY GRAPH
+ * only has to check the element it has just operated on.  CREATE PROPERTY
+ * GROUP checks all elements it has created.
+ */
+static void
+check_element_properties(Oid peoid)
+{
+	Relation	rel1;
+	ScanKeyData key1[1];
+	SysScanDesc scan1;
+	HeapTuple	tuple1;
+	List	   *propoids = NIL;
+	List	   *propexprs = NIL;
+
+	rel1 = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+	ScanKeyInit(&key1[0],
+				Anum_pg_propgraph_element_label_pgelelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(peoid));
+
+	scan1 = systable_beginscan(rel1, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, key1);
+	while (HeapTupleIsValid(tuple1 = systable_getnext(scan1)))
+	{
+		Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple1);
+		Relation	rel2;
+		ScanKeyData key2[1];
+		SysScanDesc scan2;
+		HeapTuple	tuple2;
+
+		rel2 = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+		ScanKeyInit(&key2[0],
+					Anum_pg_propgraph_label_property_plpellabelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(ellabel->oid));
+
+		scan2 = systable_beginscan(rel2, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key2);
+		while (HeapTupleIsValid(tuple2 = systable_getnext(scan2)))
+		{
+			Form_pg_propgraph_label_property lprop = (Form_pg_propgraph_label_property) GETSTRUCT(tuple2);
+			Oid			propoid;
+			Datum		datum;
+			bool		isnull;
+			char	   *propexpr;
+			ListCell   *lc1,
+					   *lc2;
+			bool		found;
+
+			propoid = lprop->plppropid;
+			datum = heap_getattr(tuple2, Anum_pg_propgraph_label_property_plpexpr, RelationGetDescr(rel2), &isnull);
+			Assert(!isnull);
+			propexpr = TextDatumGetCString(datum);
+
+			found = false;
+			forboth(lc1, propoids, lc2, propexprs)
+			{
+				if (propoid == lfirst_oid(lc1))
+				{
+					Node	   *na,
+							   *nb;
+
+					na = stringToNode(propexpr);
+					nb = stringToNode(lfirst(lc2));
+
+					found = true;
+
+					if (!equal(na, nb))
+					{
+						HeapTuple	tuple3;
+						Form_pg_propgraph_element elform;
+						List	   *dpcontext;
+						char	   *dpa,
+								   *dpb;
+
+						tuple3 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peoid));
+						if (!tuple3)
+							elog(ERROR, "cache lookup failed for property graph element %u", peoid);
+						elform = (Form_pg_propgraph_element) GETSTRUCT(tuple3);
+						dpcontext = deparse_context_for(get_rel_name(elform->pgerelid), elform->pgerelid);
+
+						dpa = deparse_expression(na, dpcontext, false, false);
+						dpb = deparse_expression(nb, dpcontext, false, false);
+
+						/*
+						 * show in sorted order to keep output independent of
+						 * index order
+						 */
+						if (strcmp(dpa, dpb) > 0)
+						{
+							char	   *tmp;
+
+							tmp = dpa;
+							dpa = dpb;
+							dpb = tmp;
+						}
+
+						ereport(ERROR,
+								errcode(ERRCODE_SYNTAX_ERROR),
+								errmsg("element \"%s\" property \"%s\" expression mismatch: %s vs. %s",
+									   NameStr(elform->pgealias), get_propgraph_property_name(propoid), dpa, dpb),
+								errdetail("In a property graph element, a property of the same name has to have the same expression in each label."));
+
+						ReleaseSysCache(tuple3);
+					}
+
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				propoids = lappend_oid(propoids, propoid);
+				propexprs = lappend(propexprs, propexpr);
+			}
+		}
+		systable_endscan(scan2);
+		table_close(rel2, AccessShareLock);
+	}
+
+	systable_endscan(scan1);
+	table_close(rel1, AccessShareLock);
+}
+
+/*
+ * Check that for the given element label, all labels of the same name in the
+ * graph have the same number and names of properties (independent of which
+ * element they are on).  (See SQL/PGQ subclause "Consistency check of a
+ * tabular property graph descriptor".)
+ *
+ * We check this after all the catalog records are already inserted.  This
+ * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER
+ * PROPERTY GRAPH.  We pass in the element label OID so that some variants of
+ * ALTER PROPERTY GRAPH only have to check the element label it has just
+ * operated on.  CREATE PROPERTY GROUP and other ALTER PROPERTY GRAPH variants
+ * check all labels.
+ */
+static void
+check_element_label_properties(Oid ellabeloid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	Oid			labelid = InvalidOid;
+	Oid			ref_ellabeloid = InvalidOid;
+	List	   *myprops,
+			   *refprops;
+	List	   *diff1,
+			   *diff2;
+
+	rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+
+	/*
+	 * Get element label info
+	 */
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_oid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(ellabeloid));
+	scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key);
+	if (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+		labelid = ellabel->pgellabelid;
+	}
+	systable_endscan(scan);
+	if (!labelid)
+		elog(ERROR, "element label %u not found", ellabeloid);
+
+	/*
+	 * Find a reference element label to fetch label properties.  The
+	 * reference element label has to have the label OID as the one being
+	 * checked but be distinct from the one being checked.
+	 */
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_pgellabelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(labelid));
+	scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element_label otherellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+		if (otherellabel->oid != ellabeloid)
+		{
+			ref_ellabeloid = otherellabel->oid;
+			break;
+		}
+	}
+	systable_endscan(scan);
+
+	table_close(rel, AccessShareLock);
+
+	/*
+	 * If there is not previous definition of this label, then we are done.
+	 */
+	if (!ref_ellabeloid)
+		return;
+
+	/*
+	 * Now check number and names.
+	 *
+	 * XXX We could provide more detail in the error messages, but that would
+	 * probably only be useful for some ALTER commands, because otherwise it's
+	 * not really clear which label definition is the wrong one, and so you'd
+	 * have to construct a rather verbose report to be of any use.  Let's keep
+	 * it simple for now.
+	 */
+
+	myprops = get_element_label_property_names(ellabeloid);
+	refprops = get_element_label_property_names(ref_ellabeloid);
+
+	if (list_length(refprops) != list_length(myprops))
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("mismatching number of properties in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+
+	diff1 = list_difference(myprops, refprops);
+	diff2 = list_difference(refprops, myprops);
+
+	if (diff1 || diff2)
+		ereport(ERROR,
+				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				errmsg("mismatching properties names in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+}
+
+/*
+ * As above, but check all labels of a graph.
+ */
+static void
+check_all_labels_properties(Oid pgrelid)
+{
+	foreach_oid(labeloid, get_graph_label_ids(pgrelid))
+	{
+		foreach_oid(ellabeloid, get_label_element_label_ids(labeloid))
+		{
+			check_element_label_properties(ellabeloid);
+		}
+	}
+}
+
+/*
+ * ALTER PROPERTY GRAPH
+ */
+ObjectAddress
+AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
+{
+	Oid			pgrelid;
+	ListCell   *lc;
+	ObjectAddress pgaddress;
+
+	pgrelid = RangeVarGetRelidExtended(stmt->pgname,
+									   ShareRowExclusiveLock,
+									   stmt->missing_ok ? RVR_MISSING_OK : 0,
+									   RangeVarCallbackOwnsRelation,
+									   NULL);
+	if (pgrelid == InvalidOid)
+	{
+		ereport(NOTICE,
+				(errmsg("relation \"%s\" does not exist, skipping",
+						stmt->pgname->relname)));
+		return InvalidObjectAddress;
+	}
+
+	ObjectAddressSet(pgaddress, RelationRelationId, pgrelid);
+
+	foreach(lc, stmt->add_vertex_tables)
+	{
+		PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc);
+		struct element_info *vinfo;
+		Relation	rel;
+		Oid			peoid;
+
+		vinfo = palloc0_object(struct element_info);
+		vinfo->kind = PGEKIND_VERTEX;
+
+		vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(vinfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add temporary element table to non-temporary property graph"),
+					 errdetail("Table \"%s\" is a temporary table.", get_rel_name(vinfo->relid)),
+					 parser_errposition(pstate, vertex->vtable->location)));
+
+		if (vertex->vtable->alias)
+			vinfo->aliasname = vertex->vtable->alias->aliasname;
+		else
+			vinfo->aliasname = vertex->vtable->relname;
+
+		vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location);
+
+		vinfo->labels = vertex->labels;
+
+		table_close(rel, NoLock);
+
+		peoid = insert_element_record(pgaddress, vinfo);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_all_labels_properties(pgrelid);
+	}
+
+	foreach(lc, stmt->add_edge_tables)
+	{
+		PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc);
+		struct element_info *einfo;
+		Relation	rel;
+		Relation	srcrel;
+		Relation	destrel;
+		Oid			peoid;
+
+		einfo = palloc0_object(struct element_info);
+		einfo->kind = PGEKIND_EDGE;
+
+		einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+		rel = table_open(einfo->relid, NoLock);
+
+		if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("cannot add temporary element table to non-temporary property graph"),
+					 errdetail("Table \"%s\" is a temporary table.", get_rel_name(einfo->relid)),
+					 parser_errposition(pstate, edge->etable->location)));
+
+		if (edge->etable->alias)
+			einfo->aliasname = edge->etable->alias->aliasname;
+		else
+			einfo->aliasname = edge->etable->relname;
+
+		einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location);
+
+		einfo->srcvertexid = get_vertex_oid(pstate, pgrelid, edge->esrcvertex, edge->location);
+		einfo->destvertexid = get_vertex_oid(pstate, pgrelid, edge->edestvertex, edge->location);
+
+		einfo->srcrelid = get_element_relid(einfo->srcvertexid);
+		einfo->destrelid = get_element_relid(einfo->destvertexid);
+
+		srcrel = table_open(einfo->srcrelid, AccessShareLock);
+		destrel = table_open(einfo->destrelid, AccessShareLock);
+
+		propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
+									einfo->aliasname, edge->location, "SOURCE",
+									&einfo->srckey, &einfo->srcref);
+		propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
+									einfo->aliasname, edge->location, "DESTINATION",
+									&einfo->destkey, &einfo->destref);
+
+		einfo->labels = edge->labels;
+
+		table_close(destrel, NoLock);
+		table_close(srcrel, NoLock);
+
+		table_close(rel, NoLock);
+
+		peoid = insert_element_record(pgaddress, einfo);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_all_labels_properties(pgrelid);
+	}
+
+	foreach(lc, stmt->drop_vertex_tables)
+	{
+		char	   *alias = strVal(lfirst(lc));
+		Oid			peoid;
+		ObjectAddress obj;
+
+		peoid = get_vertex_oid(pstate, pgrelid, alias, -1);
+		ObjectAddressSet(obj, PropgraphElementRelationId, peoid);
+		performDeletion(&obj, stmt->drop_behavior, 0);
+	}
+
+	foreach(lc, stmt->drop_edge_tables)
+	{
+		char	   *alias = strVal(lfirst(lc));
+		Oid			peoid;
+		ObjectAddress obj;
+
+		peoid = get_edge_oid(pstate, pgrelid, alias, -1);
+		ObjectAddressSet(obj, PropgraphElementRelationId, peoid);
+		performDeletion(&obj, stmt->drop_behavior, 0);
+	}
+
+	/* Remove any orphaned pg_propgraph_label entries */
+	if (stmt->drop_vertex_tables || stmt->drop_edge_tables)
+	{
+		foreach_oid(labeloid, get_graph_label_ids(pgrelid))
+		{
+			if (!get_label_element_label_ids(labeloid))
+			{
+				ObjectAddress obj;
+
+				ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid);
+				performDeletion(&obj, stmt->drop_behavior, 0);
+			}
+		}
+	}
+
+	foreach(lc, stmt->add_labels)
+	{
+		PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+		Oid			peoid;
+		Oid			pgerelid;
+		Oid			ellabeloid;
+
+		Assert(lp->label);
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		pgerelid = get_element_relid(peoid);
+
+		ellabeloid = insert_label_record(pgrelid, peoid, lp->label);
+		insert_property_records(pgrelid, ellabeloid, pgerelid, lp->properties);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_element_label_properties(ellabeloid);
+	}
+
+	if (stmt->drop_label)
+	{
+		Oid			peoid;
+		Oid			labeloid;
+		Oid			ellabeloid;
+		ObjectAddress obj;
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+								   Anum_pg_propgraph_label_oid,
+								   ObjectIdGetDatum(pgrelid),
+								   CStringGetDatum(stmt->drop_label));
+		if (!labeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
+					parser_errposition(pstate, -1));
+
+		ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+									 Anum_pg_propgraph_element_label_oid,
+									 ObjectIdGetDatum(peoid),
+									 ObjectIdGetDatum(labeloid));
+
+		if (!ellabeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
+					parser_errposition(pstate, -1));
+
+		ObjectAddressSet(obj, PropgraphElementLabelRelationId, ellabeloid);
+		performDeletion(&obj, stmt->drop_behavior, 0);
+
+		/* Remove any orphaned pg_propgraph_label entries */
+		if (!get_label_element_label_ids(labeloid))
+		{
+			ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid);
+			performDeletion(&obj, stmt->drop_behavior, 0);
+		}
+	}
+
+	if (stmt->add_properties)
+	{
+		Oid			peoid;
+		Oid			pgerelid;
+		Oid			labeloid;
+		Oid			ellabeloid;
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+								   Anum_pg_propgraph_label_oid,
+								   ObjectIdGetDatum(pgrelid),
+								   CStringGetDatum(stmt->alter_label));
+		if (!labeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+					parser_errposition(pstate, -1));
+
+		ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+									 Anum_pg_propgraph_element_label_oid,
+									 ObjectIdGetDatum(peoid),
+									 ObjectIdGetDatum(labeloid));
+		if (!ellabeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+					parser_errposition(pstate, -1));
+
+		pgerelid = get_element_relid(peoid);
+
+		insert_property_records(pgrelid, ellabeloid, pgerelid, stmt->add_properties);
+
+		CommandCounterIncrement();
+		check_element_properties(peoid);
+		check_element_label_properties(ellabeloid);
+	}
+
+	if (stmt->drop_properties)
+	{
+		Oid			peoid;
+		Oid			labeloid;
+		Oid			ellabeloid;
+		ObjectAddress obj;
+
+		if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+			peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+		else
+			peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+		labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+								   Anum_pg_propgraph_label_oid,
+								   ObjectIdGetDatum(pgrelid),
+								   CStringGetDatum(stmt->alter_label));
+		if (!labeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+					parser_errposition(pstate, -1));
+
+		ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+									 Anum_pg_propgraph_element_label_oid,
+									 ObjectIdGetDatum(peoid),
+									 ObjectIdGetDatum(labeloid));
+
+		if (!ellabeloid)
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+						   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+					parser_errposition(pstate, -1));
+
+		foreach(lc, stmt->drop_properties)
+		{
+			char	   *propname = strVal(lfirst(lc));
+			Oid			propoid;
+			Oid			plpoid;
+
+			propoid = GetSysCacheOid2(PROPGRAPHPROPNAME,
+									  Anum_pg_propgraph_property_oid,
+									  ObjectIdGetDatum(pgrelid),
+									  CStringGetDatum(propname));
+			if (!propoid)
+				ereport(ERROR,
+						errcode(ERRCODE_UNDEFINED_OBJECT),
+						errmsg("property graph \"%s\" element \"%s\" label \"%s\" has no property \"%s\"",
+							   get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label, propname),
+						parser_errposition(pstate, -1));
+
+			plpoid = GetSysCacheOid2(PROPGRAPHLABELPROP, Anum_pg_propgraph_label_property_oid, ObjectIdGetDatum(ellabeloid), ObjectIdGetDatum(propoid));
+
+			ObjectAddressSet(obj, PropgraphLabelPropertyRelationId, plpoid);
+			performDeletion(&obj, stmt->drop_behavior, 0);
+		}
+
+		check_element_label_properties(ellabeloid);
+	}
+
+	/* Remove any orphaned pg_propgraph_property entries */
+	if (stmt->drop_properties || stmt->drop_vertex_tables || stmt->drop_edge_tables)
+	{
+		foreach_oid(propoid, get_graph_property_ids(pgrelid))
+		{
+			Relation	rel;
+			SysScanDesc scan;
+			ScanKeyData key[1];
+
+			rel = table_open(PropgraphLabelPropertyRelationId, RowShareLock);
+			ScanKeyInit(&key[0],
+						Anum_pg_propgraph_label_property_plppropid,
+						BTEqualStrategyNumber, F_OIDEQ,
+						ObjectIdGetDatum(propoid));
+			scan = systable_beginscan(rel, InvalidOid /* FIXME */ ,
+									  true, NULL, 1, key);
+			if (!systable_getnext(scan))
+			{
+				ObjectAddress obj;
+
+				ObjectAddressSet(obj, PropgraphPropertyRelationId, propoid);
+				performDeletion(&obj, stmt->drop_behavior, 0);
+			}
+
+			systable_endscan(scan);
+			table_close(rel, RowShareLock);
+		}
+	}
+
+	return pgaddress;
+}
+
+/*
+ * Get OID of vertex from graph OID and element alias.  Element must be a
+ * vertex, otherwise error.
+ */
+static Oid
+get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location)
+{
+	HeapTuple	tuple;
+	Oid			peoid;
+
+	tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias));
+	if (!tuple)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("property graph \"%s\" has no element with alias \"%s\"",
+					   get_rel_name(pgrelid), alias),
+				parser_errposition(pstate, location));
+
+	if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_VERTEX)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("element \"%s\" of property graph \"%s\" is not a vertex",
+					   alias, get_rel_name(pgrelid)),
+				parser_errposition(pstate, location));
+
+	peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid;
+
+	ReleaseSysCache(tuple);
+
+	return peoid;
+}
+
+/*
+ * Get OID of edge from graph OID and element alias.  Element must be an edge,
+ * otherwise error.
+ */
+static Oid
+get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location)
+{
+	HeapTuple	tuple;
+	Oid			peoid;
+
+	tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias));
+	if (!tuple)
+		ereport(ERROR,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("property graph \"%s\" has no element with alias \"%s\"",
+					   get_rel_name(pgrelid), alias),
+				parser_errposition(pstate, location));
+
+	if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_EDGE)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("element \"%s\" of property graph \"%s\" is not an edge",
+					   alias, get_rel_name(pgrelid)),
+				parser_errposition(pstate, location));
+
+	peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid;
+
+	ReleaseSysCache(tuple);
+
+	return peoid;
+}
+
+/*
+ * Get the element table relation OID from the OID of the element.
+ */
+static Oid
+get_element_relid(Oid peid)
+{
+	HeapTuple	tuple;
+	Oid			pgerelid;
+
+	tuple = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peid));
+	if (!tuple)
+		elog(ERROR, "cache lookup failed for property graph element %u", peid);
+
+	pgerelid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgerelid;
+
+	ReleaseSysCache(tuple);
+
+	return pgerelid;
+}
+
+/*
+ * Get a list of all label OIDs of a graph.
+ */
+static List *
+get_graph_label_ids(Oid graphid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphLabelRelationId, AccessShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_label_pglpgid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(graphid));
+	scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		result = lappend_oid(result, ((Form_pg_propgraph_label) GETSTRUCT(tuple))->oid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Get a list of all element label OIDs for a label.
+ */
+static List *
+get_label_element_label_ids(Oid labelid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_pgellabelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(labelid));
+	scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		result = lappend_oid(result, ((Form_pg_propgraph_element_label) GETSTRUCT(tuple))->oid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Get the names of properties associated with the given element label OID.
+ *
+ * The result is a list of String nodes (so we can use list functions to
+ * detect differences).
+ */
+static List *
+get_element_label_property_names(Oid ellabeloid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_label_property_plpellabelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(ellabeloid));
+
+	scan = systable_beginscan(rel, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key);
+
+	while ((tuple = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple);
+
+		result = lappend(result, makeString(get_propgraph_property_name(plpform->plppropid)));
+	}
+
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Get a list of all property OIDs of a graph.
+ */
+static List *
+get_graph_property_ids(Oid graphid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphPropertyRelationId, AccessShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_property_pgppgid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(graphid));
+	scan = systable_beginscan(rel, PropgraphPropertyNameIndexId, true, NULL, 1, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		result = lappend_oid(result, ((Form_pg_propgraph_property) GETSTRUCT(tuple))->oid);
+	}
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+
+	return result;
+}
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5607273bf9..1933bd1ca3 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_OPFAMILY:
 		case OBJECT_PARAMETER_ACL:
 		case OBJECT_POLICY:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
 		case OBJECT_RULE:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dac39df83a..17918efb0c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -304,6 +304,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 		gettext_noop("index \"%s\" does not exist, skipping"),
 		gettext_noop("\"%s\" is not an index"),
 	gettext_noop("Use DROP INDEX to remove an index.")},
+	{RELKIND_PROPGRAPH,
+		ERRCODE_UNDEFINED_OBJECT,
+		gettext_noop("property graph \"%s\" does not exist"),
+		gettext_noop("property graph \"%s\" does not exist, skipping"),
+		gettext_noop("\"%s\" is not a property graph"),
+	gettext_noop("Use DROP PROPERTY GRAPH to remove a property graph.")},
 	{'\0', 0, NULL, NULL, NULL, NULL}
 };
 
@@ -1490,6 +1496,10 @@ RemoveRelations(DropStmt *drop)
 			relkind = RELKIND_FOREIGN_TABLE;
 			break;
 
+		case OBJECT_PROPGRAPH:
+			relkind = RELKIND_PROPGRAPH;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized drop object type: %d",
 				 (int) drop->removeType);
@@ -14317,6 +14327,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
 		case RELKIND_PARTITIONED_TABLE:
+		case RELKIND_PROPGRAPH:
 			/* ok to change owner */
 			break;
 		case RELKIND_INDEX:
@@ -17736,6 +17747,11 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a composite type", rv->relname)));
 
+	if (reltype == OBJECT_PROPGRAPH && relkind != RELKIND_PROPGRAPH)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a property graph", rv->relname)));
+
 	if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
 		relkind != RELKIND_PARTITIONED_INDEX
 		&& !IsA(stmt, RenameStmt))
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 29e186fa73..1cbca156a2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -595,7 +595,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos,
 			 */
 			Assert(rte->rtekind == RTE_RELATION ||
 				   (rte->rtekind == RTE_SUBQUERY &&
-					rte->relkind == RELKIND_VIEW));
+					(rte->relkind == RELKIND_VIEW || rte->relkind == RELKIND_PROPGRAPH)));
 
 			(void) getRTEPermissionInfo(rteperminfos, rte);
 			/* Many-to-one mapping not allowed */
@@ -1110,6 +1110,12 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
 					break;
 			}
 			break;
+		case RELKIND_PROPGRAPH:
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot change property graph \"%s\"",
+							RelationGetRelationName(resultRel))));
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1174,6 +1180,13 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 						 errmsg("cannot lock rows in foreign table \"%s\"",
 								RelationGetRelationName(rel))));
 			break;
+		case RELKIND_PROPGRAPH:
+			/* Should not get here; rewriter should have expanded the graph */
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot lock rows in property graph \"%s\"",
+							RelationGetRelationName(rel))));
+			break;
 		default:
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index d2e2af4f81..1b4ad32e64 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -281,6 +281,9 @@ exprType(const Node *expr)
 		case T_PlaceHolderVar:
 			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_GraphPropertyRef:
+			type = ((const GraphPropertyRef *) expr)->typeId;
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
@@ -531,6 +534,9 @@ exprTypmod(const Node *expr)
 			return ((const SetToDefault *) expr)->typeMod;
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+		case T_GraphPropertyRef:
+			/* TODO */
+			return -1;
 		default:
 			break;
 	}
@@ -1050,6 +1056,9 @@ exprCollation(const Node *expr)
 		case T_PlaceHolderVar:
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
+		case T_GraphPropertyRef:
+			coll = DEFAULT_COLLATION_OID;	/* FIXME */
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			coll = InvalidOid;	/* keep compiler quiet */
@@ -2118,6 +2127,7 @@ expression_tree_walker_impl(Node *node,
 		case T_RangeTblRef:
 		case T_SortGroupClause:
 		case T_CTESearchClause:
+		case T_GraphPropertyRef:
 		case T_MergeSupportFunc:
 			/* primitive node types with no expression subnodes */
 			break;
@@ -2656,6 +2666,26 @@ expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_GraphElementPattern:
+			{
+				GraphElementPattern *gep = (GraphElementPattern *) node;
+
+				if (WALK(gep->subexpr))
+					return true;
+				if (WALK(gep->whereClause))
+					return true;
+			}
+			break;
+		case T_GraphPattern:
+			{
+				GraphPattern *gp = (GraphPattern *) node;
+
+				if (LIST_WALK(gp->path_pattern_list))
+					return true;
+				if (WALK(gp->whereClause))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -2849,6 +2879,12 @@ range_table_entry_walker_impl(RangeTblEntry *rte,
 			if (WALK(rte->values_lists))
 				return true;
 			break;
+		case RTE_GRAPH_TABLE:
+			if (WALK(rte->graph_pattern))
+				return true;
+			if (WALK(rte->graph_table_columns))
+				return true;
+			break;
 		case RTE_CTE:
 		case RTE_NAMEDTUPLESTORE:
 		case RTE_RESULT:
@@ -3886,6 +3922,10 @@ range_table_mutator_impl(List *rtable,
 			case RTE_VALUES:
 				MUTATE(newrte->values_lists, rte->values_lists, List *);
 				break;
+			case RTE_GRAPH_TABLE:
+				MUTATE(newrte->graph_pattern, rte->graph_pattern, GraphPattern *);
+				MUTATE(newrte->graph_table_columns, rte->graph_table_columns, List *);
+				break;
 			case RTE_CTE:
 			case RTE_NAMEDTUPLESTORE:
 			case RTE_RESULT:
@@ -4500,6 +4540,18 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_RangeGraphTable:
+			{
+				RangeGraphTable *rgt = (RangeGraphTable *) node;
+
+				if (WALK(rgt->graph_pattern))
+					return true;
+				if (WALK(rgt->columns))
+					return true;
+				if (WALK(rgt->alias))
+					return true;
+			}
+			break;
 		case T_TypeName:
 			{
 				TypeName   *tn = (TypeName *) node;
@@ -4658,6 +4710,26 @@ raw_expression_tree_walker_impl(Node *node,
 					return true;
 			}
 			break;
+		case T_GraphElementPattern:
+			{
+				GraphElementPattern *gep = (GraphElementPattern *) node;
+
+				if (WALK(gep->subexpr))
+					return true;
+				if (WALK(gep->whereClause))
+					return true;
+			}
+			break;
+		case T_GraphPattern:
+			{
+				GraphPattern *gp = (GraphPattern *) node;
+
+				if (WALK(gp->path_pattern_list))
+					return true;
+				if (WALK(gp->whereClause))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3337b77ae6..bfce452ded 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -559,6 +559,11 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			/* we re-use these RELATION fields, too: */
 			WRITE_OID_FIELD(relid);
 			break;
+		case RTE_GRAPH_TABLE:
+			WRITE_OID_FIELD(relid);
+			WRITE_NODE_FIELD(graph_pattern);
+			WRITE_NODE_FIELD(graph_table_columns);
+			break;
 		case RTE_RESULT:
 			/* no extra fields */
 			break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b47950764a..42fe4f4f4b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -419,6 +419,11 @@ _readRangeTblEntry(void)
 			/* we re-use these RELATION fields, too: */
 			READ_OID_FIELD(relid);
 			break;
+		case RTE_GRAPH_TABLE:
+			READ_OID_FIELD(relid);
+			READ_NODE_FIELD(graph_pattern);
+			READ_NODE_FIELD(graph_table_columns);
+			break;
 		case RTE_RESULT:
 			/* no extra fields */
 			break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 057b4b79eb..d363877c97 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -728,6 +728,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 */
 			return;
 
+		case RTE_GRAPH_TABLE:
+			/* shouldn't happen here */
+			break;
+
 		case RTE_RESULT:
 			/* RESULT RTEs, in themselves, are no problem. */
 			break;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 969e257f70..b81660b530 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1237,6 +1237,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 				case RTE_RESULT:
 					/* these can't contain any lateral references */
 					break;
+				case RTE_GRAPH_TABLE:
+					/* shouldn't happen here */
+					Assert(false);
+					break;
 			}
 		}
 	}
@@ -2296,6 +2300,10 @@ replace_vars_in_jointree(Node *jtnode,
 						/* these shouldn't be marked LATERAL */
 						Assert(false);
 						break;
+					case RTE_GRAPH_TABLE:
+						/* shouldn't happen here */
+						Assert(false);
+						break;
 				}
 			}
 		}
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 3162a01f30..0fa237a4a9 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -23,6 +23,7 @@ OBJS = \
 	parse_enr.o \
 	parse_expr.o \
 	parse_func.o \
+	parse_graphtable.o \
 	parse_jsontable.o \
 	parse_merge.o \
 	parse_node.o \
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index e901203424..0f2229504b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2064,7 +2064,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		char		selectName[32];
 		ParseNamespaceItem *nsitem;
 		RangeTblRef *rtr;
-		ListCell   *tl;
 
 		/*
 		 * Transform SelectStmt into a Query.
@@ -2104,6 +2103,8 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		 */
 		if (targetlist)
 		{
+			ListCell   *tl;
+
 			*targetlist = NIL;
 			foreach(tl, selectQuery->targetList)
 			{
@@ -2138,8 +2139,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 		bool		recursive = (pstate->p_parent_cte &&
 								 pstate->p_parent_cte->cterecursive);
@@ -2174,161 +2173,170 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
-		 * Verify that the two children have the same number of non-junk
-		 * columns, and determine the types of the merged output columns.
-		 */
-		if (list_length(ltargetlist) != list_length(rtargetlist))
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("each %s query must have the same number of columns",
-							context),
-					 parser_errposition(pstate,
-										exprLocation((Node *) rtargetlist))));
+		constructSetOpTargetlist(op, ltargetlist, rtargetlist, targetlist,
+								 context, pstate, recursive);
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
-		op->colCollations = NIL;
-		op->groupClauses = NIL;
-		forboth(ltl, ltargetlist, rtl, rtargetlist)
-		{
-			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-			TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-			Node	   *lcolnode = (Node *) ltle->expr;
-			Node	   *rcolnode = (Node *) rtle->expr;
-			Oid			lcoltype = exprType(lcolnode);
-			Oid			rcoltype = exprType(rcolnode);
-			Node	   *bestexpr;
-			int			bestlocation;
-			Oid			rescoltype;
-			int32		rescoltypmod;
-			Oid			rescolcoll;
-
-			/* select common type, same as CASE et al */
-			rescoltype = select_common_type(pstate,
-											list_make2(lcolnode, rcolnode),
-											context,
-											&bestexpr);
-			bestlocation = exprLocation(bestexpr);
+		return (Node *) op;
+	}
+}
 
-			/*
-			 * Verify the coercions are actually possible.  If not, we'd fail
-			 * later anyway, but we want to fail now while we have sufficient
-			 * context to produce an error cursor position.
-			 *
-			 * For all non-UNKNOWN-type cases, we verify coercibility but we
-			 * don't modify the child's expression, for fear of changing the
-			 * child query's semantics.
-			 *
-			 * If a child expression is an UNKNOWN-type Const or Param, we
-			 * want to replace it with the coerced expression.  This can only
-			 * happen when the child is a leaf set-op node.  It's safe to
-			 * replace the expression because if the child query's semantics
-			 * depended on the type of this output column, it'd have already
-			 * coerced the UNKNOWN to something else.  We want to do this
-			 * because (a) we want to verify that a Const is valid for the
-			 * target type, or resolve the actual type of an UNKNOWN Param,
-			 * and (b) we want to avoid unnecessary discrepancies between the
-			 * output type of the child query and the resolved target type.
-			 * Such a discrepancy would disable optimization in the planner.
-			 *
-			 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-			 * (knowing that coerce_to_common_type would fail).  The planner
-			 * is sometimes able to fold an UNKNOWN Var to a constant before
-			 * it has to coerce the type, so failing now would just break
-			 * cases that might work.
-			 */
-			if (lcoltype != UNKNOWNOID)
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-			else if (IsA(lcolnode, Const) ||
-					 IsA(lcolnode, Param))
-			{
-				lcolnode = coerce_to_common_type(pstate, lcolnode,
-												 rescoltype, context);
-				ltle->expr = (Expr *) lcolnode;
-			}
+void
+constructSetOpTargetlist(SetOperationStmt *op, List *ltargetlist, List *rtargetlist,
+						 List **targetlist, const char *context, ParseState *pstate,
+						 bool recursive)
+{
+	ListCell   *ltl;
+	ListCell   *rtl;
 
-			if (rcoltype != UNKNOWNOID)
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-			else if (IsA(rcolnode, Const) ||
-					 IsA(rcolnode, Param))
-			{
-				rcolnode = coerce_to_common_type(pstate, rcolnode,
-												 rescoltype, context);
-				rtle->expr = (Expr *) rcolnode;
-			}
+	/*
+	 * Verify that the two children have the same number of non-junk columns,
+	 * and determine the types of the merged output columns.
+	 */
+	if (list_length(ltargetlist) != list_length(rtargetlist))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("each %s query must have the same number of columns",
+						context),
+				 parser_errposition(pstate,
+									exprLocation((Node *) rtargetlist))));
 
-			rescoltypmod = select_common_typmod(pstate,
-												list_make2(lcolnode, rcolnode),
-												rescoltype);
+	if (targetlist)
+		*targetlist = NIL;
+	op->colTypes = NIL;
+	op->colTypmods = NIL;
+	op->colCollations = NIL;
+	op->groupClauses = NIL;
 
-			/*
-			 * Select common collation.  A common collation is required for
-			 * all set operators except UNION ALL; see SQL:2008 7.13 <query
-			 * expression> Syntax Rule 15c.  (If we fail to identify a common
-			 * collation for a UNION ALL column, the colCollations element
-			 * will be set to InvalidOid, which may result in a runtime error
-			 * if something at a higher query level wants to use the column's
-			 * collation.)
-			 */
-			rescolcoll = select_common_collation(pstate,
-												 list_make2(lcolnode, rcolnode),
-												 (op->op == SETOP_UNION && op->all));
+	forboth(ltl, ltargetlist, rtl, rtargetlist)
+	{
+		TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+		TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+		Node	   *lcolnode = (Node *) ltle->expr;
+		Node	   *rcolnode = (Node *) rtle->expr;
+		Oid			lcoltype = exprType(lcolnode);
+		Oid			rcoltype = exprType(rcolnode);
+		Node	   *bestexpr;
+		int			bestlocation;
+		Oid			rescoltype;
+		int32		rescoltypmod;
+		Oid			rescolcoll;
+
+		/* select common type, same as CASE et al */
+		rescoltype = select_common_type(pstate,
+										list_make2(lcolnode, rcolnode),
+										context,
+										&bestexpr);
+		bestlocation = exprLocation(bestexpr);
 
-			/* emit results */
-			op->colTypes = lappend_oid(op->colTypes, rescoltype);
-			op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-			op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+		/*
+		 * Verify the coercions are actually possible.  If not, we'd fail
+		 * later anyway, but we want to fail now while we have sufficient
+		 * context to produce an error cursor position.
+		 *
+		 * For all non-UNKNOWN-type cases, we verify coercibility but we don't
+		 * modify the child's expression, for fear of changing the child
+		 * query's semantics.
+		 *
+		 * If a child expression is an UNKNOWN-type Const or Param, we want to
+		 * replace it with the coerced expression.  This can only happen when
+		 * the child is a leaf set-op node.  It's safe to replace the
+		 * expression because if the child query's semantics depended on the
+		 * type of this output column, it'd have already coerced the UNKNOWN
+		 * to something else.  We want to do this because (a) we want to
+		 * verify that a Const is valid for the target type, or resolve the
+		 * actual type of an UNKNOWN Param, and (b) we want to avoid
+		 * unnecessary discrepancies between the output type of the child
+		 * query and the resolved target type. Such a discrepancy would
+		 * disable optimization in the planner.
+		 *
+		 * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+		 * (knowing that coerce_to_common_type would fail).  The planner is
+		 * sometimes able to fold an UNKNOWN Var to a constant before it has
+		 * to coerce the type, so failing now would just break cases that
+		 * might work.
+		 */
+		if (lcoltype != UNKNOWNOID)
+			lcolnode = coerce_to_common_type(pstate, lcolnode,
+											 rescoltype, context);
+		else if (IsA(lcolnode, Const) ||
+				 IsA(lcolnode, Param))
+		{
+			lcolnode = coerce_to_common_type(pstate, lcolnode,
+											 rescoltype, context);
+			ltle->expr = (Expr *) lcolnode;
+		}
 
-			/*
-			 * For all cases except UNION ALL, identify the grouping operators
-			 * (and, if available, sorting operators) that will be used to
-			 * eliminate duplicates.
-			 */
-			if (op->op != SETOP_UNION || !op->all)
-			{
-				ParseCallbackState pcbstate;
+		if (rcoltype != UNKNOWNOID)
+			rcolnode = coerce_to_common_type(pstate, rcolnode,
+											 rescoltype, context);
+		else if (IsA(rcolnode, Const) ||
+				 IsA(rcolnode, Param))
+		{
+			rcolnode = coerce_to_common_type(pstate, rcolnode,
+											 rescoltype, context);
+			rtle->expr = (Expr *) rcolnode;
+		}
 
-				setup_parser_errposition_callback(&pcbstate, pstate,
-												  bestlocation);
+		rescoltypmod = select_common_typmod(pstate,
+											list_make2(lcolnode, rcolnode),
+											rescoltype);
 
-				/*
-				 * If it's a recursive union, we need to require hashing
-				 * support.
-				 */
-				op->groupClauses = lappend(op->groupClauses,
-										   makeSortGroupClauseForSetOp(rescoltype, recursive));
+		/*
+		 * Select common collation.  A common collation is required for all
+		 * set operators except UNION ALL; see SQL:2008 7.13 <query
+		 * expression> Syntax Rule 15c.  (If we fail to identify a common
+		 * collation for a UNION ALL column, the colCollations element will be
+		 * set to InvalidOid, which may result in a runtime error if something
+		 * at a higher query level wants to use the column's collation.)
+		 */
+		rescolcoll = select_common_collation(pstate,
+											 list_make2(lcolnode, rcolnode),
+											 (op->op == SETOP_UNION && op->all));
 
-				cancel_parser_errposition_callback(&pcbstate);
-			}
+		/* emit results */
+		op->colTypes = lappend_oid(op->colTypes, rescoltype);
+		op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+		op->colCollations = lappend_oid(op->colCollations, rescolcoll);
 
-			/*
-			 * Construct a dummy tlist entry to return.  We use a SetToDefault
-			 * node for the expression, since it carries exactly the fields
-			 * needed, but any other expression node type would do as well.
-			 */
-			if (targetlist)
-			{
-				SetToDefault *rescolnode = makeNode(SetToDefault);
-				TargetEntry *restle;
-
-				rescolnode->typeId = rescoltype;
-				rescolnode->typeMod = rescoltypmod;
-				rescolnode->collation = rescolcoll;
-				rescolnode->location = bestlocation;
-				restle = makeTargetEntry((Expr *) rescolnode,
-										 0, /* no need to set resno */
-										 NULL,
-										 false);
-				*targetlist = lappend(*targetlist, restle);
-			}
+		/*
+		 * For all cases except UNION ALL, identify the grouping operators
+		 * (and, if available, sorting operators) that will be used to
+		 * eliminate duplicates.
+		 */
+		if (op->op != SETOP_UNION || !op->all)
+		{
+			ParseCallbackState pcbstate;
+
+			setup_parser_errposition_callback(&pcbstate, pstate,
+											  bestlocation);
+
+			/* If it's a recursive union, we need to require hashing support. */
+			op->groupClauses = lappend(op->groupClauses,
+									   makeSortGroupClauseForSetOp(rescoltype, recursive));
+
+			cancel_parser_errposition_callback(&pcbstate);
 		}
 
-		return (Node *) op;
+		/*
+		 * Construct a dummy tlist entry to return.  We use a SetToDefault
+		 * node for the expression, since it carries exactly the fields
+		 * needed, but any other expression node type would do as well.
+		 */
+		if (targetlist)
+		{
+			SetToDefault *rescolnode = makeNode(SetToDefault);
+			TargetEntry *restle;
+
+			rescolnode->typeId = rescoltype;
+			rescolnode->typeMod = rescoltypmod;
+			rescolnode->collation = rescolcoll;
+			rescolnode->location = bestlocation;
+			restle = makeTargetEntry((Expr *) rescolnode,
+									 0, /* no need to set resno */
+									 NULL,
+									 false);
+			*targetlist = lappend(*targetlist, restle);
+		}
 	}
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 84cef57a70..ace91478d4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -297,6 +297,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
 		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
+		CreatePropGraphStmt AlterPropGraphStmt
 		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
 		DropOpClassStmt DropOpFamilyStmt DropStmt
@@ -676,6 +677,36 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
+%type <list>	vertex_tables_clause edge_tables_clause
+				opt_vertex_tables_clause opt_edge_tables_clause
+				vertex_table_list
+				opt_graph_table_key_clause
+				edge_table_list
+				source_vertex_table destination_vertex_table
+				opt_element_table_label_and_properties
+				label_and_properties_list
+				add_label_list
+%type <node>	vertex_table_definition edge_table_definition
+%type <alias>	opt_propgraph_table_alias
+%type <str>		element_table_label_clause
+%type <node>	label_and_properties element_table_properties
+				add_label
+%type <ival>	vertex_or_edge
+
+%type <list>	opt_graph_pattern_quantifier
+				path_pattern_list
+				path_pattern
+				path_pattern_expression
+				path_term
+%type <node>	graph_pattern
+				path_factor
+				path_primary
+				opt_is_label_expression
+				label_expression
+				label_disjunction
+				label_term
+%type <str>		opt_colid
+
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -719,18 +750,18 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
-	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
+	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC DESTINATION
 	DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
 	DOUBLE_P DROP
 
-	EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE
+	EACH EDGE ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE
 	EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
 	EXTENSION EXTERNAL EXTRACT
 
 	FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
 	FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
-	GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
+	GENERATED GLOBAL GRANT GRANTED GRAPH GRAPH_TABLE GREATEST GROUP_P GROUPING GROUPS
 
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
@@ -751,7 +782,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
 
-	NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO
+	NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO NODE
 	NONE NORMALIZE NORMALIZED
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
 	NULLS_P NUMERIC
@@ -763,12 +794,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
 	PLACING PLAN PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
-	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
+	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PROPERTIES PROPERTY PUBLICATION
 
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIONSHIP RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
 	RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -788,7 +819,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERTEX VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -886,7 +917,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 			SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
-%left		Op OPERATOR		/* multi-character ops and user-defined operators */
+%left		Op OPERATOR RIGHT_ARROW '|'	/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
 %left		'^'
@@ -1017,6 +1048,7 @@ stmt:
 			| AlterOperatorStmt
 			| AlterTypeStmt
 			| AlterPolicyStmt
+			| AlterPropGraphStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
 			| AlterTableStmt
@@ -1057,6 +1089,7 @@ stmt:
 			| AlterOpFamilyStmt
 			| CreatePolicyStmt
 			| CreatePLangStmt
+			| CreatePropGraphStmt
 			| CreateSchemaStmt
 			| CreateSeqStmt
 			| CreateStmt
@@ -6929,6 +6962,7 @@ object_type_any_name:
 			| MATERIALIZED VIEW						{ $$ = OBJECT_MATVIEW; }
 			| INDEX									{ $$ = OBJECT_INDEX; }
 			| FOREIGN TABLE							{ $$ = OBJECT_FOREIGN_TABLE; }
+			| PROPERTY GRAPH						{ $$ = OBJECT_PROPGRAPH; }
 			| COLLATION								{ $$ = OBJECT_COLLATION; }
 			| CONVERSION_P							{ $$ = OBJECT_CONVERSION; }
 			| STATISTICS							{ $$ = OBJECT_STATISTIC_EXT; }
@@ -7785,6 +7819,15 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
+			| PROPERTY GRAPH qualified_name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_PROPGRAPH;
+					n->objs = $3;
+					$$ = n;
+				}
 			| SCHEMA name_list
 				{
 					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -9102,6 +9145,366 @@ opt_if_exists: IF_P EXISTS						{ $$ = true; }
 		;

+/*****************************************************************************
+ *
+ *		CREATE PROPERTY GRAPH
+ *		ALTER PROPERTY GRAPH
+ *
+ *****************************************************************************/
+
+CreatePropGraphStmt: CREATE OptTemp PROPERTY GRAPH qualified_name opt_vertex_tables_clause opt_edge_tables_clause
+				{
+					CreatePropGraphStmt *n = makeNode(CreatePropGraphStmt);
+
+					n->pgname = $5;
+					n->pgname->relpersistence = $2;
+					n->vertex_tables = $6;
+					n->edge_tables = $7;
+
+					$$ = (Node *)n;
+				}
+		;
+
+opt_vertex_tables_clause:
+			vertex_tables_clause				{ $$ = $1; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
+vertex_tables_clause:
+			vertex_synonym TABLES '(' vertex_table_list ')'	{ $$ = $4; }
+		;
+
+vertex_synonym: NODE | VERTEX
+		;
+
+vertex_table_list: vertex_table_definition						{ $$ = list_make1($1); }
+			| vertex_table_list ',' vertex_table_definition		{ $$ = lappend($1, $3); }
+		;
+
+vertex_table_definition: qualified_name opt_propgraph_table_alias opt_graph_table_key_clause
+				opt_element_table_label_and_properties
+				{
+					PropGraphVertex *n = makeNode(PropGraphVertex);
+
+					$1->alias = $2;
+					n->vtable = $1;
+					n->vkey = $3;
+					n->labels = $4;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+		;
+
+opt_propgraph_table_alias:
+			AS name
+				{
+					$$ = makeNode(Alias);
+					$$->aliasname = $2;
+				}
+			| /*EMPTY*/							{ $$ = NULL; }
+		;
+
+opt_graph_table_key_clause:
+			KEY '(' columnList ')'				{ $$ = $3; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
+opt_edge_tables_clause:
+			edge_tables_clause					{ $$ = $1; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
+edge_tables_clause:
+			edge_synonym TABLES '(' edge_table_list	')'			{ $$ = $4; }
+		;
+
+edge_synonym: EDGE | RELATIONSHIP
+		;
+
+edge_table_list: edge_table_definition						{ $$ = list_make1($1); }
+			| edge_table_list ',' edge_table_definition		{ $$ = lappend($1, $3); }
+		;
+
+edge_table_definition: qualified_name opt_propgraph_table_alias opt_graph_table_key_clause
+				source_vertex_table destination_vertex_table opt_element_table_label_and_properties
+				{
+					PropGraphEdge *n = makeNode(PropGraphEdge);
+
+					$1->alias = $2;
+					n->etable = $1;
+					n->ekey = $3;
+					n->esrckey = linitial($4);
+					n->esrcvertex = lsecond($4);
+					n->esrcvertexcols = lthird($4);
+					n->edestkey = linitial($5);
+					n->edestvertex = lsecond($5);
+					n->edestvertexcols = lthird($5);
+					n->labels = $6;
+					n->location = @1;
+
+					$$ = (Node *) n;
+				}
+		;
+
+source_vertex_table: SOURCE name
+				{
+					$$ = list_make3(NULL, $2, NULL);
+				}
+				| SOURCE KEY '(' columnList ')' REFERENCES name '(' columnList ')'
+				{
+					$$ = list_make3($4, $7, $9);
+				}
+		;
+
+destination_vertex_table: DESTINATION name
+				{
+					$$ = list_make3(NULL, $2, NULL);
+				}
+				| DESTINATION KEY '(' columnList ')' REFERENCES name '(' columnList ')'
+				{
+					$$ = list_make3($4, $7, $9);
+				}
+		;
+
+opt_element_table_label_and_properties:
+			element_table_properties
+				{
+					PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties);
+
+					lp->properties = (PropGraphProperties *) $1;
+					lp->location = @1;
+
+					$$ = list_make1(lp);
+				}
+			| label_and_properties_list
+				{
+					$$ = $1;
+				}
+			| /*EMPTY*/
+				{
+					PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties);
+					PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+					pr->all = true;
+					pr->location = -1;
+					lp->properties = pr;
+					lp->location = -1;
+
+					$$ = list_make1(lp);
+				}
+		;
+
+element_table_properties:
+			NO PROPERTIES
+				{
+					PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+					pr->properties = NIL;
+					pr->location = @1;
+
+					$$ = (Node *) pr;
+				}
+			| PROPERTIES ALL COLUMNS
+			/*| PROPERTIES ARE ALL COLUMNS */
+				{
+					PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+					pr->all = true;
+					pr->location = @1;
+
+					$$ = (Node *) pr;
+				}
+			| PROPERTIES '(' xml_attribute_list ')'
+				{
+					PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+					pr->properties = $3;
+					pr->location = @1;
+
+					$$ = (Node *) pr;
+				}
+		;
+
+label_and_properties_list:
+			label_and_properties
+				{
+					$$ = list_make1($1);
+				}
+			| label_and_properties_list label_and_properties
+				{
+					$$ = lappend($1, $2);
+				}
+		;
+
+label_and_properties:
+			element_table_label_clause
+				{
+					PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties);
+					PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+					pr->all = true;
+					pr->location = -1;
+
+					lp->label = $1;
+					lp->properties = pr;
+					lp->location = @1;
+
+					$$ = (Node *) lp;
+				}
+			| element_table_label_clause element_table_properties
+				{
+					PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties);
+
+					lp->label = $1;
+					lp->properties = (PropGraphProperties *) $2;
+					lp->location = @1;
+
+					$$ = (Node *) lp;
+				}
+		;
+
+element_table_label_clause:
+			LABEL name
+				{
+					$$ = $2;
+				}
+			| DEFAULT LABEL
+				{
+					$$ = NULL;
+				}
+		;
+
+AlterPropGraphStmt:
+			ALTER PROPERTY GRAPH qualified_name ADD_P vertex_tables_clause
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->add_vertex_tables = $6;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name ADD_P vertex_tables_clause ADD_P edge_tables_clause
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->add_vertex_tables = $6;
+					n->add_edge_tables = $8;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name ADD_P edge_tables_clause
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->add_edge_tables = $6;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name DROP vertex_synonym TABLES '(' name_list ')' opt_drop_behavior
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->drop_vertex_tables = $9;
+					n->drop_behavior = $11;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name DROP edge_synonym TABLES '(' name_list ')' opt_drop_behavior
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->drop_edge_tables = $9;
+					n->drop_behavior = $11;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name
+				add_label_list
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->element_kind = $6;
+					n->element_alias = $8;
+					n->add_labels = $9;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name
+				DROP LABEL name opt_drop_behavior
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->element_kind = $6;
+					n->element_alias = $8;
+					n->drop_label = $11;
+					n->drop_behavior = $12;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name
+				ALTER LABEL name ADD_P PROPERTIES '(' xml_attribute_list ')'
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+					PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+					n->pgname = $4;
+					n->element_kind = $6;
+					n->element_alias = $8;
+					n->alter_label = $11;
+
+					pr->properties = $15;
+					pr->location = @13;
+					n->add_properties = pr;
+
+					$$ = (Node *) n;
+				}
+			| ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name
+				ALTER LABEL name DROP PROPERTIES '(' name_list ')' opt_drop_behavior
+				{
+					AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt);
+
+					n->pgname = $4;
+					n->element_kind = $6;
+					n->element_alias = $8;
+					n->alter_label = $11;
+					n->drop_properties = $15;
+					n->drop_behavior = $17;
+
+					$$ = (Node *) n;
+				}
+		;
+
+vertex_or_edge:
+			vertex_synonym						{ $$ = PROPGRAPH_ELEMENT_KIND_VERTEX; }
+			| edge_synonym						{ $$ = PROPGRAPH_ELEMENT_KIND_EDGE; }
+		;
+
+add_label_list:
+			add_label							{ $$ = list_make1($1); }
+			| add_label_list add_label			{ $$ = lappend($1, $2);	}
+		;
+
+add_label: ADD_P LABEL name element_table_properties
+				{
+					PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties);
+
+					lp->label = $3;
+					lp->properties = (PropGraphProperties *) $4;
+					lp->location = @1;
+
+					$$ = (Node *) lp;
+				}
+		;
+
+
 /*****************************************************************************
  *
  *		CREATE TRANSFORM / DROP TRANSFORM
@@ -9402,6 +9805,16 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER PROPERTY GRAPH qualified_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+
+					n->renameType = OBJECT_PROPGRAPH;
+					n->relation = $4;
+					n->newname = $7;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER PUBLICATION name RENAME TO name
 				{
 					RenameStmt *n = makeNode(RenameStmt);
@@ -10027,6 +10440,26 @@ AlterObjectSchemaStmt:
 					n->missing_ok = false;
 					$$ = (Node *) n;
 				}
+			| ALTER PROPERTY GRAPH qualified_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+
+					n->objectType = OBJECT_PROPGRAPH;
+					n->relation = $4;
+					n->newschema = $7;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER PROPERTY GRAPH IF_P EXISTS qualified_name SET SCHEMA name
+				{
+					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+
+					n->objectType = OBJECT_PROPGRAPH;
+					n->relation = $6;
+					n->newschema = $9;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
 			| ALTER ROUTINE function_with_argtypes SET SCHEMA name
 				{
 					AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
@@ -10370,6 +10803,15 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
 					n->newowner = $6;
 					$$ = (Node *) n;
 				}
+			| ALTER PROPERTY GRAPH qualified_name OWNER TO RoleSpec
+				{
+					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+
+					n->objectType = OBJECT_PROPGRAPH;
+					n->relation = $4;
+					n->newowner = $7;
+					$$ = (Node *) n;
+				}
 			| ALTER ROUTINE function_with_argtypes OWNER TO RoleSpec
 				{
 					AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
@@ -13464,6 +13906,17 @@ table_ref:	relation_expr opt_alias_clause
 					n->alias = $3;
 					$$ = (Node *) n;
 				}
+			| GRAPH_TABLE '(' qualified_name MATCH graph_pattern COLUMNS '(' xml_attribute_list ')' ')' opt_alias_clause
+				{
+					RangeGraphTable *n = makeNode(RangeGraphTable);
+
+					n->graph_name = $3;
+					n->graph_pattern = castNode(GraphPattern, $5);
+					n->columns = $8;
+					n->alias = $11;
+					n->location = @1;
+					$$ = (Node *) n;
+				}
 			| select_with_parens opt_alias_clause
 				{
 					RangeSubselect *n = makeNode(RangeSubselect);
@@ -14808,6 +15261,10 @@ a_expr:		c_expr									{ $$ = $1; }
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
 			| a_expr NOT_EQUALS a_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
+			| a_expr RIGHT_ARROW a_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
+			| a_expr '|' a_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); }
 
 			| a_expr qual_Op a_expr				%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
@@ -15287,6 +15744,10 @@ b_expr:		c_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
 			| b_expr NOT_EQUALS b_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
+			| b_expr RIGHT_ARROW b_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
+			| b_expr '|' b_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); }
 			| b_expr qual_Op b_expr				%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
 			| qual_Op b_expr					%prec Op
@@ -16455,6 +16916,8 @@ MathOp:		 '+'									{ $$ = "+"; }
 			| LESS_EQUALS							{ $$ = "<="; }
 			| GREATER_EQUALS						{ $$ = ">="; }
 			| NOT_EQUALS							{ $$ = "<>"; }
+			| RIGHT_ARROW							{ $$ = "->"; }
+			| '|'									{ $$ = "|"; }
 		;
 
 qual_Op:	Op
@@ -17045,6 +17508,213 @@ json_array_aggregate_order_by_clause_opt:
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
+
+/*****************************************************************************
+ *
+ *	graph patterns
+ *
+ *****************************************************************************/
+
+graph_pattern:
+			path_pattern_list where_clause
+				{
+					GraphPattern *gp = makeNode(GraphPattern);
+
+					gp->path_pattern_list = $1;
+					gp->whereClause = $2;
+					$$ = (Node *) gp;
+				}
+		;
+
+path_pattern_list:
+			path_pattern							{ $$ = list_make1($1); }
+			| path_pattern_list ',' path_pattern	{ $$ = lappend($1, $3); }
+		;
+
+path_pattern:
+			path_pattern_expression					{ $$ = $1; }
+		;
+
+/*
+ * path pattern expression
+ */
+
+path_pattern_expression:
+			path_term								{ $$ = $1; }
+			/* | path_multiset_alternation */
+			/* | path_pattern_union */
+		;
+
+path_term:
+			path_factor								{ $$ = list_make1($1); }
+			| path_term path_factor					{ $$ = lappend($1, $2); }
+		;
+
+path_factor:
+			path_primary opt_graph_pattern_quantifier
+				{
+					GraphElementPattern *gep = (GraphElementPattern *) $1;
+
+					gep->quantifier = $2;
+				}
+		;
+
+path_primary:
+			'(' opt_colid opt_is_label_expression where_clause ')'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = VERTEX_PATTERN;
+					gep->variable = $2;
+					gep->labelexpr = $3;
+					gep->whereClause = $4;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge pointing left: <-[ xxx ]- */
+			| '<' '-' '[' opt_colid opt_is_label_expression where_clause ']' '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_LEFT;
+					gep->variable = $4;
+					gep->labelexpr = $5;
+					gep->whereClause = $6;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge pointing right: -[ xxx ]-> */
+			| '-' '[' opt_colid opt_is_label_expression where_clause ']' '-' '>'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->variable = $3;
+					gep->labelexpr = $4;
+					gep->whereClause = $5;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '-' '[' opt_colid opt_is_label_expression where_clause ']' RIGHT_ARROW
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->variable = $3;
+					gep->labelexpr = $4;
+					gep->whereClause = $5;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge any direction: -[ xxx ]- */
+			| '-' '[' opt_colid opt_is_label_expression where_clause ']' '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_ANY;
+					gep->variable = $3;
+					gep->labelexpr = $4;
+					gep->whereClause = $5;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* abbreviated edge patterns */
+			| '<' '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_LEFT;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '-' '>'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| RIGHT_ARROW
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_ANY;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '(' path_pattern_expression where_clause ')'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = PAREN_EXPR;
+					gep->subexpr = $2;
+					gep->whereClause = $3;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+		;
+
+opt_colid:
+			ColId			{ $$ = $1; }
+			| /*EMPTY*/		{ $$ = NULL; }
+		;
+
+opt_is_label_expression:
+			IS label_expression		{ $$ = $2; }
+			| ':' label_expression	{ $$ = $2; }
+			| /*EMPTY*/				{ $$ = NULL; }
+		;
+
+/*
+ * graph pattern quantifier
+ */
+
+opt_graph_pattern_quantifier:
+			'{' Iconst '}'					{ $$ = list_make2_int($2, $2); }
+			| '{' ',' Iconst '}'			{ $$ = list_make2_int(0, $3); }
+			| '{' Iconst ',' Iconst '}'		{ $$ = list_make2_int($2, $4); }
+			| /*EMPTY*/						{ $$ = NULL; }
+		;
+
+/*
+ * label expression
+ */
+
+label_expression:
+			label_term
+			| label_disjunction
+		;
+
+label_disjunction:
+			label_expression '|' label_term
+				{ $$ = makeOrExpr($1, $3, @2); }
+		;
+
+label_term:
+			name
+				{ $$ = makeColumnRef($1, NIL, @1, yyscanner); }
+		;
+
+
 /*****************************************************************************
  *
  *	target list for SELECT
@@ -17564,6 +18234,7 @@ unreserved_keyword:
 			| DELIMITERS
 			| DEPENDS
 			| DEPTH
+			| DESTINATION
 			| DETACH
 			| DICTIONARY
 			| DISABLE_P
@@ -17573,6 +18244,7 @@ unreserved_keyword:
 			| DOUBLE_P
 			| DROP
 			| EACH
+			| EDGE
 			| EMPTY_P
 			| ENABLE_P
 			| ENCODING
@@ -17602,6 +18274,7 @@ unreserved_keyword:
 			| GENERATED
 			| GLOBAL
 			| GRANTED
+			| GRAPH
 			| GROUPS
 			| HANDLER
 			| HEADER_P
@@ -17666,6 +18339,7 @@ unreserved_keyword:
 			| NFKC
 			| NFKD
 			| NO
+			| NODE
 			| NORMALIZED
 			| NOTHING
 			| NOTIFY
@@ -17707,6 +18381,8 @@ unreserved_keyword:
 			| PROCEDURE
 			| PROCEDURES
 			| PROGRAM
+			| PROPERTIES
+			| PROPERTY
 			| PUBLICATION
 			| QUOTE
 			| QUOTES
@@ -17718,6 +18394,7 @@ unreserved_keyword:
 			| REFERENCING
 			| REFRESH
 			| REINDEX
+			| RELATIONSHIP
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
@@ -17807,6 +18484,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERTEX
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -17845,6 +18523,7 @@ col_name_keyword:
 			| EXISTS
 			| EXTRACT
 			| FLOAT_P
+			| GRAPH_TABLE
 			| GREATEST
 			| GROUPING
 			| INOUT
@@ -18136,6 +18815,7 @@ bare_label_keyword:
 			| DEPENDS
 			| DEPTH
 			| DESC
+			| DESTINATION
 			| DETACH
 			| DICTIONARY
 			| DISABLE_P
@@ -18147,6 +18827,7 @@ bare_label_keyword:
 			| DOUBLE_P
 			| DROP
 			| EACH
+			| EDGE
 			| ELSE
 			| EMPTY_P
 			| ENABLE_P
@@ -18184,6 +18865,8 @@ bare_label_keyword:
 			| GENERATED
 			| GLOBAL
 			| GRANTED
+			| GRAPH
+			| GRAPH_TABLE
 			| GREATEST
 			| GROUPING
 			| GROUPS
@@ -18279,6 +18962,7 @@ bare_label_keyword:
 			| NFKC
 			| NFKD
 			| NO
+			| NODE
 			| NONE
 			| NORMALIZE
 			| NORMALIZED
@@ -18333,6 +19017,8 @@ bare_label_keyword:
 			| PROCEDURE
 			| PROCEDURES
 			| PROGRAM
+			| PROPERTIES
+			| PROPERTY
 			| PUBLICATION
 			| QUOTE
 			| QUOTES
@@ -18346,6 +19032,7 @@ bare_label_keyword:
 			| REFERENCING
 			| REFRESH
 			| REINDEX
+			| RELATIONSHIP
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
@@ -18460,6 +19147,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERTEX
 			| VIEW
 			| VIEWS
 			| VOLATILE
diff --git a/src/backend/parser/meson.build b/src/backend/parser/meson.build
index 573d70b3d1..cbcfa07b40 100644
--- a/src/backend/parser/meson.build
+++ b/src/backend/parser/meson.build
@@ -10,6 +10,7 @@ backend_sources += files(
   'parse_enr.c',
   'parse_expr.c',
   'parse_func.c',
+  'parse_graphtable.c',
   'parse_jsontable.c',
   'parse_merge.c',
   'parse_node.c',
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index bee7d8346a..bf3ed60449 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -577,6 +577,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
+		case EXPR_KIND_PROPGRAPH_PROPERTY:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in property definition expressions");
+			else
+				err = _("grouping operations are not allowed in property definition expressions");
+
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -967,6 +975,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_PROPGRAPH_PROPERTY:
+			err = _("window functions are not allowed in property definition expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 8118036495..63f5dd5556 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -35,6 +35,7 @@
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_func.h"
+#include "parser/parse_graphtable.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
@@ -67,6 +68,8 @@ static ParseNamespaceItem *transformRangeFunction(ParseState *pstate,
 												  RangeFunction *r);
 static ParseNamespaceItem *transformRangeTableFunc(ParseState *pstate,
 												   RangeTableFunc *rtf);
+static ParseNamespaceItem *transformRangeGraphTable(ParseState *pstate,
+													RangeGraphTable *rgt);
 static TableSampleClause *transformRangeTableSample(ParseState *pstate,
 													RangeTableSample *rts);
 static ParseNamespaceItem *getNSItemForSpecialRelationTypes(ParseState *pstate,
@@ -900,6 +903,80 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
 										  tf, rtf->alias, is_lateral, true);
 }
 
+/*
+ * transformRangeGraphTable -- transform a GRAPH_TABLE clause
+ */
+static ParseNamespaceItem *
+transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
+{
+	Relation	rel;
+	Oid			graphid;
+	GraphTableParseState *gpstate = palloc0_object(GraphTableParseState);
+	Node	   *gp;
+	List	   *columns = NIL;
+	List	   *colnames = NIL;
+	ListCell   *lc;
+	int			resno = 0;
+
+	rel = parserOpenTable(pstate, rgt->graph_name, AccessShareLock);
+	if (rel->rd_rel->relkind != RELKIND_PROPGRAPH)
+		ereport(ERROR,
+				errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				errmsg("\"%s\" is not a property graph",
+					   RelationGetRelationName(rel)),
+				parser_errposition(pstate, rgt->graph_name->location));
+
+	graphid = RelationGetRelid(rel);
+
+	gpstate->graphid = graphid;
+
+	pstate->p_post_columnref_hook = graph_table_property_reference;
+	pstate->p_ref_hook_state = gpstate;
+	Assert(!pstate->p_lateral_active);
+	pstate->p_lateral_active = true;
+
+	gp = transformGraphPattern(pstate, gpstate, rgt->graph_pattern);
+
+	foreach(lc, rgt->columns)
+	{
+		ResTarget  *rt = lfirst_node(ResTarget, lc);
+		Node	   *colexpr;
+		TargetEntry *te;
+		char	   *colname;
+
+		colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET);
+
+		if (rt->name)
+			colname = rt->name;
+		else
+		{
+			if (IsA(colexpr, GraphPropertyRef))
+				colname = get_propgraph_property_name(castNode(GraphPropertyRef, colexpr)->propid);
+			else
+			{
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("complex graph table column must specify an explicit column name"),
+						parser_errposition(pstate, rt->location));
+				colname = NULL;
+			}
+		}
+
+		colnames = lappend(colnames, makeString(colname));
+
+		te = makeTargetEntry((Expr *) colexpr, ++resno, colname, false);
+		columns = lappend(columns, te);
+	}
+
+	table_close(rel, NoLock);
+
+	pstate->p_pre_columnref_hook = NULL;
+	pstate->p_ref_hook_state = NULL;
+	pstate->p_lateral_active = false;
+
+	return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true);
+}
+
 /*
  * transformRangeTableSample --- transform a TABLESAMPLE clause
  *
@@ -1123,6 +1200,18 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rtr->rtindex = nsitem->p_rtindex;
 		return (Node *) rtr;
 	}
+	else if (IsA(n, RangeGraphTable))
+	{
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+
+		nsitem = transformRangeGraphTable(pstate, (RangeGraphTable *) n);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, RangeTableSample))
 	{
 		/* TABLESAMPLE clause (wrapping some other valid FROM node) */
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 44529bb49e..33a1f3f241 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -571,6 +571,13 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 			location = exprLocation(node);
 			break;
 
+		case T_GraphPropertyRef:
+			/* FIXME */
+			collation = DEFAULT_COLLATION_OID;
+			strength = COLLATE_IMPLICIT;
+			location = -1;
+			break;
+
 		default:
 			{
 				/*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 56e413da9f..38222e745e 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -577,6 +577,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_PROPGRAPH_PROPERTY:
 			/* okay */
 			break;
 
@@ -1860,6 +1861,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_GENERATED_COLUMN:
 			err = _("cannot use subquery in column generation expression");
 			break;
+		case EXPR_KIND_PROPGRAPH_PROPERTY:
+			err = _("cannot use subquery in property definition expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3199,6 +3203,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_PROPGRAPH_PROPERTY:
+			return "property definition expression";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9b23344a3b..6409ddf181 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2658,6 +2658,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_PROPGRAPH_PROPERTY:
+			err = _("set-returning functions are not allowed in property definition expressions");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
new file mode 100644
index 0000000000..1a97168702
--- /dev/null
+++ b/src/backend/parser/parse_graphtable.c
@@ -0,0 +1,209 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_graphtable.c
+ *	  parsing of GRAPH_TABLE
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/parser/parse_graphtable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_property.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_graphtable.h"
+#include "parser/parse_node.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/relcache.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Resolve a property reference.
+ */
+Node *
+graph_table_property_reference(ParseState *pstate, ColumnRef *cref, Node *var)
+{
+	GraphTableParseState *gpstate = pstate->p_ref_hook_state;
+
+	if (list_length(cref->fields) == 2)
+	{
+		Node	   *field1 = linitial(cref->fields);
+		Node	   *field2 = lsecond(cref->fields);
+		char	   *elvarname;
+		char	   *propname;
+
+		elvarname = strVal(field1);
+		propname = strVal(field2);
+
+		if (list_member(gpstate->variables, field1))
+		{
+			GraphPropertyRef *gpr = makeNode(GraphPropertyRef);
+			Oid			propid;
+
+			propid = GetSysCacheOid2(PROPGRAPHPROPNAME, Anum_pg_propgraph_property_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
+			if (!propid)
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("property \"%s\" does not exist", propname));
+
+			gpr->location = cref->location;
+			gpr->elvarname = elvarname;
+			gpr->propid = propid;
+			gpr->typeId = GetSysCacheOid1(PROPGRAPHPROPOID, Anum_pg_propgraph_property_pgptypid, ObjectIdGetDatum(propid));
+
+			return (Node *) gpr;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * Transform a label expression.
+ */
+static Node *
+transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
+{
+	Node	   *result;
+
+	if (labelexpr == NULL)
+		return NULL;
+
+	check_stack_depth();
+
+	switch (nodeTag(labelexpr))
+	{
+		case T_ColumnRef:
+			{
+				ColumnRef  *cref = (ColumnRef *) labelexpr;
+				const char *labelname;
+				Oid			labelid;
+				GraphLabelRef *lref;
+
+				Assert(list_length(cref->fields) == 1);
+				labelname = strVal(linitial(cref->fields));
+
+				labelid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(labelname));
+				if (!labelid)
+					ereport(ERROR,
+							errcode(ERRCODE_UNDEFINED_OBJECT),
+							errmsg("label \"%s\" does not exist in property graph \"%s\"", labelname, get_rel_name(gpstate->graphid)));
+
+				lref = makeNode(GraphLabelRef);
+				lref->labelid = labelid;
+				lref->location = cref->location;
+
+				result = (Node *) lref;
+				break;
+			}
+
+		case T_BoolExpr:
+			{
+				BoolExpr   *be = (BoolExpr *) labelexpr;
+				ListCell   *lc;
+				List	   *args = NIL;
+
+				foreach(lc, be->args)
+				{
+					Node	   *arg = (Node *) lfirst(lc);
+
+					arg = transformLabelExpr(gpstate, arg);
+					args = lappend(args, arg);
+				}
+
+				result = (Node *) makeBoolExpr(be->boolop, args, be->location);
+				break;
+			}
+
+		default:
+			/* should not reach here */
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(labelexpr));
+			result = NULL;		/* keep compiler quiet */
+			break;
+	}
+
+	return result;
+}
+
+/*
+ * Transform a GraphElementPattern.
+ */
+static Node *
+transformGraphElementPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphElementPattern *gep)
+{
+	if (gep->variable)
+		gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable)));
+
+	gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
+
+	gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
+	assign_expr_collations(pstate, gep->whereClause);
+
+	return (Node *) gep;
+}
+
+/*
+ * Transform a path term (list of GraphElementPattern's).
+ */
+static Node *
+transformPathTerm(ParseState *pstate, GraphTableParseState *gpstate, List *path_term)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach(lc, path_term)
+	{
+		Node	   *n = transformGraphElementPattern(pstate, gpstate, lfirst_node(GraphElementPattern, lc));
+
+		result = lappend(result, n);
+	}
+
+	return (Node *) result;
+}
+
+/*
+ * Transform a path pattern list (list of path terms).
+ */
+static Node *
+transformPathPatternList(ParseState *pstate, GraphTableParseState *gpstate, List *path_pattern)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach(lc, path_pattern)
+	{
+		Node	   *n = transformPathTerm(pstate, gpstate, lfirst(lc));
+
+		result = lappend(result, n);
+	}
+
+	return (Node *) result;
+}
+
+/*
+ * Transform a GraphPattern.
+ */
+Node *
+transformGraphPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphPattern *graph_pattern)
+{
+	graph_pattern->path_pattern_list = (List *) transformPathPatternList(pstate, gpstate, graph_pattern->path_pattern_list);
+	graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_WHERE);
+	assign_expr_collations(pstate, graph_pattern->whereClause);
+
+	return (Node *) graph_pattern;
+}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 2f64eaf0e3..5ea9fa993b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2124,6 +2124,98 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 								rte->colcollations);
 }
 
+ParseNamespaceItem *
+addRangeTableEntryForGraphTable(ParseState *pstate,
+								Oid graphid,
+								GraphPattern *graph_pattern,
+								List *columns,
+								List *colnames,
+								Alias *alias,
+								bool lateral,
+								bool inFromCl)
+{
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	char	   *refname = alias ? alias->aliasname : pstrdup("graph_table");
+	Alias	   *eref;
+	int			numaliases;
+	int			varattno;
+	ListCell   *lc;
+	List	   *coltypes = NIL;
+	List	   *coltypmods = NIL;
+	List	   *colcollations = NIL;
+	RTEPermissionInfo *perminfo;
+	ParseNamespaceItem *nsitem;
+
+	Assert(pstate != NULL);
+
+	rte->rtekind = RTE_GRAPH_TABLE;
+	rte->relid = graphid;
+	rte->relkind = RELKIND_PROPGRAPH;
+	rte->graph_pattern = graph_pattern;
+	rte->graph_table_columns = columns;
+	rte->alias = alias;
+
+	eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
+
+	if (!eref->colnames)
+		eref->colnames = colnames;
+
+	numaliases = list_length(eref->colnames);
+
+	/* fill in any unspecified alias columns */
+	varattno = 0;
+	foreach(lc, colnames)
+	{
+		varattno++;
+		if (varattno > numaliases)
+			eref->colnames = lappend(eref->colnames, lfirst(lc));
+	}
+	if (varattno < numaliases)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+				 errmsg("GRAPH_TABLE \"%s\" has %d columns available but %d columns specified",
+						refname, varattno, numaliases)));
+
+	rte->eref = eref;
+
+	foreach(lc, columns)
+	{
+		TargetEntry *te = lfirst_node(TargetEntry, lc);
+		Node	   *colexpr = (Node *) te->expr;
+
+		coltypes = lappend_oid(coltypes, exprType(colexpr));
+		coltypmods = lappend_int(coltypmods, exprTypmod(colexpr));
+		colcollations = lappend_oid(colcollations, exprCollation(colexpr));
+	}
+
+	/*
+	 * Set flags and access permissions.
+	 */
+	rte->lateral = lateral;
+	rte->inFromCl = inFromCl;
+
+	perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte);
+	perminfo->requiredPerms = ACL_SELECT;
+
+	/*
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
+	 */
+	pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	nsitem = buildNSItemFromLists(rte, list_length(pstate->p_rtable),
+								  coltypes, coltypmods, colcollations);
+
+	nsitem->p_perminfo = perminfo;
+
+	return nsitem;
+}
+
 /*
  * Add an entry for a VALUES list to the pstate's range table (p_rtable).
  * Then, construct and return a ParseNamespaceItem for the new RTE.
@@ -2940,6 +3032,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 		case RTE_VALUES:
 		case RTE_CTE:
 		case RTE_NAMEDTUPLESTORE:
+		case RTE_GRAPH_TABLE:
 			{
 				/* Tablefunc, Values, CTE, or ENR RTE */
 				ListCell   *aliasp_item = list_head(rte->eref->colnames);
@@ -3317,6 +3410,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 		case RTE_TABLEFUNC:
 		case RTE_VALUES:
 		case RTE_CTE:
+		case RTE_GRAPH_TABLE:
 
 			/*
 			 * Subselect, Table Functions, Values, CTE RTEs never have dropped
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index ee6fcd0503..a47db5ada1 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -360,6 +360,10 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 			tle->resorigtbl = rte->relid;
 			tle->resorigcol = attnum;
 			break;
+		case RTE_GRAPH_TABLE:
+			tle->resorigtbl = rte->relid;
+			tle->resorigcol = InvalidAttrNumber;
+			break;
 		case RTE_SUBQUERY:
 			/* Subselect-in-FROM: copy up from the subselect */
 			if (attnum != InvalidAttrNumber)
@@ -1578,6 +1582,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 		case RTE_RELATION:
 		case RTE_VALUES:
 		case RTE_NAMEDTUPLESTORE:
+		case RTE_GRAPH_TABLE:
 		case RTE_RESULT:
 
 			/*
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index f74059e7b0..4274327677 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -366,6 +366,8 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+/* Note there is no need for left_arrow, since "<-" is not a single operator. */
+right_arrow		"->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -377,7 +379,7 @@ not_equals		"!="
  * If you change either set, adjust the character lists appearing in the
  * rule for "operator"!
  */
-self			[,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self			[,()\[\].;\:\|\+\-\*\/\%\^\<\>\=]
 op_chars		[\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
 operator		{op_chars}+
 
@@ -878,6 +880,11 @@ other			.
 					return NOT_EQUALS;
 				}
 
+{right_arrow}	{
+					SET_YYLLOC();
+					return RIGHT_ARROW;
+				}
+
 {self}			{
 					SET_YYLLOC();
 					return yytext[0];
@@ -955,7 +962,7 @@ other			.
 						 * that the "self" rule would have.
 						 */
 						if (nchars == 1 &&
-							strchr(",()[].;:+-*/%^<>=", yytext[0]))
+							strchr(",()[].;:|+-*/%^<>=", yytext[0]))
 							return yytext[0];
 						/*
 						 * Likewise, if what we have left is two chars, and
@@ -975,6 +982,8 @@ other			.
 								return NOT_EQUALS;
 							if (yytext[0] == '!' && yytext[1] == '=')
 								return NOT_EQUALS;
+							if (yytext[0] == '-' && yytext[1] == '>')
+								return RIGHT_ARROW;
 						}
 					}
 
diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile
index 4680752e6a..09070047b7 100644
--- a/src/backend/rewrite/Makefile
+++ b/src/backend/rewrite/Makefile
@@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = \
 	rewriteDefine.o \
+	rewriteGraphTable.o \
 	rewriteHandler.o \
 	rewriteManip.o \
 	rewriteRemove.o \
diff --git a/src/backend/rewrite/meson.build b/src/backend/rewrite/meson.build
index 23043ca6e5..2bea20233a 100644
--- a/src/backend/rewrite/meson.build
+++ b/src/backend/rewrite/meson.build
@@ -2,6 +2,7 @@
 
 backend_sources += files(
   'rewriteDefine.c',
+  'rewriteGraphTable.c',
   'rewriteHandler.c',
   'rewriteManip.c',
   'rewriteRemove.c',
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
new file mode 100644
index 0000000000..ee11594d2c
--- /dev/null
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -0,0 +1,1098 @@
+/*-------------------------------------------------------------------------
+ *
+ * rewriteGraphTable.c
+ *		Support for rewriting GRAPH_TABLE clauses.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/rewrite/rewriteGraphTable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/table.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "parser/parsetree.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_graphtable.h"
+#include "rewrite/rewriteGraphTable.h"
+#include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Represents one property graph element (vertex or edge) in the path.
+ *
+ * Label expression in an element pattern resolves into a set of elements. For
+ * each of those elements we create one graph_path_element object.
+ */
+struct graph_path_element
+{
+	Oid			elemoid;
+	Oid			reloid;
+	int			rtindex;
+	List	   *labeloids;
+	Oid			srcvertexid;
+	int			srcelem_pos;
+	Oid			destvertexid;
+	int			destelem_pos;
+	List	   *qual_exprs;
+	GraphElementPattern *parent_gep;
+};
+
+static Node *replace_property_refs(Oid propgraphid, Node *node, const List *mappings);
+static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum);
+static List *generate_queries_for_path_pattern(RangeTblEntry *rte, List *element_patterns);
+static Query *generate_query_for_graph_path(RangeTblEntry *rte, List *path);
+static Node *generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist);
+static List *generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_pattern_lists, int elempos);
+static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte);
+static Query *generate_union_from_pathqueries(List **pathqueries);
+static const char *get_gep_kind_name(GraphElementPatternKind gepkind);
+static List *get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos);
+static bool is_property_associated_with_label(Oid labeloid, Oid propoid);
+static const char *get_graph_elem_kind_name(GraphElementPatternKind gepkind);
+static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex);
+
+/*
+ * Convert GRAPH_TABLE clause into a subquery using relational
+ * operators.
+ */
+Query *
+rewriteGraphTable(Query *parsetree, int rt_index)
+{
+	RangeTblEntry *rte;
+	Query	   *graph_table_query;
+	List	   *path_pattern;
+	List	   *pathqueries = NIL;
+
+	rte = rt_fetch(rt_index, parsetree->rtable);
+
+	if (list_length(rte->graph_pattern->path_pattern_list) != 1)
+		elog(ERROR, "unsupported path pattern list length");
+
+	path_pattern = linitial(rte->graph_pattern->path_pattern_list);
+	pathqueries = generate_queries_for_path_pattern(rte, path_pattern);
+	graph_table_query = generate_union_from_pathqueries(&pathqueries);
+
+	AcquireRewriteLocks(graph_table_query, true, false);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = graph_table_query;
+	rte->lateral = true;
+
+	/*
+	 * Reset no longer applicable fields, to appease
+	 * WRITE_READ_PARSE_PLAN_TREES.
+	 */
+	rte->graph_pattern = NULL;
+	rte->graph_table_columns = NIL;
+
+#if 0
+	elog(INFO, "rewritten:\n%s", pg_get_querydef(copyObject(parsetree), false));
+#endif
+
+	return parsetree;
+}
+
+/*
+ * Generate queries represeting the given path pattern applied to the given
+ * property graph.
+ *
+ * A path pattern consists of one or more element patterns. Each of the element
+ * patterns may be satisfied by multiple elements. A path satisfying the given
+ * path pattern consists of one element from each element pattern. Each of these
+ * paths is converted into a query connecting all the elements in that path.
+ * There can be as many paths as the number of combinations of the elements.
+ * Compute all these paths and convert each of them into a query. Set of these
+ * queries is returned.
+ *
+ * Assuming that the numbering starts at 0, every element pattern at an even
+ * numbered position in the path is a vertex pattern. Every element in even
+ * numbered position is an edge pattern. Thus every even numbered element is a
+ * vertex table and odd numbered element is an edge table. An edge connects two
+ * vertices identified by the source and destination keys respectively. The
+ * connections between vertex rows from different vertex tables can be computed
+ * by applying relational join between edge table and the adjacent vertex tables
+ * respectively. Hence a query representing a path consists of JOIN between
+ * adjacent vertex and edge tables.
+ *
+ * A path pattern in itself is a K-partite graph where K = number of element
+ * patterns in the path pattern. The possible paths are computed by performing a
+ * DFS in this graph. The DFS is implemented as recursion. A path is converted
+ * into the corresponding query as soon as the last vertex table is reached.
+ *
+ * generate_queries_for_path_pattern() starts the recursion but actual work is
+ * done by generate_queries_for_path_pattern_recurse().
+ * generate_query_for_graph_path() constructs a query for a given path.
+ *
+ * A path pattern may result into no path if any of the element pattern yields
+ * no elements or edge patterns yield no edges connecting adjacent vertex
+ * patterns. In such a case a query which returns no result is returned
+ * (generate_query_for_empty_path_pattern()).
+ *
+ * 'path_pattern' is given path pattern
+ * 'rte' references the property graph in the GRAPH_TABLE clause
+ */
+static List *
+generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
+{
+	List	   *pathqueries = NIL;
+	List	   *path_elem_lists = NIL;
+	ListCell   *lc;
+	int			elempos = 0;
+
+	Assert(list_length(path_pattern) > 0);
+
+	/*
+	 * For every element pattern in the given path pattern collect all the
+	 * graph elements that satisfy the element pattern.
+	 */
+	foreach(lc, path_pattern)
+	{
+		GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc);
+
+		if (gep->kind != VERTEX_PATTERN &&
+			gep->kind != EDGE_PATTERN_LEFT && gep->kind != EDGE_PATTERN_RIGHT)
+			elog(ERROR, "unsupported element pattern kind: %s", get_gep_kind_name(gep->kind));
+
+		if (gep->quantifier)
+			elog(ERROR, "element pattern quantifier not supported yet");
+
+		path_elem_lists = lappend(path_elem_lists,
+								  get_elements_for_gep(rte->relid, gep, elempos++));
+	}
+
+	pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
+															NIL, path_elem_lists, 0);
+
+	if (!pathqueries)
+		pathqueries = list_make1(generate_query_for_empty_path_pattern(rte));
+
+	return pathqueries;
+}
+
+/*
+ * Recursive workhorse function of generate_queries_for_path_pattern().
+ */
+static List *
+generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_elem_lists, int elempos)
+{
+	List	   *gep_elems = list_nth_node(List, path_elem_lists, elempos);
+	ListCell   *lc;
+
+	foreach(lc, gep_elems)
+	{
+		struct graph_path_element *elem = lfirst(lc);
+
+		/* Update current path being built with current element. */
+		cur_path = lappend(cur_path, elem);
+
+		/*
+		 * If this is the last element in the path, generate query for the
+		 * completed path. Else recurse processing the next element.
+		 */
+		if (list_length(path_elem_lists) == list_length(cur_path))
+		{
+			Query	   *pathquery = generate_query_for_graph_path(rte, cur_path);
+
+			Assert(elempos == list_length(path_elem_lists) - 1);
+			if (pathquery)
+				pathqueries = lappend(pathqueries, pathquery);
+		}
+		else
+			pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
+																	cur_path,
+																	path_elem_lists,
+																	elempos + 1);
+		/* Make way for the next element at the same position. */
+		cur_path = list_delete_last(cur_path);
+	}
+
+	return pathqueries;
+}
+
+/*
+ * Construct a query representing given graph path.
+ *
+ * More details in the prologue of generate_queries_for_path_pattern().
+ */
+static Query *
+generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
+{
+	ListCell   *lc;
+	Query	   *path_query = makeNode(Query);
+	List	   *fromlist = NIL;
+	List	   *qual_exprs = NIL;
+
+	path_query->commandType = CMD_SELECT;
+
+	foreach(lc, graph_path)
+	{
+		struct graph_path_element *gpe = (struct graph_path_element *) lfirst(lc);
+		GraphElementPattern *gep = gpe->parent_gep;
+		RangeTblRef *rtr;
+		Relation	rel;
+		ParseNamespaceItem *pni;
+
+		Assert(gep->kind == VERTEX_PATTERN ||
+			   gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT);
+		Assert(!gep->quantifier);
+
+		if (gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT)
+		{
+			struct graph_path_element *src_gpe = list_nth(graph_path, gpe->srcelem_pos);
+			struct graph_path_element *dest_gpe = list_nth(graph_path, gpe->destelem_pos);
+
+			/*
+			 * Make sure that the source and destination elements of this edge
+			 * are placed at the expected position and have the corret
+			 * RangeTblEntry indexes as setup by create_gpe_for_element().
+			 */
+			Assert(gpe->srcelem_pos == src_gpe->rtindex - 1 &&
+				   gpe->destelem_pos == dest_gpe->rtindex - 1);
+
+			/*
+			 * If the given edge element does not connect the adjacent vertex
+			 * elements in this path, the path is broken. Abandon this path as
+			 * it won't return any rows.
+			 */
+			if (src_gpe->elemoid != gpe->srcvertexid ||
+				dest_gpe->elemoid != gpe->destvertexid)
+				return NULL;
+		}
+
+		/* Create RangeTblEntry for this element table. */
+		rel = table_open(gpe->reloid, AccessShareLock);
+		pni = addRangeTableEntryForRelation(make_parsestate(NULL), rel, AccessShareLock,
+											NULL, true, false);
+		table_close(rel, NoLock);
+		path_query->rtable = lappend(path_query->rtable, pni->p_rte);
+		path_query->rteperminfos = lappend(path_query->rteperminfos, pni->p_perminfo);
+		pni->p_rte->perminfoindex = list_length(path_query->rteperminfos);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = list_length(path_query->rtable);
+		fromlist = lappend(fromlist, rtr);
+
+		/*
+		 * Make sure that the assumption mentioned in create_gpe_for_element()
+		 * holds true. That the elements' RangeTblEntrys are added in the
+		 * order in which they appear in the path.
+		 */
+		Assert(gpe->rtindex == rtr->rtindex);
+
+		if (gep->whereClause)
+		{
+			Node	   *tr;
+
+			tr = replace_property_refs(rte->relid, gep->whereClause, list_make1(gpe));
+
+			qual_exprs = lappend(qual_exprs, tr);
+		}
+		qual_exprs = list_concat(qual_exprs, gpe->qual_exprs);
+	}
+
+	path_query->jointree = makeFromExpr(fromlist,
+										(Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1));
+
+	/* Each path query projects the columns specified in the GRAH_TABLE clause */
+	path_query->targetList = castNode(List,
+									  replace_property_refs(rte->relid,
+															(Node *) rte->graph_table_columns,
+															graph_path));
+	return path_query;
+}
+
+/*
+ * Construct a query which would not return any rows.
+ *
+ * More details in the prologue of generate_queries_for_path_pattern().
+ */
+static Query *
+generate_query_for_empty_path_pattern(RangeTblEntry *rte)
+{
+	Query	   *query = makeNode(Query);
+	ListCell   *lc;
+
+	query->commandType = CMD_SELECT;
+
+
+	query->rtable = NIL;
+	query->rteperminfos = NIL;
+
+
+	query->jointree = makeFromExpr(NIL, (Node *) makeBoolConst(false, false));
+
+	/*
+	 * Even though no rows are returned, the result still projects the same
+	 * columns as projected by GRAPH_TABLE clause. Do this by constructing a
+	 * target list full of NULL values.
+	 */
+	foreach(lc, rte->graph_table_columns)
+	{
+		TargetEntry *te = copyObject(lfirst_node(TargetEntry, lc));
+		Node	   *nte = (Node *) te->expr;
+
+		te->expr = (Expr *) makeNullConst(exprType(nte), exprTypmod(nte), exprCollation(nte));
+		query->targetList = lappend(query->targetList, te);
+	}
+
+	return query;
+}
+
+/*
+ * Construct a query which is UNION of given path queries.
+ *
+ * The function destroys given pathqueries list while constructing
+ * SetOperationStmt recrursively. Hence the function always returns with
+ * `pathqueries` set to NIL.
+ */
+static Query *
+generate_union_from_pathqueries(List **pathqueries)
+{
+	List	   *rtable = NIL;
+	Query	   *sampleQuery = linitial_node(Query, *pathqueries);
+	SetOperationStmt *sostmt;
+	Query	   *union_query;
+	int			resno;
+	ListCell   *lctl,
+			   *lct,
+			   *lcm,
+			   *lcc;
+
+	Assert(list_length(*pathqueries) > 0);
+
+	/* If there's only one pathquery, no need to construct a UNION query. */
+	if (list_length(*pathqueries) == 1)
+	{
+		*pathqueries = NIL;
+		return sampleQuery;
+	}
+
+	sostmt = castNode(SetOperationStmt,
+					  generate_setop_from_pathqueries(*pathqueries, &rtable, NULL));
+
+	/* Encapsulate the set operation statement into a Query. */
+	union_query = makeNode(Query);
+	union_query->commandType = CMD_SELECT;
+	union_query->rtable = rtable;
+	union_query->setOperations = (Node *) sostmt;
+	union_query->rteperminfos = NIL;
+	union_query->jointree = makeFromExpr(NIL, NULL);
+
+	/*
+	 * Generate dummy targetlist for outer query using column names from one
+	 * of the queries and common datatypes/collations of topmost set
+	 * operation.  It shouldn't matter which query. Also it shouldn't matter
+	 * which RT index is used as varno in the target list entries, as long as
+	 * it corresponds to a real RT entry; else funny things may happen when
+	 * the tree is mashed by rule rewriting. So we use 1 since there's always
+	 * one RT entry at least.
+	 */
+	Assert(rt_fetch(1, rtable));
+	union_query->targetList = NULL;
+	resno = 1;
+	forfour(lct, sostmt->colTypes,
+			lcm, sostmt->colTypmods,
+			lcc, sostmt->colCollations,
+			lctl, sampleQuery->targetList)
+	{
+		Oid			colType = lfirst_oid(lct);
+		int32		colTypmod = lfirst_int(lcm);
+		Oid			colCollation = lfirst_oid(lcc);
+		TargetEntry *sample_tle = (TargetEntry *) lfirst(lctl);
+		char	   *colName;
+		TargetEntry *tle;
+		Var		   *var;
+
+		Assert(!sample_tle->resjunk);
+		colName = pstrdup(sample_tle->resname);
+		var = makeVar(1, sample_tle->resno, colType, colTypmod, colCollation, 0);
+		var->location = exprLocation((Node *) sample_tle->expr);
+		tle = makeTargetEntry((Expr *) var, (AttrNumber) resno++, colName, false);
+		union_query->targetList = lappend(union_query->targetList, tle);
+	}
+
+	*pathqueries = NIL;
+	return union_query;
+}
+
+/*
+ * Construct a query which is UNION of all the given path queries.
+ *
+ * The function destroys given pathqueries list while constructing
+ * SetOperationStmt recursively.
+ */
+static Node *
+generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist)
+{
+	SetOperationStmt *sostmt;
+	Query	   *lquery;
+	Node	   *rarg;
+	RangeTblRef *lrtr = makeNode(RangeTblRef);
+	List	   *rtargetlist;
+	ParseNamespaceItem *pni;
+
+	/* Recursion termination condition. */
+	if (list_length(pathqueries) == 0)
+	{
+		*targetlist = NIL;
+		return NULL;
+	}
+
+	lquery = linitial_node(Query, pathqueries);
+
+	pni = addRangeTableEntryForSubquery(make_parsestate(NULL), lquery, NULL,
+										false, false);
+	*rtable = lappend(*rtable, pni->p_rte);
+	lrtr->rtindex = list_length(*rtable);
+	rarg = generate_setop_from_pathqueries(list_delete_first(pathqueries), rtable, &rtargetlist);
+	if (rarg == NULL)
+	{
+		/*
+		 * No further path queries in the list. Convert the last query into an
+		 * RangeTblRef as expected by SetOperationStmt. Extract a list of the
+		 * non-junk TLEs for upper-level processing.
+		 */
+		if (targetlist)
+		{
+			ListCell   *tl;
+
+			*targetlist = NIL;
+			foreach(tl, lquery->targetList)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(tl);
+
+				if (!tle->resjunk)
+					*targetlist = lappend(*targetlist, tle);
+			}
+		}
+		return (Node *) lrtr;
+	}
+
+	sostmt = makeNode(SetOperationStmt);
+	sostmt->op = SETOP_UNION;
+	sostmt->all = true;
+	sostmt->larg = (Node *) lrtr;
+	sostmt->rarg = rarg;
+	constructSetOpTargetlist(sostmt, lquery->targetList, rtargetlist, targetlist, "UNION", NULL, false);
+
+	return (Node *) sostmt;
+}
+
+/*
+ * Construct a graph_path_element object for the graph element given by `elemoid`
+ * statisfied by the graph element pattern `gep`.
+ *
+ * 'elempos` is the position of given element pattern in the path pattern.
+ *
+ * If the type of graph element does not fit the element pattern kind, the
+ * function returns NULL.
+ */
+static struct graph_path_element *
+create_gpe_for_element(GraphElementPattern *gep, Oid elemoid, int elempos)
+{
+	HeapTuple	eletup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elemoid));
+	Form_pg_propgraph_element pgeform;
+	struct graph_path_element *gpe;
+
+	if (!eletup)
+		elog(ERROR, "cache lookup failed for property graph element %u", elemoid);
+	pgeform = ((Form_pg_propgraph_element) GETSTRUCT(eletup));
+
+	if ((pgeform->pgekind == PGEKIND_VERTEX && gep->kind != VERTEX_PATTERN) ||
+		(pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(gep->kind)))
+	{
+		ReleaseSysCache(eletup);
+		return NULL;
+	}
+
+	gpe = palloc0_object(struct graph_path_element);
+	gpe->parent_gep = gep;
+	gpe->elemoid = elemoid;
+	gpe->reloid = pgeform->pgerelid;
+	gpe->qual_exprs = NIL;
+
+	/*
+	 * When the path containing this element will be converted into a query
+	 * (generate_query_for_graph_path()) this element will be converted into a
+	 * RangeTblEntry. The RangeTblEntrys are created in the same order in
+	 * which elements appear in the path and thus get consecutive rtindexes.
+	 * Knowing those rtindexes here makes it possible to craft elements' qual
+	 * expressions only once. Otherwise they need to be crafted as many times
+	 * as the number of paths this element appears in. Hence save the assumed
+	 * rtindex so that it can be verified later.
+	 */
+	gpe->rtindex = elempos + 1;
+
+	if (IS_EDGE_PATTERN(gep->kind))
+	{
+		int			src_rtindex;
+		int			dest_rtindex;
+		List	   *edge_qual;
+
+		gpe->srcvertexid = pgeform->pgesrcvertexid;
+		gpe->destvertexid = pgeform->pgedestvertexid;
+
+		if (gep->kind == EDGE_PATTERN_RIGHT)
+		{
+			gpe->srcelem_pos = elempos - 1;
+			gpe->destelem_pos = elempos + 1;
+			src_rtindex = gpe->rtindex - 1;
+			dest_rtindex = gpe->rtindex + 1;
+		}
+		else if (gep->kind == EDGE_PATTERN_LEFT)
+		{
+			gpe->srcelem_pos = elempos + 1;
+			gpe->destelem_pos = elempos - 1;
+			src_rtindex = gpe->rtindex + 1;
+			dest_rtindex = gpe->rtindex - 1;
+		}
+		else
+		{
+			/* We don't support undirected edges yet. */
+			Assert(false);
+			gpe->srcelem_pos = elempos;
+			gpe->destelem_pos = elempos;
+			src_rtindex = gpe->rtindex;
+			dest_rtindex = gpe->rtindex;
+		}
+
+		edge_qual = build_edge_vertex_link_quals(eletup, gpe->rtindex, src_rtindex,
+												 Anum_pg_propgraph_element_pgesrckey,
+												 Anum_pg_propgraph_element_pgesrcref);
+		gpe->qual_exprs = list_concat(gpe->qual_exprs, edge_qual);
+		edge_qual = build_edge_vertex_link_quals(eletup, gpe->rtindex, dest_rtindex,
+												 Anum_pg_propgraph_element_pgedestkey,
+												 Anum_pg_propgraph_element_pgedestref);
+		gpe->qual_exprs = list_concat(gpe->qual_exprs, edge_qual);
+	}
+
+	ReleaseSysCache(eletup);
+
+	return gpe;
+}
+
+static const char *
+get_gep_kind_name(GraphElementPatternKind gepkind)
+{
+	switch (gepkind)
+	{
+		case VERTEX_PATTERN:
+			return "vertex";
+		case EDGE_PATTERN_LEFT:
+			return "edge pointing left";
+		case EDGE_PATTERN_RIGHT:
+			return "edge pointing right";
+		case EDGE_PATTERN_ANY:
+			return "undirected edge";
+		case PAREN_EXPR:
+			return "nested path pattern";
+	}
+
+	pg_unreachable();
+}
+
+/*
+ * Returns the list of OIDs of graph labels which the given label expression
+ * resolves to in the given property graph.
+ */
+static List *
+get_labels_for_expr(Oid propgraphid, Node *labelexpr)
+{
+	List	   *label_oids;
+
+	if (!labelexpr)
+	{
+		Relation	rel;
+		SysScanDesc scan;
+		ScanKeyData key[1];
+		HeapTuple	tup;
+
+		/*
+		 * According to section 9.2 "Contextual inference of a set of labels"
+		 * subclause 2.a.ii of SQL/PGQ standard, element pattern which does
+		 * not have a label expression is considered to have label expression
+		 * equivalent to '%|!%' which is set of all labels.
+		 */
+		label_oids = NIL;
+		rel = table_open(PropgraphLabelRelationId, AccessShareLock);
+		ScanKeyInit(&key[0],
+					Anum_pg_propgraph_label_pglpgid,
+					BTEqualStrategyNumber,
+					F_OIDEQ, ObjectIdGetDatum(propgraphid));
+		scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
+								  true, NULL, 1, key);
+		while (HeapTupleIsValid(tup = systable_getnext(scan)))
+		{
+			Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup);
+
+			label_oids = lappend_oid(label_oids, label->oid);
+		}
+		systable_endscan(scan);
+		table_close(rel, AccessShareLock);
+	}
+	else if (IsA(labelexpr, GraphLabelRef))
+	{
+		GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr);
+
+		label_oids = list_make1_oid(glr->labelid);
+	}
+	else if (IsA(labelexpr, BoolExpr))
+	{
+		BoolExpr   *be = castNode(BoolExpr, labelexpr);
+		List	   *label_exprs = be->args;
+		ListCell   *llc;
+
+		label_oids = NIL;
+		foreach(llc, label_exprs)
+		{
+			GraphLabelRef *glr = lfirst_node(GraphLabelRef, llc);
+
+			label_oids = lappend_oid(label_oids, glr->labelid);
+		}
+	}
+	else
+		elog(ERROR, "unsupported label expression type: %d", (int) nodeTag(labelexpr));
+
+	return label_oids;
+}
+
+/*
+ * Given a graph element pattern `gep`, return a list of all the graph elements
+ * that satisfy the graph pattern.
+ *
+ * First we find all the graph labels that satisfy the label expression in
+ * graph element pattern. Each label has associated with one or more graph
+ * elements. A union of all such elements satisfies the element pattern. The
+ * returned list contains one graph_path_element object representing each of
+ * these elements respectively.
+ *
+ * `elempos` is position of the element pattern in the path pattern.
+ */
+static List *
+get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos)
+{
+	List	   *label_oids = get_labels_for_expr(propgraphid, gep->labelexpr);
+	List	   *elem_oids_seen = NIL;
+	List	   *elem_gpe_oids = NIL;
+	List	   *elem_gpes = NIL;
+	ListCell   *lc;
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tup;
+
+	rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+	foreach(lc, label_oids)
+	{
+		Oid			labeloid = lfirst_oid(lc);
+		bool		found = false;
+
+		ScanKeyInit(&key[0],
+					Anum_pg_propgraph_element_label_pgellabelid,
+					BTEqualStrategyNumber,
+					F_OIDEQ, ObjectIdGetDatum(labeloid));
+		scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true,
+								  NULL, 1, key);
+		while (HeapTupleIsValid(tup = systable_getnext(scan)))
+		{
+			Form_pg_propgraph_element_label label_elem = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+			Oid			elem_oid = label_elem->pgelelid;
+
+			if (!list_member_oid(elem_oids_seen, elem_oid))
+			{
+				/*
+				 * Found a new element that is associated with labels in the
+				 * given element pattern. If it fits the element pattern kind
+				 * we will create GraphPathPattern object for it and flag that
+				 * the current label has at least one element, that satisfies
+				 * the given element pattern, associated with it.
+				 */
+				struct graph_path_element *gpe = create_gpe_for_element(gep, elem_oid, elempos);
+
+				if (gpe)
+				{
+					elem_gpes = lappend(elem_gpes, gpe);
+					elem_gpe_oids = lappend_oid(elem_gpe_oids, elem_oid);
+					found = true;
+				}
+
+				/*
+				 * Add the graph element to the elements considered so far to
+				 * avoid processing same element associated with multiple
+				 * labels multiple times. Also avoids creating duplicate
+				 * GraphPathElements.
+				 */
+				elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
+			}
+			else if (list_member_oid(elem_gpe_oids, elem_oid))
+			{
+				/*
+				 * The graph element is known to qualify the given element
+				 * pattern. Flag that the current label has at least one
+				 * element, that satisfies the given element pattern,
+				 * associated with it.
+				 */
+				found = true;
+			}
+		}
+
+		if (!found)
+		{
+			/*
+			 * We did not find any element, that fits given element pattern
+			 * kind, associated with this label. The label or its properties
+			 * can not be associated with the given element pattern. Throw an
+			 * error if the label was explicitly specified in the element
+			 * pattern. Otherwise just Remove it from the list.
+			 */
+			if (!gep->labelexpr)
+				foreach_delete_current(label_oids, lc);
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("can not find label \"%s\" in property graph \"%s\" for element type \"%s\"",
+								get_propgraph_label_name(labeloid),
+								get_rel_name(propgraphid),
+								get_graph_elem_kind_name(gep->kind))));
+		}
+
+		systable_endscan(scan);
+	}
+	table_close(rel, AccessShareLock);
+
+	/* Update the filtered label list in each graph_path_element. */
+	foreach(lc, elem_gpes)
+	{
+		struct graph_path_element *gpe = lfirst(lc);
+
+		gpe->labeloids = label_oids;
+	}
+
+	return elem_gpes;
+}
+
+static const char *
+get_graph_elem_kind_name(GraphElementPatternKind gepkind)
+{
+	if (gepkind == VERTEX_PATTERN)
+		return "vertex";
+	else if (IS_EDGE_PATTERN(gepkind))
+		return "edge";
+	else if (gepkind == PAREN_EXPR)
+		return "nested path pattern";
+
+	return "unknown";
+}
+
+/*
+ * Mutating property references into table variables
+ */
+
+struct replace_property_refs_context
+{
+	Oid			propgraphid;
+	const List *mappings;
+};
+
+static Node *
+replace_property_refs_mutator(Node *node, struct replace_property_refs_context *context)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Var		   *newvar = copyObject(var);
+
+		/*
+		 * If it's already a Var, then it was a lateral reference.  Since we
+		 * are in a subquery after the rewrite, we have to increase the level
+		 * by one.
+		 */
+		newvar->varlevelsup++;
+
+		return (Node *) newvar;
+	}
+	else if (IsA(node, GraphPropertyRef))
+	{
+		GraphPropertyRef *gpr = (GraphPropertyRef *) node;
+		Node	   *n = NULL;
+		ListCell   *lc;
+		struct graph_path_element *found_mapping = NULL;
+		List	   *unrelated_labels = NIL;
+
+		foreach(lc, context->mappings)
+		{
+			struct graph_path_element *m = lfirst(lc);
+
+			if (m->parent_gep->variable && strcmp(gpr->elvarname, m->parent_gep->variable) == 0)
+			{
+				found_mapping = m;
+				break;
+			}
+		}
+		if (!found_mapping)
+			elog(ERROR, "undefined element variable \"%s\"", gpr->elvarname);
+
+		/*
+		 * Find property definition for given element through any of the
+		 * associated labels.
+		 */
+		foreach(lc, found_mapping->labeloids)
+		{
+			Oid			labeloid = lfirst_oid(lc);
+			Oid			elem_labelid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+													   Anum_pg_propgraph_element_label_oid,
+													   ObjectIdGetDatum(found_mapping->elemoid),
+													   ObjectIdGetDatum(labeloid));
+
+			if (OidIsValid(elem_labelid))
+			{
+				HeapTuple	tup = SearchSysCache2(PROPGRAPHLABELPROP, ObjectIdGetDatum(elem_labelid),
+												  ObjectIdGetDatum(gpr->propid));
+
+				if (!tup)
+				{
+					/*
+					 * The label is associated with the given element but it
+					 * is not associated with the required property. Check
+					 * next label.
+					 */
+					continue;
+				}
+
+				n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
+																			tup, Anum_pg_propgraph_label_property_plpexpr)));
+				ChangeVarNodes(n, 1, found_mapping->rtindex, 0);
+
+				ReleaseSysCache(tup);
+			}
+			else
+			{
+				/*
+				 * Label is not associated with the element but it may be
+				 * associated with the property through some other element.
+				 * Save it for later use.
+				 */
+				unrelated_labels = lappend_oid(unrelated_labels, labeloid);
+			}
+		}
+
+		/* See if we can resolve the property in some other way. */
+		if (!n)
+		{
+			ListCell   *lcu;
+			bool		prop_associated = false;
+
+			foreach(lcu, unrelated_labels)
+			{
+				if (is_property_associated_with_label(lfirst_oid(lcu), gpr->propid))
+				{
+					prop_associated = true;
+					break;
+				}
+			}
+
+			if (prop_associated)
+			{
+				/*
+				 * The property is associated with at least one of the labels
+				 * that satisfy given element pattern. If it's associated with
+				 * the given element through some any of the labels, use
+				 * correspondig value expression. Otherwise NULL. Ref. SQL/PGQ
+				 * standard section 6.5 Property Referece, General Rule 2.b.
+				 *
+				 * NOTE: An element path pattern may resolve to multiple
+				 * elements.  The above section does not seem to describe this
+				 * case. But it depends upon how the term ER is interpreted.
+				 * For a given path there's only one element bound to a given
+				 * ER. Hence the above stated rule can be applied here.  The
+				 * section also states the case when no element binds to ER.
+				 * We consider such paths as broken and do not contribute any
+				 * rows to the GRAPH_TABLE.
+				 */
+				n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
+											  found_mapping->rtindex);
+
+				if (!n)
+				{
+					/* XXX: Does collation of NULL value matter? */
+					n = (Node *) makeNullConst(gpr->typeId, -1, InvalidOid);
+				}
+			}
+
+		}
+
+		if (!n)
+			elog(ERROR, "property \"%s\" of element variable \"%s\" not found",
+				 get_propgraph_property_name(gpr->propid), found_mapping->parent_gep->variable);
+
+		return n;
+	}
+
+	return expression_tree_mutator(node, replace_property_refs_mutator, context);
+}
+
+static Node *
+replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
+{
+	struct replace_property_refs_context context;
+
+	context.mappings = mappings;
+	context.propgraphid = propgraphid;
+
+	return expression_tree_mutator(node, replace_property_refs_mutator, &context);
+}
+
+/*
+ * Build join qualification expressions between edge and vertex tables.
+ */
+static List *
+build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum)
+{
+	List	   *quals = NIL;
+	Form_pg_propgraph_element pgeform;
+	Datum		datum;
+	Datum	   *d1,
+			   *d2;
+	int			n1,
+				n2;
+
+	pgeform = (Form_pg_propgraph_element) GETSTRUCT(edgetup);
+
+	datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_key_attnum);
+	deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d1, NULL, &n1);
+
+	datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_ref_attnum);
+	deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d2, NULL, &n2);
+
+	if (n1 != n2)
+		elog(ERROR, "array size key (%d) vs ref (%d) mismatch for element ID %u", catalog_key_attnum, catalog_ref_attnum, pgeform->oid);
+
+	for (int i = 0; i < n1; i++)
+	{
+		AttrNumber	keyattn = DatumGetInt16(d1[i]);
+		AttrNumber	refattn = DatumGetInt16(d2[i]);
+		Oid			atttypid;
+		TypeCacheEntry *typentry;
+		OpExpr	   *op;
+
+		/*
+		 * TODO: Assumes types the same on both sides; no collations yet. Some
+		 * of this could probably be shared with foreign key triggers.
+		 */
+		atttypid = get_atttype(pgeform->pgerelid, keyattn);
+		typentry = lookup_type_cache(atttypid, TYPECACHE_EQ_OPR);
+
+		op = makeNode(OpExpr);
+		op->location = -1;
+		op->opno = typentry->eq_opr;
+		op->opresulttype = BOOLOID;
+		op->args = list_make2(makeVar(edgerti, keyattn, atttypid, -1, 0, 0),
+							  makeVar(refrti, refattn, atttypid, -1, 0, 0));
+		quals = lappend(quals, op);
+	}
+
+	return quals;
+}
+
+/*
+ * Check if the given property is associated with the given label.
+ *
+ * A label projects the same set of properties through every element it is
+ * associated with. Find any of the elements and return true if that element is
+ * associated with the given property. False otherwise.
+ */
+static bool
+is_property_associated_with_label(Oid labeloid, Oid propoid)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tup;
+	bool		associated = false;
+
+	rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_pgellabelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(labeloid));
+	scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
+							  true, NULL, 1, key);
+
+	if (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+
+		associated = SearchSysCacheExists2(PROPGRAPHLABELPROP,
+										   ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
+	}
+	systable_endscan(scan);
+	table_close(rel, RowShareLock);
+
+	return associated;
+}
+
+/*
+ * If given element has the given property associated with it, through any of
+ * the associated labels, return value expression of the property. Otherwise
+ * NULL.
+ */
+static Node *
+get_element_property_expr(Oid elemoid, Oid propoid, int rtindex)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	labeltup;
+	Node	   *n = NULL;
+
+	rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_element_label_pgelelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(elemoid));
+	scan = systable_beginscan(rel, PropgraphElementLabelElementLabelIndexId,
+							  true, NULL, 1, key);
+
+	while (HeapTupleIsValid(labeltup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(labeltup);
+
+		HeapTuple	proptup = SearchSysCache2(PROPGRAPHLABELPROP,
+											  ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
+
+		if (!proptup)
+			continue;
+		n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
+																	proptup, Anum_pg_propgraph_label_property_plpexpr)));
+		ChangeVarNodes(n, 1, rtindex, 0);
+
+		ReleaseSysCache(proptup);
+		break;
+	}
+	systable_endscan(scan);
+	table_close(rel, RowShareLock);
+
+	return n;
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index c223a2c50a..b49bcebdbd 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -36,6 +36,7 @@
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteDefine.h"
+#include "rewrite/rewriteGraphTable.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
 #include "rewrite/rewriteSearchCycle.h"
@@ -2024,6 +2025,16 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 
 		rte = rt_fetch(rt_index, parsetree->rtable);
 
+		/*
+		 * Convert GRAPH_TABLE clause into a subquery using relational
+		 * operators.  (This will change the rtekind to subquery, so it must
+		 * be done before the subquery handling below.)
+		 */
+		if (rte->rtekind == RTE_GRAPH_TABLE)
+		{
+			parsetree = rewriteGraphTable(parsetree, rt_index);
+		}
+
 		/*
 		 * A subquery RTE can't have associated rules, so there's nothing to
 		 * do to this level of the query, but we must recurse into the
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b2ea8125c9..3f0d55f715 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -44,6 +44,7 @@
 #include "commands/portalcmds.h"
 #include "commands/prepare.h"
 #include "commands/proclang.h"
+#include "commands/propgraphcmds.h"
 #include "commands/publicationcmds.h"
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
@@ -148,6 +149,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_AlterOperatorStmt:
 		case T_AlterOwnerStmt:
 		case T_AlterPolicyStmt:
+		case T_AlterPropGraphStmt:
 		case T_AlterPublicationStmt:
 		case T_AlterRoleSetStmt:
 		case T_AlterRoleStmt:
@@ -178,6 +180,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 		case T_CreateOpFamilyStmt:
 		case T_CreatePLangStmt:
 		case T_CreatePolicyStmt:
+		case T_CreatePropGraphStmt:
 		case T_CreatePublicationStmt:
 		case T_CreateRangeStmt:
 		case T_CreateRoleStmt:
@@ -1736,6 +1739,14 @@ ProcessUtilitySlow(ParseState *pstate,
 				commandCollected = true;
 				break;
 
+			case T_CreatePropGraphStmt:
+				address = CreatePropGraph(pstate, (CreatePropGraphStmt *) parsetree);
+				break;
+
+			case T_AlterPropGraphStmt:
+				address = AlterPropGraph(pstate, (AlterPropGraphStmt *) parsetree);
+				break;
+
 			case T_CreateTransformStmt:
 				address = CreateTransform((CreateTransformStmt *) parsetree);
 				break;
@@ -2004,6 +2015,7 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel)
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
 		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_PROPGRAPH:
 			RemoveRelations(stmt);
 			break;
 		default:
@@ -2280,6 +2292,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_PROCEDURE:
 			tag = CMDTAG_ALTER_PROCEDURE;
 			break;
+		case OBJECT_PROPGRAPH:
+			tag = CMDTAG_ALTER_PROPERTY_GRAPH;
+			break;
 		case OBJECT_ROLE:
 			tag = CMDTAG_ALTER_ROLE;
 			break;
@@ -2556,6 +2571,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_INDEX:
 					tag = CMDTAG_DROP_INDEX;
 					break;
+				case OBJECT_PROPGRAPH:
+					tag = CMDTAG_DROP_PROPERTY_GRAPH;
+					break;
 				case OBJECT_TYPE:
 					tag = CMDTAG_DROP_TYPE;
 					break;
@@ -2937,6 +2955,14 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
+		case T_CreatePropGraphStmt:
+			tag = CMDTAG_CREATE_PROPERTY_GRAPH;
+			break;
+
+		case T_AlterPropGraphStmt:
+			tag = CMDTAG_ALTER_PROPERTY_GRAPH;
+			break;
+
 		case T_CreateTransformStmt:
 			tag = CMDTAG_CREATE_TRANSFORM;
 			break;
@@ -3634,6 +3660,14 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreatePropGraphStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
+		case T_AlterPropGraphStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_CreateTransformStmt:
 			lev = LOGSTMT_DDL;
 			break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 00eda1b34c..e6c86b8ae6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -34,6 +34,11 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -345,6 +350,9 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									bool attrsOnly, bool keysOnly,
 									bool showTblSpc, bool inherits,
 									int prettyFlags, bool missing_ok);
+static void make_propgraphdef_elements(StringInfo buf, Oid pgrelid, char pgekind);
+static void make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid);
+static void make_propgraphdef_properties(StringInfo buf, Oid ellabelid, Oid elrelid);
 static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only,
 										 bool missing_ok);
 static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags,
@@ -1584,6 +1592,331 @@ pg_get_querydef(Query *query, bool pretty)
 	return buf.data;
 }
 
+/*
+ * pg_get_propgraphdef - get the definition of a property graph
+ */
+Datum
+pg_get_propgraphdef(PG_FUNCTION_ARGS)
+{
+	Oid			pgrelid = PG_GETARG_OID(0);
+	StringInfoData buf;
+	HeapTuple	classtup;
+	Form_pg_class classform;
+	char	   *name;
+	char	   *nsp;
+
+	initStringInfo(&buf);
+
+	classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(pgrelid));
+	if (!HeapTupleIsValid(classtup))
+		PG_RETURN_NULL();
+
+	classform = (Form_pg_class) GETSTRUCT(classtup);
+	name = NameStr(classform->relname);
+
+	if (classform->relkind != RELKIND_PROPGRAPH)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a property graph", name)));
+
+	nsp = get_namespace_name(classform->relnamespace);
+
+	appendStringInfo(&buf, "CREATE PROPERTY GRAPH %s",
+					 quote_qualified_identifier(nsp, name));
+
+	ReleaseSysCache(classtup);
+
+	make_propgraphdef_elements(&buf, pgrelid, PGEKIND_VERTEX);
+	make_propgraphdef_elements(&buf, pgrelid, PGEKIND_EDGE);
+
+	PG_RETURN_TEXT_P(string_to_text(buf.data));
+}
+
+/*
+ * Generates a VERTEX TABLES (...) or EDGE TABLES (...) clause.  Pass in the
+ * property graph relation OID and the element kind (vertex or edge).  Result
+ * is appended to buf.
+ */
+static void
+make_propgraphdef_elements(StringInfo buf, Oid pgrelid, char pgekind)
+{
+	Relation	pgerel;
+	ScanKeyData scankey[1];
+	SysScanDesc scan;
+	bool		first;
+	HeapTuple	tup;
+
+	pgerel = table_open(PropgraphElementRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey[0],
+				Anum_pg_propgraph_element_pgepgid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(pgrelid));
+
+	scan = systable_beginscan(pgerel, PropgraphElementAliasIndexId, true, NULL, 1, scankey);
+
+	first = true;
+	while ((tup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup);
+		char	   *relname;
+		Datum		datum;
+		bool		isnull;
+
+		if (pgeform->pgekind != pgekind)
+			continue;
+
+		if (first)
+		{
+			appendStringInfo(buf, "\n    %s TABLES (\n", pgekind == PGEKIND_VERTEX ? "VERTEX" : "EDGE");
+			first = false;
+		}
+		else
+			appendStringInfo(buf, ",\n");
+
+		relname = get_rel_name(pgeform->pgerelid);
+		if (relname && strcmp(relname, NameStr(pgeform->pgealias)) == 0)
+			appendStringInfo(buf, "        %s",
+							 generate_relation_name(pgeform->pgerelid, NIL));
+		else
+			appendStringInfo(buf, "        %s AS %s",
+							 generate_relation_name(pgeform->pgerelid, NIL),
+							 NameStr(pgeform->pgealias));
+
+		datum = heap_getattr(tup, Anum_pg_propgraph_element_pgekey, RelationGetDescr(pgerel), &isnull);
+		if (!isnull)
+		{
+			appendStringInfoString(buf, " KEY (");
+			decompile_column_index_array(datum, pgeform->pgerelid, buf);
+			appendStringInfoString(buf, ")");
+		}
+		else
+			elog(ERROR, "null pgekey for element %u", pgeform->oid);
+
+		if (pgekind == PGEKIND_EDGE)
+		{
+			Datum		srckey;
+			Datum		srcref;
+			Datum		destkey;
+			Datum		destref;
+			HeapTuple	tup2;
+			Form_pg_propgraph_element pgeform2;
+
+			datum = heap_getattr(tup, Anum_pg_propgraph_element_pgesrckey, RelationGetDescr(pgerel), &isnull);
+			srckey = isnull ? 0 : datum;
+			datum = heap_getattr(tup, Anum_pg_propgraph_element_pgesrcref, RelationGetDescr(pgerel), &isnull);
+			srcref = isnull ? 0 : datum;
+			datum = heap_getattr(tup, Anum_pg_propgraph_element_pgedestkey, RelationGetDescr(pgerel), &isnull);
+			destkey = isnull ? 0 : datum;
+			datum = heap_getattr(tup, Anum_pg_propgraph_element_pgedestref, RelationGetDescr(pgerel), &isnull);
+			destref = isnull ? 0 : datum;
+
+			appendStringInfoString(buf, " SOURCE");
+			tup2 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(pgeform->pgesrcvertexid));
+			if (!tup2)
+				elog(ERROR, "cache lookup failed for property graph element %u", pgeform->pgesrcvertexid);
+			pgeform2 = (Form_pg_propgraph_element) GETSTRUCT(tup2);
+			if (srckey)
+			{
+				appendStringInfoString(buf, " KEY (");
+				decompile_column_index_array(srckey, pgeform->pgerelid, buf);
+				appendStringInfo(buf, ") REFERENCES %s (", NameStr(pgeform2->pgealias));
+				decompile_column_index_array(srcref, pgeform2->pgerelid, buf);
+				appendStringInfoString(buf, ")");
+			}
+			else
+				appendStringInfo(buf, " %s ", NameStr(pgeform2->pgealias));
+			ReleaseSysCache(tup2);
+
+			appendStringInfoString(buf, " DESTINATION");
+			tup2 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(pgeform->pgedestvertexid));
+			if (!tup2)
+				elog(ERROR, "cache lookup failed for property graph element %u", pgeform->pgedestvertexid);
+			pgeform2 = (Form_pg_propgraph_element) GETSTRUCT(tup2);
+			if (destkey)
+			{
+				appendStringInfoString(buf, " KEY (");
+				decompile_column_index_array(destkey, pgeform->pgerelid, buf);
+				appendStringInfo(buf, ") REFERENCES %s (", NameStr(pgeform2->pgealias));
+				decompile_column_index_array(destref, pgeform2->pgerelid, buf);
+				appendStringInfoString(buf, ")");
+			}
+			else
+				appendStringInfo(buf, " %s", NameStr(pgeform2->pgealias));
+			ReleaseSysCache(tup2);
+		}
+
+		make_propgraphdef_labels(buf, pgeform->oid, NameStr(pgeform->pgealias), pgeform->pgerelid);
+	}
+	if (!first)
+		appendStringInfo(buf, "\n    )");
+
+	systable_endscan(scan);
+	table_close(pgerel, AccessShareLock);
+}
+
+/*
+ * Generates label and properties list.  Pass in the element OID, the element
+ * alias, and the graph relation OID.  Result is append to buf.
+ */
+static void
+make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid)
+{
+	Relation	pglrel;
+	ScanKeyData scankey[1];
+	SysScanDesc scan;
+	int			count;
+	HeapTuple	tup;
+
+	pglrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey[0],
+				Anum_pg_propgraph_element_label_pgelelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(elid));
+
+	count = 0;
+	scan = systable_beginscan(pglrel, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, scankey);
+	while ((tup = systable_getnext(scan)))
+	{
+		count++;
+	}
+	systable_endscan(scan);
+
+	/* XXX need to re-init scan key for second scan */
+	ScanKeyInit(&scankey[0],
+				Anum_pg_propgraph_element_label_pgelelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(elid));
+
+	scan = systable_beginscan(pglrel, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, scankey);
+
+	while ((tup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_element_label pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+		const char *labelname;
+
+		labelname = get_propgraph_label_name(pgelform->pgellabelid);
+
+		if (strcmp(labelname, elalias) == 0)
+		{
+			/* If the default label is the only label, don't print anything. */
+			if (count != 1)
+				appendStringInfo(buf, " DEFAULT LABEL");
+		}
+		else
+			appendStringInfo(buf, " LABEL %s", quote_identifier(labelname));
+
+		make_propgraphdef_properties(buf, pgelform->oid, elrelid);
+	}
+
+	systable_endscan(scan);
+
+	table_close(pglrel, AccessShareLock);
+}
+
+/*
+ * Helper function for make_propgraphdef_properties(): Sort (propname, expr)
+ * pairs by name.
+ */
+static int
+propdata_by_name_cmp(const ListCell *a, const ListCell *b)
+{
+	List	   *la = lfirst_node(List, a);
+	List	   *lb = lfirst_node(List, b);
+	char	   *pna = strVal(linitial(la));
+	char	   *pnb = strVal(linitial(lb));
+
+	return strcmp(pna, pnb);
+}
+
+/*
+ * Generates element table properties clause (PROPERTIES (...) or NO
+ * PROPERTIES).  Pass in label ODI and element table OID.  Result is appended
+ * to buf.
+ */
+static void
+make_propgraphdef_properties(StringInfo buf, Oid ellabelid, Oid elrelid)
+{
+	Relation	plprel;
+	ScanKeyData scankey[1];
+	SysScanDesc scan;
+	HeapTuple	tup;
+	List	   *outlist = NIL;
+
+	plprel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey[0],
+				Anum_pg_propgraph_label_property_plpellabelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(ellabelid));
+
+	/*
+	 * We want to output the properties in a deterministic order.  So we first
+	 * read all the data, then sort, then print it.
+	 */
+	scan = systable_beginscan(plprel, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, scankey);
+
+	while ((tup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tup);
+		Datum		exprDatum;
+		bool		isnull;
+		char	   *tmp;
+		Node	   *expr;
+		char	   *propname;
+
+		exprDatum = heap_getattr(tup, Anum_pg_propgraph_label_property_plpexpr, RelationGetDescr(plprel), &isnull);
+		Assert(!isnull);
+		tmp = TextDatumGetCString(exprDatum);
+		expr = stringToNode(tmp);
+		pfree(tmp);
+
+		propname = get_propgraph_property_name(plpform->plppropid);
+
+		outlist = lappend(outlist, list_make2(makeString(propname), expr));
+	}
+
+	systable_endscan(scan);
+	table_close(plprel, AccessShareLock);
+
+	list_sort(outlist, propdata_by_name_cmp);
+
+	if (outlist)
+	{
+		List	   *context;
+		ListCell   *lc;
+		bool		first = true;
+
+		context = deparse_context_for(get_relation_name(elrelid), elrelid);
+
+		appendStringInfo(buf, " PROPERTIES (");
+
+		foreach(lc, outlist)
+		{
+			List	   *data = lfirst_node(List, lc);
+			char	   *propname = strVal(linitial(data));
+			Node	   *expr = lsecond(data);
+
+			if (first)
+				first = false;
+			else
+				appendStringInfo(buf, ", ");
+
+			if (IsA(expr, Var) && strcmp(propname, get_attname(elrelid, castNode(Var, expr)->varattno, false)) == 0)
+				appendStringInfo(buf, "%s", propname);
+			else
+				appendStringInfo(buf, "%s AS %s",
+								 deparse_expression_pretty(expr, context, false, false, 0, 0),
+								 propname);
+		}
+
+		appendStringInfo(buf, ")");
+	}
+	else
+		appendStringInfo(buf, " NO PROPERTIES");
+}
+
 /*
  * pg_get_statisticsobjdef
  *		Get the definition of an extended statistics object
@@ -7275,6 +7608,171 @@ get_utility_query_def(Query *query, deparse_context *context)
 	}
 }
 
+
+/*
+ * Parse back a graph label expression
+ */
+static void
+get_graph_label_expr(Node *label_expr, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+
+	check_stack_depth();
+
+	switch (nodeTag(label_expr))
+	{
+		case T_GraphLabelRef:
+			{
+				GraphLabelRef *lref = (GraphLabelRef *) label_expr;
+
+				appendStringInfoString(buf, quote_identifier(get_propgraph_label_name(lref->labelid)));
+				break;
+			}
+
+		case T_BoolExpr:
+			{
+				BoolExpr   *be = (BoolExpr *) label_expr;
+				ListCell   *lc;
+				bool		first = true;
+
+				Assert(be->boolop == OR_EXPR);
+
+				foreach(lc, be->args)
+				{
+					if (!first)
+					{
+						if (be->boolop == OR_EXPR)
+							appendStringInfoString(buf, "|");
+					}
+					else
+						first = false;
+					get_graph_label_expr(lfirst(lc), context);
+				}
+
+				break;
+			}
+
+		default:
+			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(label_expr));
+			break;
+	}
+}
+
+/*
+ * Parse back a path pattern expression
+ */
+static void
+get_path_pattern_expr_def(List *path_pattern_expr, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+	ListCell   *lc;
+
+	foreach(lc, path_pattern_expr)
+	{
+		GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc);
+		const char *sep = "";
+
+		switch (gep->kind)
+		{
+			case VERTEX_PATTERN:
+				appendStringInfoString(buf, "(");
+				break;
+			case EDGE_PATTERN_LEFT:
+				appendStringInfoString(buf, "<-[");
+				break;
+			case EDGE_PATTERN_RIGHT:
+			case EDGE_PATTERN_ANY:
+				appendStringInfoString(buf, "-[");
+				break;
+			case PAREN_EXPR:
+				appendStringInfoString(buf, "(");
+				break;
+		}
+
+		if (gep->variable)
+		{
+			appendStringInfoString(buf, gep->variable);
+			sep = " ";
+		}
+
+		if (gep->labelexpr)
+		{
+			appendStringInfoString(buf, sep);
+			appendStringInfoString(buf, "IS ");
+			get_graph_label_expr(gep->labelexpr, context);
+			sep = " ";
+		}
+
+		if (gep->subexpr)
+		{
+			appendStringInfoString(buf, sep);
+			get_path_pattern_expr_def(gep->subexpr, context);
+			sep = " ";
+		}
+
+		if (gep->whereClause)
+		{
+			appendStringInfoString(buf, sep);
+			appendStringInfoString(buf, "WHERE ");
+			get_rule_expr(gep->whereClause, context, false);
+		}
+
+		switch (gep->kind)
+		{
+			case VERTEX_PATTERN:
+				appendStringInfoString(buf, ")");
+				break;
+			case EDGE_PATTERN_LEFT:
+			case EDGE_PATTERN_ANY:
+				appendStringInfoString(buf, "]-");
+				break;
+			case EDGE_PATTERN_RIGHT:
+				appendStringInfoString(buf, "]->");
+				break;
+			case PAREN_EXPR:
+				appendStringInfoString(buf, ")");
+				break;
+		}
+
+		if (gep->quantifier)
+		{
+			int			lower = linitial_int(gep->quantifier);
+			int			upper = lsecond_int(gep->quantifier);
+
+			appendStringInfo(buf, "{%d,%d}", lower, upper);
+		}
+	}
+}
+
+/*
+ * Parse back a graph pattern
+ */
+static void
+get_graph_pattern_def(GraphPattern *graph_pattern, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+	ListCell   *lc;
+	bool		first = true;
+
+	foreach(lc, graph_pattern->path_pattern_list)
+	{
+		List	   *path_pattern_expr = lfirst_node(List, lc);
+
+		if (!first)
+			appendStringInfoString(buf, ", ");
+		else
+			first = false;
+
+		get_path_pattern_expr_def(path_pattern_expr, context);
+	}
+
+	if (graph_pattern->whereClause)
+	{
+		appendStringInfoString(buf, "WHERE ");
+		get_rule_expr(graph_pattern->whereClause, context, false);
+	}
+}
+
 /*
  * Display a Var appropriately.
  *
@@ -7837,6 +8335,7 @@ get_name_for_var_field(Var *var, int fieldno,
 		case RTE_RELATION:
 		case RTE_VALUES:
 		case RTE_NAMEDTUPLESTORE:
+		case RTE_GRAPH_TABLE:
 		case RTE_RESULT:
 
 			/*
@@ -10226,6 +10725,14 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_tablefunc((TableFunc *) node, context, showimplicit);
 			break;
 
+		case T_GraphPropertyRef:
+			{
+				GraphPropertyRef *gpr = (GraphPropertyRef *) node;
+
+				appendStringInfo(buf, "%s.%s", quote_identifier(gpr->elvarname), quote_identifier(get_propgraph_property_name(gpr->propid)));
+				break;
+			}
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
@@ -12084,6 +12591,36 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			case RTE_TABLEFUNC:
 				get_tablefunc(rte->tablefunc, context, true);
 				break;
+			case RTE_GRAPH_TABLE:
+				appendStringInfoString(buf, "GRAPH_TABLE (");
+				appendStringInfoString(buf, generate_relation_name(rte->relid, context->namespaces));
+				appendStringInfoString(buf, " MATCH ");
+				get_graph_pattern_def(rte->graph_pattern, context);
+				appendStringInfoString(buf, " COLUMNS (");
+				{
+					ListCell   *lc;
+					bool		first = true;
+
+					foreach(lc, rte->graph_table_columns)
+					{
+						TargetEntry *te = lfirst_node(TargetEntry, lc);
+						deparse_context context = {0};
+
+						if (!first)
+							appendStringInfoString(buf, ", ");
+						else
+							first = false;
+
+						context.buf = buf;
+
+						get_rule_expr((Node *) te->expr, &context, false);
+						appendStringInfoString(buf, " AS ");
+						appendStringInfoString(buf, quote_identifier(te->resname));
+					}
+				}
+				appendStringInfoString(buf, ")");
+				appendStringInfoString(buf, ")");
+				break;
 			case RTE_VALUES:
 				/* Values list RTE */
 				appendStringInfoChar(buf, '(');
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 48a280d089..746333e315 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -32,6 +32,8 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_statistic.h"
@@ -3714,3 +3716,39 @@ get_subscription_name(Oid subid, bool missing_ok)
 
 	return subname;
 }
+
+char *
+get_propgraph_label_name(Oid labeloid)
+{
+	HeapTuple	tuple;
+	char	   *labelname;
+
+	tuple = SearchSysCache1(PROPGRAPHLABELOID, labeloid);
+	if (!tuple)
+	{
+		elog(ERROR, "cache lookup failed for label %u", labeloid);
+		return NULL;
+	}
+	labelname = pstrdup(NameStr(((Form_pg_propgraph_label) GETSTRUCT(tuple))->pgllabel));
+	ReleaseSysCache(tuple);
+
+	return labelname;
+}
+
+char *
+get_propgraph_property_name(Oid propoid)
+{
+	HeapTuple	tuple;
+	char	   *propname;
+
+	tuple = SearchSysCache1(PROPGRAPHPROPOID, propoid);
+	if (!tuple)
+	{
+		elog(ERROR, "cache lookup failed for property %u", propoid);
+		return NULL;
+	}
+	propname = pstrdup(NameStr(((Form_pg_propgraph_property) GETSTRUCT(tuple))->pgpname));
+	ReleaseSysCache(tuple);
+
+	return propname;
+}
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index c323b5bd3d..57fb5e6062 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -497,7 +497,8 @@ flagInhAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		/* Some kinds never have parents */
 		if (tbinfo->relkind == RELKIND_SEQUENCE ||
 			tbinfo->relkind == RELKIND_VIEW ||
-			tbinfo->relkind == RELKIND_MATVIEW)
+			tbinfo->relkind == RELKIND_MATVIEW ||
+			tbinfo->relkind == RELKIND_PROPGRAPH)
 			continue;
 
 		/* Don't bother computing anything for non-target tables, either */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 8c20c263c4..254b47a7ef 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3641,6 +3641,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
 		strcmp(type, "DOMAIN") == 0 ||
 		strcmp(type, "FOREIGN TABLE") == 0 ||
 		strcmp(type, "MATERIALIZED VIEW") == 0 ||
+		strcmp(type, "PROPERTY GRAPH") == 0 ||
 		strcmp(type, "SEQUENCE") == 0 ||
 		strcmp(type, "STATISTICS") == 0 ||
 		strcmp(type, "TABLE") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b6e01d3d29..528c61e5b4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1691,10 +1691,10 @@ expand_table_name_patterns(Archive *fout,
 						  "\n     LEFT JOIN pg_catalog.pg_namespace n"
 						  "\n     ON n.oid OPERATOR(pg_catalog.=) c.relnamespace"
 						  "\nWHERE c.relkind OPERATOR(pg_catalog.=) ANY"
-						  "\n    (array['%c', '%c', '%c', '%c', '%c', '%c'])\n",
+						  "\n    (array['%c', '%c', '%c', '%c', '%c', '%c', '%c'])\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
 						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
-						  RELKIND_PARTITIONED_TABLE);
+						  RELKIND_PARTITIONED_TABLE, RELKIND_PROPGRAPH);
 		initPQExpBuffer(&dbbuf);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
@@ -2861,6 +2861,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 	if (tbinfo->dataObj != NULL)
 		return;
 
+	/* Skip property graphs (no data to dump) */
+	if (tbinfo->relkind == RELKIND_PROPGRAPH)
+		return;
 	/* Skip VIEWs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_VIEW)
 		return;
@@ -6966,7 +6969,8 @@ getTables(Archive *fout, int *numTables)
 						 CppAsString2(RELKIND_COMPOSITE_TYPE) ", "
 						 CppAsString2(RELKIND_MATVIEW) ", "
 						 CppAsString2(RELKIND_FOREIGN_TABLE) ", "
-						 CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n"
+						 CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
+						 CppAsString2(RELKIND_PROPGRAPH) ")\n"
 						 "ORDER BY c.oid");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15792,8 +15796,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
 		reltypename = "VIEW";
 
-		appendPQExpBuffer(delq, "DROP VIEW %s;\n", qualrelname);
-
 		if (dopt->binary_upgrade)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid);
@@ -15819,6 +15821,47 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 			appendPQExpBuffer(q, "\n  WITH %s CHECK OPTION", tbinfo->checkoption);
 		appendPQExpBufferStr(q, ";\n");
 	}
+	else if (tbinfo->relkind == RELKIND_PROPGRAPH)
+	{
+		PQExpBuffer query = createPQExpBuffer();
+		PGresult   *res;
+		int			len;
+
+		reltypename = "PROPERTY GRAPH";
+
+		if (dopt->binary_upgrade)
+			binary_upgrade_set_pg_class_oids(fout, q,
+											 tbinfo->dobj.catId.oid, false);
+
+		appendPQExpBuffer(query,
+						  "SELECT pg_catalog.pg_get_propgraphdef('%u'::pg_catalog.oid) AS pgdef",
+						  tbinfo->dobj.catId.oid);
+
+		res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+		if (PQntuples(res) != 1)
+		{
+			if (PQntuples(res) < 1)
+				pg_fatal("query to obtain definition of property graph \"%s\" returned no data",
+						 tbinfo->dobj.name);
+			else
+				pg_fatal("query to obtain definition of property graph \"%s\" returned more than one definition",
+						 tbinfo->dobj.name);
+		}
+
+		len = PQgetlength(res, 0, 0);
+
+		if (len == 0)
+			pg_fatal("definition of property graph \"%s\" appears to be empty (length zero)",
+					 tbinfo->dobj.name);
+
+		appendPQExpBufferStr(q, PQgetvalue(res, 0, 0));
+
+		PQclear(res);
+		destroyPQExpBuffer(query);
+
+		appendPQExpBufferStr(q, ";\n");
+	}
 	else
 	{
 		char	   *partkeydef = NULL;
@@ -15894,8 +15937,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		numParents = tbinfo->numParents;
 		parents = tbinfo->parents;
 
-		appendPQExpBuffer(delq, "DROP %s %s;\n", reltypename, qualrelname);
-
 		if (dopt->binary_upgrade)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid);
@@ -16533,6 +16574,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n",
 						  qualrelname);
 
+	appendPQExpBuffer(delq, "DROP %s %s;\n", reltypename, qualrelname);
+
 	if (dopt->binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj,
 										reltypename, qrelname,
@@ -18469,6 +18512,16 @@ getDependencies(Archive *fout)
 						 "classid = 'pg_amproc'::regclass AND objid = p.oid "
 						 "AND NOT (refclassid = 'pg_opfamily'::regclass AND amprocfamily = refobjid)\n");
 
+	/*
+	 * Translate dependencies of pg_propgraph_element entries into
+	 * dependencies of their parent pg_class entry.
+	 */
+	appendPQExpBufferStr(query, "UNION ALL\n"
+						 "SELECT 'pg_class'::regclass AS classid, pgepgid AS objid, refclassid, refobjid, deptype "
+						 "FROM pg_depend d, pg_propgraph_element pge "
+						 "WHERE deptype NOT IN ('p', 'e', 'i') AND "
+						 "classid = 'pg_propgraph_element'::regclass AND objid = pge.oid\n");
+
 	/* Sort the output for efficiency below */
 	appendPQExpBufferStr(query, "ORDER BY 1,2");
 
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 5bcc2244d5..9d6485aca1 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2912,6 +2912,17 @@ my %tests = (
 		},
 	},
 
+	'CREATE PROPERTY GRAPH propgraph' => {
+		create_order => 20,
+		create_sql   => 'CREATE PROPERTY GRAPH dump_test.propgraph;',
+		regexp => qr/^
+			\QCREATE PROPERTY GRAPH dump_test.propgraph\E;
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => { exclude_dump_test_schema => 1, only_dump_measurement => 1, },
+	},
+
 	'CREATE PUBLICATION pub1' => {
 		create_order => 50,
 		create_sql => 'CREATE PUBLICATION pub1;',
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4dfc7b2d85..30bbdd6b93 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -871,7 +871,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 					success = describeTableDetails(pattern, show_verbose, show_system);
 				else
 					/* standard listing of interesting things */
-					success = listTables("tvmsE", NULL, show_verbose, show_system);
+					success = listTables("tvmsEG", NULL, show_verbose, show_system);
 				break;
 			case 'A':
 				{
@@ -1002,6 +1002,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
 			case 'i':
 			case 's':
 			case 'E':
+			case 'G':
 				success = listTables(&cmd[1], pattern, show_verbose, show_system);
 				break;
 			case 'r':
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7c9a1f234c..7ba631cd1b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1029,6 +1029,7 @@ permissionsList(const char *pattern, bool showSystem)
 					  " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
+					  " WHEN " CppAsString2(RELKIND_PROPGRAPH) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  ",
@@ -1039,6 +1040,7 @@ permissionsList(const char *pattern, bool showSystem)
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
 					  gettext_noop("foreign table"),
+					  gettext_noop("property graph"),
 					  gettext_noop("partitioned table"),
 					  gettext_noop("Type"));
 
@@ -1130,6 +1132,7 @@ permissionsList(const char *pattern, bool showSystem)
 						 CppAsString2(RELKIND_MATVIEW) ","
 						 CppAsString2(RELKIND_SEQUENCE) ","
 						 CppAsString2(RELKIND_FOREIGN_TABLE) ","
+						 CppAsString2(RELKIND_PROPGRAPH) ","
 						 CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n");
 
 	if (!showSystem && !pattern)
@@ -2013,6 +2016,10 @@ describeOneTableDetails(const char *schemaname,
 				printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
 								  schemaname, relationname);
 			break;
+		case RELKIND_PROPGRAPH:
+			printfPQExpBuffer(&title, _("Property graph \"%s.%s\""),
+							  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -3052,6 +3059,32 @@ describeOneTableDetails(const char *schemaname,
 		}
 	}
 
+	/* Add property graph definition in verbose mode */
+	if (tableinfo.relkind == RELKIND_PROPGRAPH && verbose)
+	{
+		PGresult   *result;
+		char	   *pgdef = NULL;
+
+		printfPQExpBuffer(&buf,
+						  "SELECT pg_catalog.pg_get_propgraphdef('%s'::pg_catalog.oid);",
+						  oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+			pgdef = pg_strdup(PQgetvalue(result, 0, 0));
+
+		PQclear(result);
+
+		if (pgdef)
+		{
+			printTableAddFooter(&cont, _("Property graph definition:"));
+			printfPQExpBuffer(&buf, " %s", pgdef);
+			printTableAddFooter(&cont, buf.data);
+		}
+	}
+
 	/* Get view_def if table is a view or materialized view */
 	if ((tableinfo.relkind == RELKIND_VIEW ||
 		 tableinfo.relkind == RELKIND_MATVIEW) && verbose)
@@ -3903,6 +3936,7 @@ describeRoleGrants(const char *pattern, bool showSystem)
  * m - materialized views
  * s - sequences
  * E - foreign table (Note: different from 'f', the relkind value)
+ * G - property graphs
  * (any order of the above is fine)
  */
 bool
@@ -3914,6 +3948,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	bool		showMatViews = strchr(tabtypes, 'm') != NULL;
 	bool		showSeq = strchr(tabtypes, 's') != NULL;
 	bool		showForeign = strchr(tabtypes, 'E') != NULL;
+	bool		showPropGraphs = strchr(tabtypes, 'G') != NULL;
 
 	PQExpBufferData buf;
 	PGresult   *res;
@@ -3922,8 +3957,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	bool		translate_columns[] = {false, false, true, false, false, false, false, false, false};
 
 	/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
-	if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
-		showTables = showViews = showMatViews = showSeq = showForeign = true;
+	if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign || showPropGraphs))
+		showTables = showViews = showMatViews = showSeq = showForeign = showPropGraphs = true;
 
 	initPQExpBuffer(&buf);
 
@@ -3940,6 +3975,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
 					  " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
+					  " WHEN " CppAsString2(RELKIND_PROPGRAPH) " THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
 					  gettext_noop("Schema"),
@@ -3953,6 +3989,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("foreign table"),
 					  gettext_noop("partitioned table"),
 					  gettext_noop("partitioned index"),
+					  gettext_noop("property graph"),
 					  gettext_noop("Type"),
 					  gettext_noop("Owner"));
 	cols_so_far = 4;
@@ -4036,6 +4073,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */
 	if (showForeign)
 		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ",");
+	if (showPropGraphs)
+		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_PROPGRAPH) ",");
 
 	appendPQExpBufferStr(&buf, "''");	/* dummy */
 	appendPQExpBufferStr(&buf, ")\n");
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 19d20c5878..00f33cdc19 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -249,6 +249,7 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\dFp[+] [PATTERN]      list text search parsers\n");
 	HELP0("  \\dFt[+] [PATTERN]      list text search templates\n");
 	HELP0("  \\dg[S+] [PATTERN]      list roles\n");
+	HELP0("  \\dG[S+] [PATTERN]      list property graphs");
 	HELP0("  \\di[S+] [PATTERN]      list indexes\n");
 	HELP0("  \\dl[+]                 list large objects, same as \\lo_list\n");
 	HELP0("  \\dL[S+] [PATTERN]      list procedural languages\n");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a7ccde6d7d..71fe2c1e0e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -788,6 +788,14 @@ static const SchemaQuery Query_for_list_of_partitioned_indexes = {
 	.result = "c.relname",
 };
 
+static const SchemaQuery Query_for_list_of_propgraphs = {
+	.catname = "pg_catalog.pg_class c",
+	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_PROPGRAPH) ")",
+	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+	.namespace = "c.relnamespace",
+	.result = "pg_catalog.quote_ident(c.relname)",
+};
+
 
 /* All relations */
 static const SchemaQuery Query_for_list_of_relations = {
@@ -1256,6 +1264,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"PARSER", NULL, NULL, &Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL, NULL},
 	{"PROCEDURE", NULL, NULL, Query_for_list_of_procedures},
+	{"PROPERTY GRAPH", NULL, NULL, &Query_for_list_of_propgraphs},
 	{"PUBLICATION", NULL, Query_for_list_of_publications},
 	{"ROLE", Query_for_list_of_roles},
 	{"ROUTINE", NULL, NULL, &Query_for_list_of_routines, NULL, THING_NO_CREATE},
@@ -2307,6 +2316,20 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
 		COMPLETE_WITH("(");
 
+	/* ALTER PROPERTY GRAPH */
+	else if (Matches("ALTER", "PROPERTY", "GRAPH"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
+	else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny))
+		COMPLETE_WITH("ADD", "ALTER", "DROP", "OWNER TO", "RENAME TO", "SET SCHEMA");
+	else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD|ALTER|DROP"))
+		COMPLETE_WITH("VERTEX", "EDGE");
+	else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD|DROP", "VERTEX|EDGE"))
+		COMPLETE_WITH("TABLES");
+	else if (HeadMatches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD") && TailMatches("EDGE"))
+		COMPLETE_WITH("TABLES");
+	else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ALTER", "VERTEX|EDGE"))
+		COMPLETE_WITH("TABLE");
+
 	/* ALTER RULE <name>, add ON */
 	else if (Matches("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH("ON");
@@ -2786,7 +2809,7 @@ psql_completion(const char *text, int start, int end)
 					  "FOREIGN DATA WRAPPER", "FOREIGN TABLE",
 					  "FUNCTION", "INDEX", "LANGUAGE", "LARGE OBJECT",
 					  "MATERIALIZED VIEW", "OPERATOR", "POLICY",
-					  "PROCEDURE", "PROCEDURAL LANGUAGE", "PUBLICATION", "ROLE",
+					  "PROCEDURE", "PROCEDURAL LANGUAGE", "PROPERTY GRAPH", "PUBLICATION", "ROLE",
 					  "ROUTINE", "RULE", "SCHEMA", "SEQUENCE", "SERVER",
 					  "STATISTICS", "SUBSCRIPTION", "TABLE",
 					  "TABLESPACE", "TEXT SEARCH", "TRANSFORM FOR",
@@ -2824,6 +2847,8 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (Matches("COMMENT", "ON", "PROCEDURAL", "LANGUAGE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+	else if (Matches("COMMENT", "ON", "PROPERTY", "GRAPH"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
 	else if (Matches("COMMENT", "ON", "RULE", MatchAny))
 		COMPLETE_WITH("ON");
 	else if (Matches("COMMENT", "ON", "RULE", MatchAny, "ON"))
@@ -3142,6 +3167,25 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
 		COMPLETE_WITH("(");
 
+/* CREATE PROPERTY GRAPH */
+	else if (Matches("CREATE", "PROPERTY"))
+		COMPLETE_WITH("GRAPH");
+	else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny))
+		COMPLETE_WITH("VERTEX");
+	else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE"))
+		COMPLETE_WITH("TABLES");
+	else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES"))
+		COMPLETE_WITH("(");
+	else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "("))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+	else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(*)"))
+		COMPLETE_WITH("EDGE");
+	else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP"))
+		COMPLETE_WITH("TABLES");
+	else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP", "TABLES"))
+		COMPLETE_WITH("(");
+	else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP", "TABLES", "("))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE PUBLICATION */
 	else if (Matches("CREATE", "PUBLICATION", MatchAny))
@@ -3803,6 +3847,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
 
+	/* DROP PROPERTY GRAPH */
+	else if (Matches("DROP", "PROPERTY"))
+		COMPLETE_WITH("GRAPH");
+	else if (Matches("DROP", "PROPERTY", "GRAPH"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
+
 	/* DROP RULE */
 	else if (Matches("DROP", "RULE", MatchAny))
 		COMPLETE_WITH("ON");
@@ -4049,6 +4099,7 @@ psql_completion(const char *text, int start, int end)
 											"LARGE OBJECT",
 											"PARAMETER",
 											"PROCEDURE",
+											"PROPERTY GRAPH",
 											"ROUTINE",
 											"SCHEMA",
 											"SEQUENCE",
@@ -4178,6 +4229,14 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH("FROM");
 	}
 
+/* GRAPH_TABLE */
+	else if (TailMatches("GRAPH_TABLE"))
+		COMPLETE_WITH("(");
+	else if (TailMatches("GRAPH_TABLE", "("))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
+	else if (TailMatches("GRAPH_TABLE", "(", MatchAny))
+		COMPLETE_WITH("MATCH");
+
 /* GROUP BY */
 	else if (TailMatches("FROM", MatchAny, "GROUP"))
 		COMPLETE_WITH("BY");
@@ -4501,8 +4560,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN",
 					  "EVENT TRIGGER", "FOREIGN TABLE", "FUNCTION",
 					  "LARGE OBJECT", "MATERIALIZED VIEW", "LANGUAGE",
-					  "PUBLICATION", "PROCEDURE", "ROLE", "ROUTINE", "SCHEMA",
+					  "PROPERTY GRAPH", "PUBLICATION", "PROCEDURE", "ROLE", "ROUTINE", "SCHEMA",
 					  "SEQUENCE", "SUBSCRIPTION", "TABLESPACE", "TYPE", "VIEW");
+	else if (Matches("SECURITY", "LABEL", "ON", "PROPERTY", "GRAPH"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
 	else if (Matches("SECURITY", "LABEL", "ON", MatchAny, MatchAny))
 		COMPLETE_WITH("IS");
 
@@ -4919,6 +4980,8 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH("OBJECT");
 		else if (TailMatches("CREATE|ALTER|DROP", "MATERIALIZED"))
 			COMPLETE_WITH("VIEW");
+		else if (TailMatches("CREATE|ALTER|DROP", "PROPERTY"))
+			COMPLETE_WITH("GRAPH");
 		else if (TailMatches("CREATE|ALTER|DROP", "TEXT"))
 			COMPLETE_WITH("SEARCH");
 		else if (TailMatches("CREATE|ALTER|DROP", "USER"))
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index ddc4658b92..f2f876f598 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -302,6 +302,8 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+/* Note there is no need for left_arrow, since "<-" is not a single operator. */
+right_arrow		"->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -313,7 +315,7 @@ not_equals		"!="
  * If you change either set, adjust the character lists appearing in the
  * rule for "operator"!
  */
-self			[,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self			[,()\[\].;\:\|\+\-\*\/\%\^\<\>\=]
 op_chars		[\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
 operator		{op_chars}+
 
@@ -647,6 +649,10 @@ other			.
 					ECHO;
 				}
 
+{right_arrow}	{
+					ECHO;
+				}
+
 	/*
 	 * These rules are specific to psql --- they implement parenthesis
 	 * counting and detection of command-ending semicolon.  These must
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index 167f91a6e3..e126aa199b 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -81,7 +81,12 @@ CATALOG_HEADERS := \
 	pg_publication_namespace.h \
 	pg_publication_rel.h \
 	pg_subscription.h \
-	pg_subscription_rel.h
+	pg_subscription_rel.h \
+	pg_propgraph_element.h \
+	pg_propgraph_element_label.h \
+	pg_propgraph_label.h \
+	pg_propgraph_label_property.h \
+	pg_propgraph_property.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
 
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index f70d1daba5..de218c3e99 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -69,6 +69,11 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_propgraph_element.h',
+  'pg_propgraph_element_label.h',
+  'pg_propgraph_label.h',
+  'pg_propgraph_label_property.h',
+  'pg_propgraph_property.h',
 ]
 
 # The .dat files we need can just be listed alphabetically.
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 0fc2c093b0..729a2d7015 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -171,6 +171,7 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128);
 #define		  RELKIND_FOREIGN_TABLE   'f'	/* foreign table */
 #define		  RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
 #define		  RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
+#define		  RELKIND_PROPGRAPH		  'g'	/* property graph */
 
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4abc6d9526..6e63a1d5ac 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3825,6 +3825,9 @@
   proargtypes => 'oid oid', prosrc => 'oidge' },
 
 # System-view support functions
+{ oid => '8302', descr => 'source text of a property graph',
+  proname => 'pg_get_propgraphdef', provolatile => 's', prorettype => 'text',
+  proargtypes => 'oid', prosrc => 'pg_get_propgraphdef' },
 { oid => '1573', descr => 'source text of a rule',
   proname => 'pg_get_ruledef', provolatile => 's', prorettype => 'text',
   proargtypes => 'oid', prosrc => 'pg_get_ruledef' },
diff --git a/src/include/catalog/pg_propgraph_element.h b/src/include/catalog/pg_propgraph_element.h
new file mode 100644
index 0000000000..2bc2066b6c
--- /dev/null
+++ b/src/include/catalog/pg_propgraph_element.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_element.h
+ *	  definition of the "property graph elements" system catalog (pg_propgraph_element)
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_propgraph_element.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROPGRAPH_ELEMENT_H
+#define PG_PROPGRAPH_ELEMENT_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_propgraph_element_d.h"
+
+/* ----------------
+ *		pg_propgraph_element definition.  cpp turns this into
+ *		typedef struct FormData_pg_propgraph_element
+ * ----------------
+ */
+CATALOG(pg_propgraph_element,8299,PropgraphElementRelationId)
+{
+	Oid			oid;
+
+	/* OID of the property graph relation */
+	Oid			pgepgid BKI_LOOKUP(pg_class);
+
+	/* OID of the element table */
+	Oid			pgerelid BKI_LOOKUP(pg_class);
+
+	/* element alias */
+	NameData	pgealias;
+
+	/* vertex or edge? -- see PGEKIND_* below */
+	char		pgekind;
+
+	/* for edges: source vertex */
+	Oid			pgesrcvertexid BKI_LOOKUP_OPT(pg_propgraph_element);
+
+	/* for edges: destination vertex */
+	Oid			pgedestvertexid BKI_LOOKUP_OPT(pg_propgraph_element);
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	/* element key (column numbers in pgerelid relation) */
+	int16		pgekey[1] BKI_FORCE_NOT_NULL;
+
+	/*
+	 * for edges: source vertex key (column numbers in pgerelid relation)
+	 */
+	int16		pgesrckey[1];
+
+	/*
+	 * for edges: source vertex table referenced columns (column numbers in
+	 * relation reached via pgesrcvertexid)
+	 */
+	int16		pgesrcref[1];
+
+	/*
+	 * for edges: destination vertex key (column numbers in pgerelid relation)
+	 */
+	int16		pgedestkey[1];
+
+	/*
+	 * for edges: destination vertex table referenced columns (column numbers
+	 * in relation reached via pgedestvertexid)
+	 */
+	int16		pgedestref[1];
+#endif
+} FormData_pg_propgraph_element;
+
+/* ----------------
+ *		Form_pg_propgraph_element corresponds to a pointer to a tuple with
+ *		the format of pg_propgraph_element relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_element *Form_pg_propgraph_element;
+
+DECLARE_TOAST(pg_propgraph_element, 8315, 8316);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_element_oid_index, 8300, PropgraphElementObjectIndexId, pg_propgraph_element, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_element_alias_index, 8301, PropgraphElementAliasIndexId, pg_propgraph_element, btree(pgepgid oid_ops, pgealias name_ops));
+
+MAKE_SYSCACHE(PROPGRAPHELOID, pg_propgraph_element_oid_index, 128);
+MAKE_SYSCACHE(PROPGRAPHELALIAS, pg_propgraph_element_alias_index, 128);
+
+#ifdef EXPOSE_TO_CLIENT_CODE
+
+/*
+ * Symbolic values for pgekind column
+ */
+#define PGEKIND_VERTEX 'v'
+#define PGEKIND_EDGE 'e'
+
+#endif							/* EXPOSE_TO_CLIENT_CODE */
+
+#endif							/* PG_PROPGRAPH_ELEMENT_H */
diff --git a/src/include/catalog/pg_propgraph_element_label.h b/src/include/catalog/pg_propgraph_element_label.h
new file mode 100644
index 0000000000..91851a1927
--- /dev/null
+++ b/src/include/catalog/pg_propgraph_element_label.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_element_label.h
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_propgraph_element_label.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROPGRAPH_ELEMENT_LABEL_H
+#define PG_PROPGRAPH_ELEMENT_LABEL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_propgraph_element_label_d.h"
+
+/* ----------------
+ *		pg_propgraph_element_label definition.  cpp turns this into
+ *		typedef struct FormData_pg_propgraph_element_label
+ * ----------------
+ */
+CATALOG(pg_propgraph_element_label,8305,PropgraphElementLabelRelationId)
+{
+	Oid			oid;
+
+	/* OID of the label */
+	Oid			pgellabelid BKI_LOOKUP(pg_propgraph_label);
+
+	/* OID of the property graph element */
+	Oid			pgelelid BKI_LOOKUP(pg_propgraph_element);
+} FormData_pg_propgraph_element_label;
+
+/* ----------------
+ *		Form_pg_propgraph_element_label corresponds to a pointer to a tuple with
+ *		the format of pg_propgraph_element_label relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_element_label *Form_pg_propgraph_element_label;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_element_label_oid_index, 8312, PropgraphElementLabelObjectIndexId, pg_propgraph_element_label, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_element_label_element_label_index, 8313, PropgraphElementLabelElementLabelIndexId, pg_propgraph_element_label, btree(pgelelid oid_ops, pgellabelid oid_ops));
+DECLARE_INDEX(pg_propgraph_element_label_label_index, 8317, PropgraphElementLabelLabelIndexId, pg_propgraph_element_label, btree(pgellabelid oid_ops));
+
+MAKE_SYSCACHE(PROPGRAPHELEMENTLABELELEMENTLABEL, pg_propgraph_element_label_element_label_index, 128);
+
+#endif							/* PG_PROPGRAPH_ELEMENT_LABEL_H */
diff --git a/src/include/catalog/pg_propgraph_label.h b/src/include/catalog/pg_propgraph_label.h
new file mode 100644
index 0000000000..c6b711351b
--- /dev/null
+++ b/src/include/catalog/pg_propgraph_label.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_label.h
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_propgraph_label.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROPGRAPH_LABEL_H
+#define PG_PROPGRAPH_LABEL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_propgraph_label_d.h"
+
+/* ----------------
+ *		pg_propgraph_label definition.  cpp turns this into
+ *		typedef struct FormData_pg_propgraph_label
+ * ----------------
+ */
+CATALOG(pg_propgraph_label,8303,PropgraphLabelRelationId)
+{
+	Oid			oid;
+
+	/* OID of the property graph relation */
+	Oid			pglpgid BKI_LOOKUP(pg_class);
+
+	/* label name */
+	NameData	pgllabel;
+} FormData_pg_propgraph_label;
+
+/* ----------------
+ *		Form_pg_propgraph_label corresponds to a pointer to a tuple with
+ *		the format of pg_propgraph_label relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_label *Form_pg_propgraph_label;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_label_oid_index, 8304, PropgraphLabelObjectIndexId, pg_propgraph_label, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_label_graph_name_index, 8314, PropgraphLabelGraphNameIndexId, pg_propgraph_label, btree(pglpgid oid_ops, pgllabel name_ops));
+
+MAKE_SYSCACHE(PROPGRAPHLABELOID, pg_propgraph_label_oid_index, 128);
+MAKE_SYSCACHE(PROPGRAPHLABELNAME, pg_propgraph_label_graph_name_index, 128);
+
+#endif							/* PG_PROPGRAPH_LABEL_H */
diff --git a/src/include/catalog/pg_propgraph_label_property.h b/src/include/catalog/pg_propgraph_label_property.h
new file mode 100644
index 0000000000..a959521567
--- /dev/null
+++ b/src/include/catalog/pg_propgraph_label_property.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_label_property.h
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_propgraph_label_property.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROPGRAPH_LABEL_PROPERTY_H
+#define PG_PROPGRAPH_LABEL_PROPERTY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_propgraph_label_property_d.h"
+
+/* ----------------
+ *		pg_propgraph_label_property definition.  cpp turns this into
+ *		typedef struct FormData_pg_propgraph_label_property
+ * ----------------
+ */
+CATALOG(pg_propgraph_label_property,8318,PropgraphLabelPropertyRelationId)
+{
+	Oid			oid;
+
+	/* OID of the property */
+	Oid			plppropid BKI_LOOKUP(pg_propgraph_property);
+
+	/* OID of the element label */
+	Oid			plpellabelid BKI_LOOKUP(pg_propgraph_element_label);
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	/* property expression */
+	pg_node_tree plpexpr BKI_FORCE_NOT_NULL;
+
+#endif
+} FormData_pg_propgraph_label_property;
+
+/* ----------------
+ *		Form_pg_propgraph_label_property corresponds to a pointer to a tuple with
+ *		the format of pg_propgraph_label_property relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_label_property *Form_pg_propgraph_label_property;
+
+DECLARE_TOAST(pg_propgraph_label_property, 8319, 8320);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_label_property_oid_index, 8321, PropgraphLabelPropertyObjectIndexId, pg_propgraph_label_property, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_label_property_label_prop_index, 8322, PropgraphLabelPropertyLabelPropIndexId, pg_propgraph_label_property, btree(plpellabelid oid_ops, plppropid oid_ops));
+
+MAKE_SYSCACHE(PROPGRAPHLABELPROP, pg_propgraph_label_property_label_prop_index, 128);
+
+#endif							/* PG_PROPGRAPH_LABEL_PROPERTY_H */
diff --git a/src/include/catalog/pg_propgraph_property.h b/src/include/catalog/pg_propgraph_property.h
new file mode 100644
index 0000000000..b8921ace30
--- /dev/null
+++ b/src/include/catalog/pg_propgraph_property.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_property.h
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_propgraph_property.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROPGRAPH_PROPERTY_H
+#define PG_PROPGRAPH_PROPERTY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_propgraph_property_d.h"
+
+/* ----------------
+ *		pg_propgraph_property definition.  cpp turns this into
+ *		typedef struct FormData_pg_propgraph_property
+ * ----------------
+ */
+CATALOG(pg_propgraph_property,8306,PropgraphPropertyRelationId)
+{
+	Oid			oid;
+
+	/* OID of the property graph relation */
+	Oid			pgppgid BKI_LOOKUP(pg_class);
+
+	/* property name */
+	NameData	pgpname;
+
+	/* data type of the property */
+	Oid			pgptypid BKI_LOOKUP_OPT(pg_type);
+} FormData_pg_propgraph_property;
+
+/* ----------------
+ *		Form_pg_propgraph_property corresponds to a pointer to a tuple with
+ *		the format of pg_propgraph_property relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_property *Form_pg_propgraph_property;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_property_oid_index, 8307, PropgraphPropertyObjectIndexId, pg_propgraph_property, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_property_name_index, 8308, PropgraphPropertyNameIndexId, pg_propgraph_property, btree(pgppgid oid_ops, pgpname name_ops));
+
+MAKE_SYSCACHE(PROPGRAPHPROPOID, pg_propgraph_property_oid_index, 128);
+MAKE_SYSCACHE(PROPGRAPHPROPNAME, pg_propgraph_property_name_index, 128);
+
+#endif							/* PG_PROPGRAPH_PROPERTY_H */
diff --git a/src/include/commands/propgraphcmds.h b/src/include/commands/propgraphcmds.h
new file mode 100644
index 0000000000..2440bd4a60
--- /dev/null
+++ b/src/include/commands/propgraphcmds.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * propgraphcmds.h
+ *	  prototypes for propgraphcmds.c.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/propgraphcmds.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PROPGRAPHCMDS_H
+#define PROPGRAPHCMDS_H
+
+#include "catalog/objectaddress.h"
+#include "parser/parse_node.h"
+
+extern ObjectAddress CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt);
+extern ObjectAddress AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt);
+
+#endif							/* PROPGRAPHCMDS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 124d853e49..c591b566e2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -682,6 +682,19 @@ typedef struct RangeTableFuncCol
 	ParseLoc	location;		/* token location, or -1 if unknown */
 } RangeTableFuncCol;
 
+/*
+ * RangeGraphTable - raw form of GRAPH_TABLE clause
+ */
+typedef struct RangeGraphTable
+{
+	NodeTag		type;
+	RangeVar   *graph_name;
+	struct GraphPattern *graph_pattern;
+	List	   *columns;
+	Alias	   *alias;			/* table alias & optional column aliases */
+	ParseLoc	location;		/* token location, or -1 if unknown */
+} RangeGraphTable;
+
 /*
  * RangeTableSample - TABLESAMPLE appearing in a raw FROM clause
  *
@@ -948,6 +961,42 @@ typedef struct PartitionCmd
 	bool		concurrent;
 } PartitionCmd;
 
+/*
+ * Nodes for graph pattern
+ */
+
+typedef struct GraphPattern
+{
+	NodeTag		type;
+	List	   *path_pattern_list;
+	Node	   *whereClause;
+} GraphPattern;
+
+typedef enum GraphElementPatternKind
+{
+	VERTEX_PATTERN,
+	EDGE_PATTERN_LEFT,
+	EDGE_PATTERN_RIGHT,
+	EDGE_PATTERN_ANY,
+	PAREN_EXPR,
+} GraphElementPatternKind;
+
+#define IS_EDGE_PATTERN(kind) ((kind) == EDGE_PATTERN_ANY || \
+							   (kind) == EDGE_PATTERN_RIGHT || \
+							   (kind) == EDGE_PATTERN_LEFT)
+
+typedef struct GraphElementPattern
+{
+	NodeTag		type;
+	GraphElementPatternKind kind;
+	const char *variable;
+	Node	   *labelexpr;
+	List	   *subexpr;
+	Node	   *whereClause;
+	List	   *quantifier;
+	ParseLoc	location;
+} GraphElementPattern;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1020,6 +1069,7 @@ typedef enum RTEKind
 	RTE_VALUES,					/* VALUES (<exprlist>), (<exprlist>), ... */
 	RTE_CTE,					/* common table expr (WITH list element) */
 	RTE_NAMEDTUPLESTORE,		/* tuplestore, e.g. for AFTER triggers */
+	RTE_GRAPH_TABLE,			/* GRAPH_TABLE clause */
 	RTE_RESULT,					/* RTE represents an empty FROM clause; such
 								 * RTEs are added by the planner, they're not
 								 * present during parsing or rewriting */
@@ -1180,6 +1230,12 @@ typedef struct RangeTblEntry
 	 */
 	TableFunc  *tablefunc;
 
+	/*
+	 * Fields valid for a graph table RTE (else NULL):
+	 */
+	GraphPattern *graph_pattern;
+	List	   *graph_table_columns;
+
 	/*
 	 * Fields valid for a values RTE (else NIL):
 	 */
@@ -2275,6 +2331,7 @@ typedef enum ObjectType
 	OBJECT_PARAMETER_ACL,
 	OBJECT_POLICY,
 	OBJECT_PROCEDURE,
+	OBJECT_PROPGRAPH,
 	OBJECT_PUBLICATION,
 	OBJECT_PUBLICATION_NAMESPACE,
 	OBJECT_PUBLICATION_REL,
@@ -3999,6 +4056,88 @@ typedef struct CreateCastStmt
 	bool		inout;
 } CreateCastStmt;
 
+/* ----------------------
+ *	CREATE PROPERTY GRAPH Statement
+ * ----------------------
+ */
+typedef struct CreatePropGraphStmt
+{
+	NodeTag		type;
+	RangeVar   *pgname;
+	List	   *vertex_tables;
+	List	   *edge_tables;
+} CreatePropGraphStmt;
+
+typedef struct PropGraphVertex
+{
+	NodeTag		type;
+	RangeVar   *vtable;
+	List	   *vkey;
+	List	   *labels;
+	ParseLoc	location;
+} PropGraphVertex;
+
+typedef struct PropGraphEdge
+{
+	NodeTag		type;
+	RangeVar   *etable;
+	List	   *ekey;
+	List	   *esrckey;
+	char	   *esrcvertex;
+	List	   *esrcvertexcols;
+	List	   *edestkey;
+	char	   *edestvertex;
+	List	   *edestvertexcols;
+	List	   *labels;
+	ParseLoc	location;
+} PropGraphEdge;
+
+typedef struct PropGraphLabelAndProperties
+{
+	NodeTag		type;
+	const char *label;
+	struct PropGraphProperties *properties;
+	ParseLoc	location;
+} PropGraphLabelAndProperties;
+
+typedef struct PropGraphProperties
+{
+	NodeTag		type;
+	List	   *properties;
+	bool		all;
+	ParseLoc	location;
+} PropGraphProperties;
+
+/* ----------------------
+ *	ALTER PROPERTY GRAPH Statement
+ * ----------------------
+ */
+
+typedef enum AlterPropGraphElementKind
+{
+	PROPGRAPH_ELEMENT_KIND_VERTEX = 1,
+	PROPGRAPH_ELEMENT_KIND_EDGE = 2,
+} AlterPropGraphElementKind;
+
+typedef struct AlterPropGraphStmt
+{
+	NodeTag		type;
+	RangeVar   *pgname;
+	bool		missing_ok;
+	List	   *add_vertex_tables;
+	List	   *add_edge_tables;
+	List	   *drop_vertex_tables;
+	List	   *drop_edge_tables;
+	DropBehavior drop_behavior;
+	AlterPropGraphElementKind element_kind;
+	const char *element_alias;
+	List	   *add_labels;
+	const char *drop_label;
+	const char *alter_label;
+	PropGraphProperties *add_properties;
+	List	   *drop_properties;
+} AlterPropGraphStmt;
+
 /* ----------------------
  *	CREATE TRANSFORM Statement
  * ----------------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index ea47652adb..07d2070516 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2124,6 +2124,28 @@ typedef struct InferenceElem
 	Oid			inferopclass;	/* OID of att opclass, or InvalidOid */
 } InferenceElem;
 
+/*
+ * GraphLabelRef - label reference in label expression inside GRAPH_TABLE clause
+ */
+typedef struct GraphLabelRef
+{
+	NodeTag		type;
+	Oid			labelid;
+	ParseLoc	location;
+} GraphLabelRef;
+
+/*
+ * GraphPropertyRef - property reference inside GRAPH_TABLE clause
+ */
+typedef struct GraphPropertyRef
+{
+	Expr		xpr;
+	const char *elvarname;
+	Oid			propid;
+	Oid			typeId;
+	ParseLoc	location;
+} GraphPropertyRef;
+
 /*--------------------
  * TargetEntry -
  *	   a target entry (used in query target lists)
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index 28b66fccb4..385f7fdfa1 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -62,5 +62,8 @@ extern List *BuildOnConflictExcludedTargetlist(Relation targetrel,
 											   Index exclRelIndex);
 
 extern SortGroupClause *makeSortGroupClauseForSetOp(Oid rescoltype, bool require_hash);
+extern void constructSetOpTargetlist(SetOperationStmt *op, List *ltargetlist,
+									 List *rtargetlist, List **targetlist, const char *context,
+									 ParseState *pstate, bool recursive);
 
 #endif							/* ANALYZE_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f8659078ce..dca4e3e0ab 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -136,6 +136,7 @@ PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("depth", DEPTH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("destination", DESTINATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -147,6 +148,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("edge", EDGE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -190,6 +192,8 @@ PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("graph", GRAPH, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("graph_table", GRAPH_TABLE, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD, BARE_LABEL)
@@ -294,6 +298,7 @@ PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("no", NO, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("node", NODE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("none", NONE, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -355,6 +360,8 @@ PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("properties", PROPERTIES, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("property", PROPERTY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -368,6 +375,7 @@ PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("relationship", RELATIONSHIP, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -487,6 +495,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("vertex", VERTEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h
new file mode 100644
index 0000000000..af0f550cd2
--- /dev/null
+++ b/src/include/parser/parse_graphtable.h
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_graphtable.h
+ *		parsing of GRAPH_TABLE
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/parser/parse_graphtable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARSE_GRAPHTABLE_H
+#define PARSE_GRAPHTABLE_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+#include "parser/parse_node.h"
+
+typedef struct GraphTableParseState
+{
+	Oid			graphid;
+	List	   *variables;
+} GraphTableParseState;
+
+extern Node *graph_table_property_reference(ParseState *pstate, ColumnRef *cref, Node *var);
+
+extern Node *transformGraphPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphPattern *graph_pattern);
+
+#endif							/* PARSE_GRAPHTABLE_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5b781d87a9..3246fbe156 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -82,6 +82,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_COPY_WHERE,		/* WHERE condition in COPY FROM */
 	EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
 	EXPR_KIND_CYCLE_MARK,		/* cycle mark value */
+	EXPR_KIND_PROPGRAPH_PROPERTY,	/* derived property expression */
 } ParseExprKind;

diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index bea2da5496..c11b895b7b 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -81,6 +81,14 @@ extern ParseNamespaceItem *addRangeTableEntryForTableFunc(ParseState *pstate,
 														  Alias *alias,
 														  bool lateral,
 														  bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForGraphTable(ParseState *pstate,
+														   Oid graphid,
+														   GraphPattern *graph_pattern,
+														   List *columns,
+														   List *colnames,
+														   Alias *alias,
+														   bool lateral,
+														   bool inFromCl);
 extern ParseNamespaceItem *addRangeTableEntryForJoin(ParseState *pstate,
 													 List *colnames,
 													 ParseNamespaceColumn *nscolumns,
diff --git a/src/include/rewrite/rewriteGraphTable.h b/src/include/rewrite/rewriteGraphTable.h
new file mode 100644
index 0000000000..0c0319f5cf
--- /dev/null
+++ b/src/include/rewrite/rewriteGraphTable.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * rewriteGraphTable.h
+ *		Support for rewriting GRAPH_TABLE clauses.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/rewrite/rewriteGraphTable.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REWRITEGRAPHTABLE_H
+#define REWRITEGRAPHTABLE_H
+
+#include "nodes/parsenodes.h"
+
+extern Query *rewriteGraphTable(Query *parsetree, int rt_index);
+
+#endif							/* REWRITEGRAPHTABLE_H */
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 7fdcec6dd9..5582483d06 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -48,6 +48,7 @@ PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, fals
 PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
+PG_CMDTAG(CMDTAG_ALTER_PROPERTY_GRAPH, "ALTER PROPERTY GRAPH", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
 PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
 PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
@@ -103,6 +104,7 @@ PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, fa
 PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
+PG_CMDTAG(CMDTAG_CREATE_PROPERTY_GRAPH, "CREATE PROPERTY GRAPH", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
 PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
 PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
@@ -156,6 +158,7 @@ PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, fals
 PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
+PG_CMDTAG(CMDTAG_DROP_PROPERTY_GRAPH, "DROP PROPERTY GRAPH", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
 PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
 PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 731d84b2a9..b6813d7292 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -166,6 +166,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_LANGUAGE		(ACL_USAGE)
 #define ACL_ALL_RIGHTS_LARGEOBJECT	(ACL_SELECT|ACL_UPDATE)
 #define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM)
+#define ACL_ALL_RIGHTS_PROPGRAPH	(ACL_SELECT)
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 20446f6f83..82d0bbb267 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -206,6 +206,9 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
 
+extern char *get_propgraph_label_name(Oid labeloid);
+extern char *get_propgraph_property_name(Oid propoid);
+
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
 #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index fe8d3e5178..24659cb41e 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -67,7 +67,8 @@ my %replace_string = (
 	'EQUALS_GREATER' => '=>',
 	'LESS_EQUALS' => '<=',
 	'GREATER_EQUALS' => '>=',
-	'NOT_EQUALS' => '<>',);
+	'NOT_EQUALS' => '<>',
+	'RIGHT_ARROW' => '->',);
 
 # specific replace_types for specific non-terminals - never include the ':'
 # ECPG-only replace_types are defined in ecpg-replace_types
@@ -186,9 +187,9 @@ sub main
 
 		my $prec = 0;
 
-		# Make sure any braces are split
-		s/{/ { /g;
-		s/}/ } /g;
+		# Make sure any (unquoted) braces are split
+		s/(?<!')\{(?!')/ { /g;
+		s/(?<!')\}(?!')/ } /g;
 
 		# Any comments are split
 		s|\/\*| /* |g;
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index f363a34659..22edcf378f 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -335,6 +335,8 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+/* Note there is no need for left_arrow, since "<-" is not a single operator. */
+right_arrow		"->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -346,7 +348,7 @@ not_equals		"!="
  * If you change either set, adjust the character lists appearing in the
  * rule for "operator"!
  */
-self			[,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self			[,()\[\].;\:\|\+\-\*\/\%\^\<\>\=]
 op_chars		[\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
 operator		{op_chars}+
 
@@ -840,6 +842,10 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 					return NOT_EQUALS;
 				}
 
+{right_arrow}	{
+					return RIGHT_ARROW;
+				}
+
 {informix_special} {
 					/* are we simulating Informix? */
 					if (INFORMIX_MODE)
@@ -933,7 +939,7 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 						 * that the "self" rule would have.
 						 */
 						if (nchars == 1 &&
-							strchr(",()[].;:+-*/%^<>=", yytext[0]))
+							strchr(",()[].;:|+-*/%^<>=", yytext[0]))
 							return yytext[0];
 
 						/*
@@ -954,6 +960,8 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 								return NOT_EQUALS;
 							if (yytext[0] == '!' && yytext[1] == '=')
 								return NOT_EQUALS;
+							if (yytext[0] == '-' && yytext[1] == '>')
+								return RIGHT_ARROW;
 						}
 					}
 
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 8182ce28aa..8f6146e8dc 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -237,6 +237,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <ival>	ICONST PARAM
 %token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
 %token			LESS_EQUALS GREATER_EQUALS NOT_EQUALS
+%token			BRACKET_RIGHT_ARROW LEFT_ARROW_BRACKET MINUS_LEFT_BRACKET RIGHT_BRACKET_MINUS
 
 /*
  * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index ae54cb254f..ab9ce9e9ac 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -520,6 +520,49 @@ ERROR:  left and right associated data types for operator class options parsing
 ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) test_opclass_options_func(internal); -- Ok
 ALTER OPERATOR FAMILY alt_opf19 USING btree DROP FUNCTION 5 (int4, int4);
 DROP OPERATOR FAMILY alt_opf19 USING btree;
+--
+-- Property Graph
+--
+SET SESSION AUTHORIZATION regress_alter_generic_user1;
+CREATE PROPERTY GRAPH alt_graph1;
+CREATE PROPERTY GRAPH alt_graph2;
+CREATE PROPERTY GRAPH alt_graph3;
+ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph2; -- failed (name conflict)
+ERROR:  relation "alt_graph2" already exists
+ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph4; -- OK
+ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user2;  -- failed (no role membership)
+ERROR:  must be able to SET ROLE "regress_alter_generic_user2"
+ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user3;  -- OK
+ALTER PROPERTY GRAPH alt_graph4 SET SCHEMA alt_nsp2;  -- OK
+ALTER PROPERTY GRAPH alt_nsp2.alt_graph4 RENAME TO alt_graph2;  -- OK
+ALTER PROPERTY GRAPH alt_graph2 SET SCHEMA alt_nsp2;  -- failed (name conflict)
+ERROR:  relation "alt_graph2" already exists in schema "alt_nsp2"
+SET SESSION AUTHORIZATION regress_alter_generic_user2;
+CREATE PROPERTY GRAPH alt_graph5;
+ALTER PROPERTY GRAPH alt_graph3 RENAME TO alt_graph5;  -- failed (not owner)
+ERROR:  must be owner of property graph alt_graph3
+ALTER PROPERTY GRAPH alt_graph5 RENAME TO alt_graph6;  -- OK
+ALTER PROPERTY GRAPH alt_graph3 OWNER TO regress_alter_generic_user2;  -- failed (not owner)
+ERROR:  must be owner of property graph alt_graph3
+ALTER PROPERTY GRAPH alt_graph6 OWNER TO regress_alter_generic_user3;  -- failed (no role membership)
+ERROR:  must be able to SET ROLE "regress_alter_generic_user3"
+ALTER PROPERTY GRAPH alt_graph3 SET SCHEMA alt_nsp2;  -- failed (not owner)
+ERROR:  must be owner of property graph alt_graph3
+RESET SESSION AUTHORIZATION;
+SELECT nspname, relname, rolname
+  FROM pg_class c, pg_namespace n, pg_authid a
+  WHERE c.relnamespace = n.oid AND c.relowner = a.oid
+    AND n.nspname in ('alt_nsp1', 'alt_nsp2')
+    AND c.relkind = 'g'
+  ORDER BY nspname, relname;
+ nspname  |  relname   |           rolname           
+----------+------------+-----------------------------
+ alt_nsp1 | alt_graph2 | regress_alter_generic_user3
+ alt_nsp1 | alt_graph3 | regress_alter_generic_user1
+ alt_nsp1 | alt_graph6 | regress_alter_generic_user2
+ alt_nsp2 | alt_graph2 | regress_alter_generic_user1
+(4 rows)
+
 --
 -- Statistics
 --
@@ -710,7 +753,7 @@ NOTICE:  drop cascades to server alt_fserv3
 DROP LANGUAGE alt_lang2 CASCADE;
 DROP LANGUAGE alt_lang3 CASCADE;
 DROP SCHEMA alt_nsp1 CASCADE;
-NOTICE:  drop cascades to 28 other objects
+NOTICE:  drop cascades to 31 other objects
 DETAIL:  drop cascades to function alt_func3(integer)
 drop cascades to function alt_agg3(integer)
 drop cascades to function alt_func4(integer)
@@ -727,6 +770,9 @@ drop cascades to operator family alt_opc1 for access method hash
 drop cascades to operator family alt_opc2 for access method hash
 drop cascades to operator family alt_opf4 for access method hash
 drop cascades to operator family alt_opf2 for access method hash
+drop cascades to property graph alt_graph2
+drop cascades to property graph alt_graph3
+drop cascades to property graph alt_graph6
 drop cascades to table alt_regress_1
 drop cascades to table alt_regress_2
 drop cascades to text search dictionary alt_ts_dict3
@@ -740,12 +786,13 @@ drop cascades to text search template alt_ts_temp2
 drop cascades to text search parser alt_ts_prs3
 drop cascades to text search parser alt_ts_prs2
 DROP SCHEMA alt_nsp2 CASCADE;
-NOTICE:  drop cascades to 9 other objects
+NOTICE:  drop cascades to 10 other objects
 DETAIL:  drop cascades to function alt_nsp2.alt_func2(integer)
 drop cascades to function alt_nsp2.alt_agg2(integer)
 drop cascades to conversion alt_nsp2.alt_conv2
 drop cascades to operator alt_nsp2.@-@(integer,integer)
 drop cascades to operator family alt_nsp2.alt_opf2 for access method hash
+drop cascades to property graph alt_nsp2.alt_graph2
 drop cascades to text search dictionary alt_nsp2.alt_ts_dict2
 drop cascades to text search configuration alt_nsp2.alt_ts_conf2
 drop cascades to text search template alt_nsp2.alt_ts_temp2
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
new file mode 100644
index 0000000000..43316fbc02
--- /dev/null
+++ b/src/test/regress/expected/create_property_graph.out
@@ -0,0 +1,496 @@
+CREATE SCHEMA create_property_graph_tests;
+GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC;
+SET search_path = create_property_graph_tests;
+CREATE ROLE regress_graph_user1;
+CREATE ROLE regress_graph_user2;
+CREATE PROPERTY GRAPH g1;
+COMMENT ON PROPERTY GRAPH g1 IS 'a graph';
+CREATE PROPERTY GRAPH g1;  -- error: duplicate
+ERROR:  relation "g1" already exists
+CREATE TABLE t1 (a int, b text);
+CREATE TABLE t2 (i int PRIMARY KEY, j int, k int);
+CREATE TABLE t3 (x int, y text, z text);
+CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i));
+CREATE TABLE e2 (a int, x int, t text);
+CREATE PROPERTY GRAPH g2
+    VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2)
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+-- test dependencies/object descriptions
+DROP TABLE t1;  -- fail
+ERROR:  cannot drop table t1 because other objects depend on it
+DETAIL:  vertex t1 of property graph g2 depends on table t1
+edge e1 of property graph g2 depends on table t1
+edge e2 of property graph g2 depends on table t1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TABLE t1 DROP COLUMN b;  -- non-key column; fail
+ERROR:  cannot drop column b of table t1 because other objects depend on it
+DETAIL:  property b of label t1 of vertex t1 of property graph g2 depends on column b of table t1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TABLE t1 DROP COLUMN a;  -- key column; fail
+ERROR:  cannot drop column a of table t1 because other objects depend on it
+DETAIL:  vertex t1 of property graph g2 depends on column a of table t1
+edge e1 of property graph g2 depends on column a of table t1
+edge e2 of property graph g2 depends on column a of table t1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- like g2 but assembled with ALTER
+CREATE PROPERTY GRAPH g3;
+ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL);
+ALTER PROPERTY GRAPH g3
+    ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1)
+    ADD EDGE TABLES (
+        e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS ADD LABEL t3l3 PROPERTIES ALL COLUMNS;
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x;  -- error
+ERROR:  property graph "g3" element "t3" has no label "t3l3x"
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3;
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2);  -- fail (TODO: dubious error message)
+ERROR:  cannot drop vertex t2 of property graph g3 because other objects depend on it
+DETAIL:  edge e1 of property graph g3 depends on vertex t2 of property graph g3
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE;
+NOTICE:  drop cascades to edge e1 of property graph g3
+ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2);
+CREATE PROPERTY GRAPH g4
+    VERTEX TABLES (
+        t1 KEY (a) NO PROPERTIES,
+        t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k),
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz)
+    )
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i)
+            PROPERTIES ALL COLUMNS,
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+            PROPERTIES ALL COLUMNS
+    );
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk);
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
+CREATE TABLE t11 (a int PRIMARY KEY);
+CREATE TABLE t12 (b int PRIMARY KEY);
+CREATE TABLE t13 (
+    c int PRIMARY KEY,
+    d int REFERENCES t11,
+    e int REFERENCES t12
+);
+CREATE PROPERTY GRAPH g5
+    VERTEX TABLES (t11, t12)
+    EDGE TABLES (t13 SOURCE t11 DESTINATION t12);
+SELECT pg_get_propgraphdef('g5'::regclass);
+                                                pg_get_propgraphdef                                                
+-------------------------------------------------------------------------------------------------------------------
+ CREATE PROPERTY GRAPH create_property_graph_tests.g5                                                             +
+     VERTEX TABLES (                                                                                              +
+         t11 KEY (a) PROPERTIES (a),                                                                              +
+         t12 KEY (b) PROPERTIES (b)                                                                               +
+     )                                                                                                            +
+     EDGE TABLES (                                                                                                +
+         t13 KEY (c) SOURCE KEY (e) REFERENCES t11 (a) DESTINATION KEY (e) REFERENCES t12 (b) PROPERTIES (c, d, e)+
+     )
+(1 row)
+
+-- error cases
+CREATE PROPERTY GRAPH gx VERTEX TABLES (xx, yy);
+ERROR:  relation "xx" does not exist
+CREATE PROPERTY GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a));
+ERROR:  alias "t1" used more than once as element table
+LINE 1: ...Y GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a)...
+                                                             ^
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2
+    );
+ERROR:  source vertex "t1" of edge "e1" does not exist
+LINE 4:         e1 SOURCE t1 DESTINATION t2
+                ^
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION tx
+    );
+ERROR:  destination vertex "tx" of edge "e1" does not exist
+LINE 4:         e1 SOURCE t1 DESTINATION tx
+                ^
+COMMENT ON PROPERTY GRAPH gx IS 'not a graph';
+ERROR:  relation "gx" does not exist
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2)
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2  -- no foreign keys
+    );
+ERROR:  no SOURCE key specified and no suitable foreign key exists for definition of edge "e1"
+LINE 4:         e1 SOURCE t1 DESTINATION t2  -- no foreign keys
+                ^
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL foo PROPERTIES (a + 1 AS aa)
+                   LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+ERROR:  element "t1" property "aa" expression mismatch: (1 + a) vs. (a + 1)
+DETAIL:  In a property graph element, a property of the same name has to have the same expression in each label.
+ALTER PROPERTY GRAPH g2
+    ADD VERTEX TABLES (
+        t1 AS t1x KEY (a) LABEL foo PROPERTIES (a + 1 AS aa)
+                          LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+ERROR:  element "t1x" property "aa" expression mismatch: (1 + a) vs. (a + 1)
+DETAIL:  In a property graph element, a property of the same name has to have the same expression in each label.
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) PROPERTIES (b AS p1),
+        t2 PROPERTIES (k AS p1)  -- type mismatch
+    );
+ERROR:  property "p1" data type mismatch: text vs. integer
+DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE t1 ADD LABEL foo PROPERTIES (b AS k);  -- type mismatch
+ERROR:  property "k" data type mismatch: integer vs. text
+DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS b, k)  -- mismatching number of properties on label
+    );
+ERROR:  mismatching number of properties in definition of label "l1"
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a)  -- mismatching number of properties on label
+    );
+ERROR:  mismatching number of properties in definition of label "l1"
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS j)  -- mismatching property names on label
+    );
+ERROR:  mismatching properties names in definition of label "l1"
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS yy, b AS zz);  -- mismatching number of properties on label
+ERROR:  mismatching number of properties in definition of label "t3l1"
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS zz);  -- mismatching property names on label
+ERROR:  mismatching properties names in definition of label "t3l1"
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x);  -- mismatching number of properties on label
+ERROR:  mismatching number of properties in definition of label "t3l1"
+ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1;
+SET ROLE regress_graph_user1;
+GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
+GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
+ERROR:  invalid privilege type UPDATE for property graph
+RESET ROLE;
+-- information schema
+SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name 
+------------------------+-----------------------------+---------------------
+ regression             | create_property_graph_tests | g1
+ regression             | create_property_graph_tests | g2
+ regression             | create_property_graph_tests | g3
+ regression             | create_property_graph_tests | g4
+ regression             | create_property_graph_tests | g5
+(5 rows)
+
+SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | element_table_kind | table_catalog |        table_schema         | table_name | element_table_definition 
+------------------------+-----------------------------+---------------------+---------------------+--------------------+---------------+-----------------------------+------------+--------------------------
+ regression             | create_property_graph_tests | g2                  | e1                  | EDGE               | regression    | create_property_graph_tests | e1         | 
+ regression             | create_property_graph_tests | g2                  | e2                  | EDGE               | regression    | create_property_graph_tests | e2         | 
+ regression             | create_property_graph_tests | g2                  | t1                  | VERTEX             | regression    | create_property_graph_tests | t1         | 
+ regression             | create_property_graph_tests | g2                  | t2                  | VERTEX             | regression    | create_property_graph_tests | t2         | 
+ regression             | create_property_graph_tests | g2                  | t3                  | VERTEX             | regression    | create_property_graph_tests | t3         | 
+ regression             | create_property_graph_tests | g3                  | t1                  | VERTEX             | regression    | create_property_graph_tests | t1         | 
+ regression             | create_property_graph_tests | g3                  | t3                  | VERTEX             | regression    | create_property_graph_tests | t3         | 
+ regression             | create_property_graph_tests | g4                  | e1                  | EDGE               | regression    | create_property_graph_tests | e1         | 
+ regression             | create_property_graph_tests | g4                  | e2                  | EDGE               | regression    | create_property_graph_tests | e2         | 
+ regression             | create_property_graph_tests | g4                  | t1                  | VERTEX             | regression    | create_property_graph_tests | t1         | 
+ regression             | create_property_graph_tests | g4                  | t2                  | VERTEX             | regression    | create_property_graph_tests | t2         | 
+ regression             | create_property_graph_tests | g4                  | t3                  | VERTEX             | regression    | create_property_graph_tests | t3         | 
+ regression             | create_property_graph_tests | g5                  | t11                 | VERTEX             | regression    | create_property_graph_tests | t11        | 
+ regression             | create_property_graph_tests | g5                  | t12                 | VERTEX             | regression    | create_property_graph_tests | t12        | 
+ regression             | create_property_graph_tests | g5                  | t13                 | EDGE               | regression    | create_property_graph_tests | t13        | 
+(15 rows)
+
+SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | column_name | ordinal_position 
+------------------------+-----------------------------+---------------------+---------------------+-------------+------------------
+ regression             | create_property_graph_tests | g2                  | e1                  | a           |                1
+ regression             | create_property_graph_tests | g2                  | e1                  | i           |                2
+ regression             | create_property_graph_tests | g2                  | e2                  | a           |                1
+ regression             | create_property_graph_tests | g2                  | e2                  | x           |                2
+ regression             | create_property_graph_tests | g2                  | t1                  | a           |                1
+ regression             | create_property_graph_tests | g2                  | t2                  | i           |                1
+ regression             | create_property_graph_tests | g2                  | t3                  | x           |                1
+ regression             | create_property_graph_tests | g3                  | t1                  | a           |                1
+ regression             | create_property_graph_tests | g3                  | t3                  | x           |                1
+ regression             | create_property_graph_tests | g4                  | e1                  | a           |                1
+ regression             | create_property_graph_tests | g4                  | e1                  | i           |                2
+ regression             | create_property_graph_tests | g4                  | e2                  | a           |                1
+ regression             | create_property_graph_tests | g4                  | e2                  | x           |                2
+ regression             | create_property_graph_tests | g4                  | t1                  | a           |                1
+ regression             | create_property_graph_tests | g4                  | t2                  | i           |                1
+ regression             | create_property_graph_tests | g4                  | t3                  | x           |                1
+ regression             | create_property_graph_tests | g5                  | t11                 | a           |                1
+ regression             | create_property_graph_tests | g5                  | t12                 | b           |                1
+ regression             | create_property_graph_tests | g5                  | t13                 | c           |                1
+(19 rows)
+
+SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | edge_table_alias | vertex_table_alias |  edge_end   | edge_table_column_name | vertex_table_column_name | ordinal_position 
+------------------------+-----------------------------+---------------------+------------------+--------------------+-------------+------------------------+--------------------------+------------------
+ regression             | create_property_graph_tests | g2                  | e1               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g2                  | e1               | t2                 | DESTINATION | i                      | i                        |                1
+ regression             | create_property_graph_tests | g2                  | e2               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g2                  | e2               | t3                 | DESTINATION | x                      | x                        |                1
+ regression             | create_property_graph_tests | g2                  | e2               | t3                 | DESTINATION | t                      | y                        |                2
+ regression             | create_property_graph_tests | g4                  | e1               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g4                  | e1               | t2                 | DESTINATION | i                      | i                        |                1
+ regression             | create_property_graph_tests | g4                  | e2               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g4                  | e2               | t3                 | DESTINATION | x                      | x                        |                1
+ regression             | create_property_graph_tests | g4                  | e2               | t3                 | DESTINATION | t                      | y                        |                2
+ regression             | create_property_graph_tests | g5                  | t13              | t11                | SOURCE      | e                      | a                        |                1
+ regression             | create_property_graph_tests | g5                  | t13              | t12                | DESTINATION | e                      | b                        |                1
+(12 rows)
+
+SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | label_name 
+------------------------+-----------------------------+---------------------+---------------------+------------
+ regression             | create_property_graph_tests | g2                  | e1                  | e1
+ regression             | create_property_graph_tests | g2                  | e2                  | e2
+ regression             | create_property_graph_tests | g2                  | t1                  | t1
+ regression             | create_property_graph_tests | g2                  | t2                  | t2
+ regression             | create_property_graph_tests | g2                  | t3                  | t3l1
+ regression             | create_property_graph_tests | g2                  | t3                  | t3l2
+ regression             | create_property_graph_tests | g3                  | t1                  | t1
+ regression             | create_property_graph_tests | g3                  | t3                  | t3l1
+ regression             | create_property_graph_tests | g3                  | t3                  | t3l2
+ regression             | create_property_graph_tests | g4                  | e1                  | e1
+ regression             | create_property_graph_tests | g4                  | e2                  | e2
+ regression             | create_property_graph_tests | g4                  | t1                  | t1
+ regression             | create_property_graph_tests | g4                  | t2                  | t2
+ regression             | create_property_graph_tests | g4                  | t3                  | t3l1
+ regression             | create_property_graph_tests | g4                  | t3                  | t3l2
+ regression             | create_property_graph_tests | g5                  | t11                 | t11
+ regression             | create_property_graph_tests | g5                  | t12                 | t12
+ regression             | create_property_graph_tests | g5                  | t13                 | t13
+(18 rows)
+
+SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | property_name | property_expression 
+------------------------+-----------------------------+---------------------+---------------------+---------------+---------------------
+ regression             | create_property_graph_tests | g2                  | e1                  | a             | a
+ regression             | create_property_graph_tests | g2                  | e1                  | i             | i
+ regression             | create_property_graph_tests | g2                  | e1                  | t             | t
+ regression             | create_property_graph_tests | g2                  | e2                  | a             | a
+ regression             | create_property_graph_tests | g2                  | e2                  | t             | t
+ regression             | create_property_graph_tests | g2                  | e2                  | x             | x
+ regression             | create_property_graph_tests | g2                  | t1                  | a             | a
+ regression             | create_property_graph_tests | g2                  | t1                  | b             | b
+ regression             | create_property_graph_tests | g2                  | t2                  | i             | i
+ regression             | create_property_graph_tests | g2                  | t2                  | j             | j
+ regression             | create_property_graph_tests | g2                  | t2                  | k             | k
+ regression             | create_property_graph_tests | g2                  | t3                  | x             | x
+ regression             | create_property_graph_tests | g2                  | t3                  | y             | y
+ regression             | create_property_graph_tests | g2                  | t3                  | z             | z
+ regression             | create_property_graph_tests | g3                  | t1                  | a             | a
+ regression             | create_property_graph_tests | g3                  | t1                  | b             | b
+ regression             | create_property_graph_tests | g3                  | t3                  | x             | x
+ regression             | create_property_graph_tests | g3                  | t3                  | y             | y
+ regression             | create_property_graph_tests | g3                  | t3                  | z             | z
+ regression             | create_property_graph_tests | g4                  | e1                  | a             | a
+ regression             | create_property_graph_tests | g4                  | e1                  | i             | i
+ regression             | create_property_graph_tests | g4                  | e1                  | t             | t
+ regression             | create_property_graph_tests | g4                  | e2                  | a             | a
+ regression             | create_property_graph_tests | g4                  | e2                  | t             | t
+ regression             | create_property_graph_tests | g4                  | e2                  | x             | x
+ regression             | create_property_graph_tests | g4                  | t2                  | i_j           | (i + j)
+ regression             | create_property_graph_tests | g4                  | t2                  | kk            | (k * 2)
+ regression             | create_property_graph_tests | g4                  | t3                  | x             | x
+ regression             | create_property_graph_tests | g4                  | t3                  | yy            | y
+ regression             | create_property_graph_tests | g4                  | t3                  | zz            | z
+ regression             | create_property_graph_tests | g5                  | t11                 | a             | a
+ regression             | create_property_graph_tests | g5                  | t12                 | b             | b
+ regression             | create_property_graph_tests | g5                  | t13                 | c             | c
+ regression             | create_property_graph_tests | g5                  | t13                 | d             | d
+ regression             | create_property_graph_tests | g5                  | t13                 | e             | e
+(35 rows)
+
+SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | label_name | property_name 
+------------------------+-----------------------------+---------------------+------------+---------------
+ regression             | create_property_graph_tests | g2                  | e1         | a
+ regression             | create_property_graph_tests | g2                  | e1         | i
+ regression             | create_property_graph_tests | g2                  | e1         | t
+ regression             | create_property_graph_tests | g2                  | e2         | a
+ regression             | create_property_graph_tests | g2                  | e2         | t
+ regression             | create_property_graph_tests | g2                  | e2         | x
+ regression             | create_property_graph_tests | g2                  | t1         | a
+ regression             | create_property_graph_tests | g2                  | t1         | b
+ regression             | create_property_graph_tests | g2                  | t2         | i
+ regression             | create_property_graph_tests | g2                  | t2         | j
+ regression             | create_property_graph_tests | g2                  | t2         | k
+ regression             | create_property_graph_tests | g2                  | t3l1       | x
+ regression             | create_property_graph_tests | g2                  | t3l1       | y
+ regression             | create_property_graph_tests | g2                  | t3l1       | z
+ regression             | create_property_graph_tests | g2                  | t3l2       | x
+ regression             | create_property_graph_tests | g2                  | t3l2       | y
+ regression             | create_property_graph_tests | g2                  | t3l2       | z
+ regression             | create_property_graph_tests | g3                  | t1         | a
+ regression             | create_property_graph_tests | g3                  | t1         | b
+ regression             | create_property_graph_tests | g3                  | t3l1       | x
+ regression             | create_property_graph_tests | g3                  | t3l1       | y
+ regression             | create_property_graph_tests | g3                  | t3l1       | z
+ regression             | create_property_graph_tests | g3                  | t3l2       | x
+ regression             | create_property_graph_tests | g3                  | t3l2       | y
+ regression             | create_property_graph_tests | g3                  | t3l2       | z
+ regression             | create_property_graph_tests | g4                  | e1         | a
+ regression             | create_property_graph_tests | g4                  | e1         | i
+ regression             | create_property_graph_tests | g4                  | e1         | t
+ regression             | create_property_graph_tests | g4                  | e2         | a
+ regression             | create_property_graph_tests | g4                  | e2         | t
+ regression             | create_property_graph_tests | g4                  | e2         | x
+ regression             | create_property_graph_tests | g4                  | t2         | i_j
+ regression             | create_property_graph_tests | g4                  | t2         | kk
+ regression             | create_property_graph_tests | g4                  | t3l1       | x
+ regression             | create_property_graph_tests | g4                  | t3l1       | yy
+ regression             | create_property_graph_tests | g4                  | t3l2       | x
+ regression             | create_property_graph_tests | g4                  | t3l2       | zz
+ regression             | create_property_graph_tests | g5                  | t11        | a
+ regression             | create_property_graph_tests | g5                  | t12        | b
+ regression             | create_property_graph_tests | g5                  | t13        | c
+ regression             | create_property_graph_tests | g5                  | t13        | d
+ regression             | create_property_graph_tests | g5                  | t13        | e
+(42 rows)
+
+SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | label_name 
+------------------------+-----------------------------+---------------------+------------
+ regression             | create_property_graph_tests | g2                  | e1
+ regression             | create_property_graph_tests | g2                  | e2
+ regression             | create_property_graph_tests | g2                  | t1
+ regression             | create_property_graph_tests | g2                  | t2
+ regression             | create_property_graph_tests | g2                  | t3l1
+ regression             | create_property_graph_tests | g2                  | t3l2
+ regression             | create_property_graph_tests | g3                  | t1
+ regression             | create_property_graph_tests | g3                  | t3l1
+ regression             | create_property_graph_tests | g3                  | t3l2
+ regression             | create_property_graph_tests | g4                  | e1
+ regression             | create_property_graph_tests | g4                  | e2
+ regression             | create_property_graph_tests | g4                  | t1
+ regression             | create_property_graph_tests | g4                  | t2
+ regression             | create_property_graph_tests | g4                  | t3l1
+ regression             | create_property_graph_tests | g4                  | t3l2
+ regression             | create_property_graph_tests | g5                  | t11
+ regression             | create_property_graph_tests | g5                  | t12
+ regression             | create_property_graph_tests | g5                  | t13
+(18 rows)
+
+SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | property_name | data_type | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier 
+------------------------+-----------------------------+---------------------+---------------+-----------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+---------------------------+--------------------------+------------------------+---------------+--------------+------------+---------------------+----------------
+ regression             | create_property_graph_tests | g2                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g2                  | b             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g2                  | i             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
+ regression             | create_property_graph_tests | g2                  | j             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | j
+ regression             | create_property_graph_tests | g2                  | k             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | k
+ regression             | create_property_graph_tests | g2                  | t             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
+ regression             | create_property_graph_tests | g2                  | x             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g2                  | y             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
+ regression             | create_property_graph_tests | g2                  | z             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
+ regression             | create_property_graph_tests | g3                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g3                  | b             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g3                  | x             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g3                  | y             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
+ regression             | create_property_graph_tests | g3                  | z             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
+ regression             | create_property_graph_tests | g4                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g4                  | i             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
+ regression             | create_property_graph_tests | g4                  | i_j           | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i_j
+ regression             | create_property_graph_tests | g4                  | kk            | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | kk
+ regression             | create_property_graph_tests | g4                  | t             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
+ regression             | create_property_graph_tests | g4                  | x             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g4                  | yy            | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | yy
+ regression             | create_property_graph_tests | g4                  | zz            | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | zz
+ regression             | create_property_graph_tests | g5                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g5                  | b             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g5                  | c             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | c
+ regression             | create_property_graph_tests | g5                  | d             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | d
+ regression             | create_property_graph_tests | g5                  | e             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | e
+(27 rows)
+
+SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name;
+       grantor       |       grantee       | property_graph_catalog |    property_graph_schema    | property_graph_name | privilege_type | is_grantable 
+---------------------+---------------------+------------------------+-----------------------------+---------------------+----------------+--------------
+ regress_graph_user1 | regress_graph_user1 | regression             | create_property_graph_tests | g1                  | SELECT         | YES
+ regress_graph_user1 | regress_graph_user2 | regression             | create_property_graph_tests | g1                  | SELECT         | NO
+(2 rows)
+
+\a\t
+SELECT pg_get_propgraphdef('g2'::regclass);
+CREATE PROPERTY GRAPH create_property_graph_tests.g2
+    VERTEX TABLES (
+        t1 KEY (a) PROPERTIES (a, b),
+        t2 KEY (i) PROPERTIES (i, j, k),
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y, z) LABEL t3l2 PROPERTIES (x, y, z)
+    )
+    EDGE TABLES (
+        e1 KEY (a, i) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i) PROPERTIES (a, i, t),
+        e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) PROPERTIES (a, t, x)
+    )
+SELECT pg_get_propgraphdef('g3'::regclass);
+CREATE PROPERTY GRAPH create_property_graph_tests.g3
+    VERTEX TABLES (
+        t1 KEY (a) PROPERTIES (a, b),
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y, z) LABEL t3l2 PROPERTIES (x, y, z)
+    )
+SELECT pg_get_propgraphdef('g4'::regclass);
+CREATE PROPERTY GRAPH create_property_graph_tests.g4
+    VERTEX TABLES (
+        t1 KEY (a) NO PROPERTIES,
+        t2 KEY (i) PROPERTIES ((i + j) AS i_j, (k * 2) AS kk),
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz)
+    )
+    EDGE TABLES (
+        e1 KEY (a, i) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i) PROPERTIES (a, i, t),
+        e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) PROPERTIES (a, t, x)
+    )
+SELECT pg_get_propgraphdef('pg_type'::regclass);  -- error
+ERROR:  "pg_type" is not a property graph
+\a\t
+\dG g1
+                             List of relations
+           Schema            | Name |      Type      |        Owner        
+-----------------------------+------+----------------+---------------------
+ create_property_graph_tests | g1   | property graph | regress_graph_user1
+(1 row)
+
+-- TODO
+\d g1
+Property graph "create_property_graph_tests.g1"
+ Column | Type 
+--------+------
+
+\d+ g1
+Property graph "create_property_graph_tests.g1"
+ Column | Type | Storage 
+--------+------+---------
+Property graph definition:
+ CREATE PROPERTY GRAPH create_property_graph_tests.g1
+
+DROP TABLE g2;  -- error: wrong object type
+ERROR:  "g2" is not a table
+HINT:  Use DROP PROPERTY GRAPH to remove a property graph.
+DROP PROPERTY GRAPH g1;
+DROP PROPERTY GRAPH g1;  -- error: does not exist
+ERROR:  property graph "g1" does not exist
+DROP PROPERTY GRAPH IF EXISTS g1;
+NOTICE:  property graph "g1" does not exist, skipping
+-- leave for pg_upgrade/pg_dump tests
+--DROP SCHEMA create_property_graph_tests CASCADE;
+DROP ROLE regress_graph_user1, regress_graph_user2;
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
new file mode 100644
index 0000000000..813fc9aa25
--- /dev/null
+++ b/src/test/regress/expected/graph_table.out
@@ -0,0 +1,484 @@
+CREATE SCHEMA graph_table_tests;
+GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC;
+SET search_path = graph_table_tests;
+CREATE TABLE products (
+    product_no integer PRIMARY KEY,
+    name varchar,
+    price numeric
+);
+CREATE TABLE customers (
+    customer_id integer PRIMARY KEY,
+    name varchar,
+    address varchar
+);
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    ordered_when date
+);
+CREATE TABLE order_items (
+    order_items_id integer PRIMARY KEY,
+    order_id integer REFERENCES orders (order_id),
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+CREATE TABLE customer_orders (
+    customer_orders_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    order_id integer REFERENCES orders (order_id)
+);
+CREATE TABLE wishlists (
+    wishlist_id integer PRIMARY KEY,
+    wishlist_name varchar
+);
+CREATE TABLE wishlist_items (
+    wishlist_items_id integer PRIMARY KEY,
+    wishlist_id integer REFERENCES wishlists (wishlist_id),
+    product_no integer REFERENCES products (product_no)
+);
+CREATE TABLE customer_wishlists (
+    customer_wishlist_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    wishlist_id integer REFERENCES wishlists (wishlist_id)
+);
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products,
+        customers,
+        orders
+           DEFAULT LABEL
+            LABEL lists PROPERTIES (order_id as node_id, 'order'::varchar(10) as list_type),
+        wishlists
+           DEFAULT LABEL
+            LABEL lists PROPERTIES (wishlist_id as node_id, 'wishlist'::varchar(10) as list_type)
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (order_id as link_id, product_no),
+        wishlist_items KEY (wishlist_items_id)
+            SOURCE KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (wishlist_id as link_id, product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, order_id as link_id),
+        customer_wishlists KEY (customer_wishlist_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, wishlist_id as link_id)
+    );
+SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  relation "xxx" does not exist
+LINE 1: SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS custo...
+                                               ^
+SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  "pg_class" is not a property graph
+LINE 1: SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS ...
+                                               ^
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name));  -- error
+ERROR:  missing FROM-clause entry for table "cx"
+LINE 1: ...US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS...
+                                                             ^
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name));  -- error
+ERROR:  property "namex" does not exist
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers|employees WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  label "employees" does not exist in property graph "myshop"
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c.name AS customer_name));  -- error
+ERROR:  syntax error at or near "COLUMNS"
+LINE 1: ...mers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c...
+                                                             ^
+INSERT INTO products VALUES
+    (1, 'product1', 10),
+    (2, 'product2', 20),
+    (3, 'product3', 30);
+INSERT INTO customers VALUES
+    (1, 'customer1', 'US'),
+    (2, 'customer2', 'CA'),
+    (3, 'customer3', 'GL');
+INSERT INTO orders VALUES
+    (1, date '2024-01-01'),
+    (2, date '2024-01-02'),
+    (3, date '2024-01-03');
+INSERT INTO wishlists VALUES
+    (1, 'wishlist1'),
+    (2, 'wishlist2'),
+    (3, 'wishlist3');
+INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES
+    (1, 1, 1, 5),
+    (2, 1, 2, 10),
+    (3, 2, 1, 7);
+INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES
+    (1, 1, 1),
+    (2, 2, 2);
+INSERT INTO customer_wishlists (customer_wishlist_id, customer_id, wishlist_id) VALUES
+    (1, 2, 3),
+    (2, 3, 1),
+    (3, 3, 2);
+INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES
+    (1, 1, 2),
+    (2, 1, 3),
+    (3, 2, 1),
+    (4, 3, 1);
+-- single element path pattern
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
+   name    
+-----------
+ customer1
+ customer2
+ customer3
+(3 rows)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
+   name    
+-----------
+ customer1
+(1 row)
+
+-- graph element specification without label or variable
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[]->(o IS orders) COLUMNS (c.name AS customer_name));
+ customer_name 
+---------------
+ customer1
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c:customers)-[co:customer_orders]->(o:orders WHERE o.ordered_when = date '2024-01-02') COLUMNS (c.name, c.address));
+   name    | address 
+-----------+---------
+ customer2 | CA
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when));
+ name | ordered_when 
+------+--------------
+(0 rows)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when));
+   name    | ordered_when 
+-----------+--------------
+ customer1 | 01-01-2024
+ customer2 | 01-02-2024
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH ( o IS orders ) <- [ IS customer_orders ] - (c IS customers) COLUMNS ( c.name, o.ordered_when));
+   name    | ordered_when 
+-----------+--------------
+ customer1 | 01-01-2024
+ customer2 | 01-02-2024
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS cust_lists]->(l IS lists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY customer_name, product_name, list_type;
+ customer_name | product_name | list_type 
+---------------+--------------+-----------
+ customer1     | product1     | order
+ customer1     | product2     | order
+ customer2     | product1     | order
+ customer2     | product1     | wishlist
+ customer3     | product1     | wishlist
+ customer3     | product2     | wishlist
+ customer3     | product3     | wishlist
+(7 rows)
+
+-- label disjunction
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name)) ORDER BY customer_name, product_name;
+ customer_name | product_name 
+---------------+--------------
+ customer1     | product1
+ customer1     | product2
+ customer2     | product1
+ customer2     | product1
+ customer3     | product1
+ customer3     | product2
+ customer3     | product3
+(7 rows)
+
+-- property not associated with labels queried results in error
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY 1, 2, 3;
+ERROR:  property "list_type" of element variable "l" not found
+-- vertex to vertex connection abbreviation
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1;
+   name    | ordered_when 
+-----------+--------------
+ customer1 | 01-01-2024
+ customer2 | 01-02-2024
+(2 rows)
+
+-- lateral test
+CREATE TABLE x1 (a int, b text);
+INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
+ a |  b  | customer_name | cid 
+---+-----+---------------+-----
+ 1 | one | customer1     |   1
+(1 row)
+
+DROP TABLE x1;
+create table v1 (id int primary key,
+					vname varchar(10),
+					vprop1 int,
+					vprop2 int);
+create table v2 (id1 int,
+					id2 int,
+					vname varchar(10),
+					vprop1 int,
+					vprop2 int);
+create table v3 (id int primary key,
+					vname varchar(10),
+					vprop1 int,
+					vprop2 int);
+-- edge connecting v1 and v2
+create table e1_2 (id_1 int,
+					id_2_1 int,
+					id_2_2 int,
+					ename varchar(10),
+					eprop1 int);
+-- edge connecting v1 and v3
+create table e1_3 (id_1 int,
+					id_3 int,
+					ename varchar(10),
+					eprop1 int,
+					primary key (id_1, id_3));
+create table e2_3 (id_2_1 int,
+                    id_2_2 int,
+                    id_3 int,
+                    ename varchar(10),
+                    eprop1 int);
+create property graph g1
+vertex tables (
+	v1
+        label vl1 properties (vname, vprop1)
+        label l1 properties (vname as elname), -- label shared by vertexes as well as edges
+	v2 key (id1, id2)
+		label vl2 properties (vname, vprop2, 'vl2_prop'::varchar(10) as lprop1)
+        label vl3 properties (vname, vprop1, 'vl2_prop'::varchar(10) as lprop1)
+        label l1 properties (vname as elname),
+	v3
+		label vl3 properties (vname, vprop1, 'vl3_prop'::varchar(10) as lprop1)
+        label l1 properties (vname as elname)
+)
+-- edges with differing number of columns in destination keys
+edge tables (
+	e1_2 key (id_1, id_2_1, id_2_2)
+		source key (id_1) references v1 (id)
+		destination key (id_2_1, id_2_2) references v2 (id1, id2)
+		label el1 properties (eprop1, ename)
+        label l1 properties (ename as elname),
+	e1_3
+		source key (id_1) references v1 (id)
+		destination key (id_3) references v3 (id)
+		-- order of property names doesn't matter
+		label el1 properties (ename, eprop1)
+        label l1 properties (ename as elname),
+    e2_3 key (id_2_1, id_2_2, id_3)
+        source key (id_2_1, id_2_2) references v2 (id1, id2)
+        destination key (id_3) references v3 (id)
+        -- new property lprop2 not shared by el1
+        -- does not share eprop1 from by el1
+        label el2 properties (ename, eprop1 * 10 as lprop2)
+        label l1 properties (ename as elname)
+);
+insert into v1 values (1, 'v11', 10, 100),
+                      (2, 'v12', 20, 200),
+                      (3, 'v13', 30, 300);
+insert into v2 values (1000, 1, 'v21', 1010, 1100),
+                      (1000, 2, 'v22', 1020, 1200),
+                      (1000, 3, 'v23', 1030, 1300);
+insert into v3 values (2001, 'v31', 2010, 2100),
+                      (2002, 'v32', 2020, 2200),
+                      (2003, 'v33', 2030, 2300);
+insert into e1_2 values (1, 1000, 2, 'e121', 10001),
+                        (2, 1000, 1, 'e122', 10002);
+insert into e1_3 values (1, 2003, 'e131', 10003),
+                        (1, 2001, 'e132', 10004);
+insert into e2_3 values (1000, 2, 2002, 'e231', 10005);
+-- empty element path pattern, counts number of edges in the graph
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 as one));
+ count 
+-------
+     5
+(1 row)
+
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()->() COLUMNS (1 as one));
+ count 
+-------
+     5
+(1 row)
+
+-- Vertex element v2 has label vl3 which exposes property vprop1. But vl3 is
+-- not part of label expression. Instead v2 get bound through label vl2 which
+-- does not expose vprop1. The GRAPH_TABLE clause project vprop1.
+--
+-- TODO: This case fails since catalogs do not associated properties with
+-- elements directly. More code is needed to make it work.
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname,
+a.vprop1));
+ vname | vprop1 
+-------+--------
+ v11   |     10
+ v12   |     20
+ v13   |     30
+ v21   |   1010
+ v22   |   1020
+ v23   |   1030
+(6 rows)
+
+-- vprop2 is associated with vl2 but not vl3
+select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, b.ename as conn, c.vname as dest, c.lprop1, c.vprop2, c.vprop1));
+ src | conn | dest |  lprop1  | vprop2 | vprop1 
+-----+------+------+----------+--------+--------
+ v12 | e122 | v21  | vl2_prop |   1100 |   1010
+ v11 | e121 | v22  | vl2_prop |   1200 |   1020
+ v11 | e131 | v33  | vl3_prop |        |   2030
+ v11 | e132 | v31  | vl3_prop |        |   2010
+(4 rows)
+
+-- Errors
+-- vl1 is not associated with property vprop2
+select src, src_vprop2, conn, dest from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, a.vprop2 as src_vprop2, b.ename as conn, c.vname as dest));
+ERROR:  property "vprop2" of element variable "a" not found
+-- property ename is associated with edge labels but not with a vertex label
+select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, src.ename as sename));
+ERROR:  property "ename" of element variable "src" not found
+-- vname is associated vertex labels but not an edge label
+select * from graph_table (g1 match (src)-[conn]->(dest) columns (conn.vname as cvname, conn.ename as cename));
+ERROR:  property "vname" of element variable "conn" not found
+-- el1 is associated with edges but is only label used to qualify vertex
+select * from graph_table (g1 match (src is el1)-[conn]->(dest) columns (conn.ename as cename));
+ERROR:  can not find label "el1" in property graph "g1" for element type "vertex"
+-- el1 is associated with edges but is one of the labels used to qualify vertex
+select * from graph_table (g1 match (src is el1 | vl1)-[conn]->(dest) columns (conn.ename as cename));
+ERROR:  can not find label "el1" in property graph "g1" for element type "vertex"
+-- select all the properties across all the labels associated with a given type
+-- of graph element
+select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname, src.vprop1 as svp1, src.vprop2 as svp2, src.lprop1 as slp1, dest.vprop1 as dvp1, dest.vprop2 as dvp2, dest.lprop1 as dlp1, conn.eprop1 as cep1, conn.lprop2 as clp2));
+ svname | cename | dvname | svp1 | svp2 |   slp1   | dvp1 | dvp2 |   dlp1   | cep1  |  clp2  
+--------+--------+--------+------+------+----------+------+------+----------+-------+--------
+ v12    | e122   | v21    |   20 |      |          | 1010 | 1100 | vl2_prop | 10002 |       
+ v11    | e121   | v22    |   10 |      |          | 1020 | 1200 | vl2_prop | 10001 |       
+ v11    | e131   | v33    |   10 |      |          | 2030 |      | vl3_prop | 10003 |       
+ v11    | e132   | v31    |   10 |      |          | 2010 |      | vl3_prop | 10004 |       
+ v22    | e231   | v32    | 1020 | 1200 | vl2_prop | 2020 |      | vl3_prop |       | 100050
+(5 rows)
+
+-- three label disjunction
+select * from graph_table (g1 match (src IS vl1 | vl2 | vl3)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname));
+ svname | cename | dvname 
+--------+--------+--------
+ v12    | e122   | v21
+ v11    | e121   | v22
+ v11    | e131   | v33
+ v11    | e132   | v31
+ v22    | e231   | v32
+(5 rows)
+
+-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination.
+with all_connected_vertices as (select svn, dvn from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svn, dest.vname as dvn))),
+    all_vertices as (select vn from graph_table (g1 match (vertex) columns (vertex.vname as vn)))
+select vn from all_vertices except (select svn from all_connected_vertices union select dvn from all_connected_vertices);
+ vn  
+-----
+ v13
+ v23
+(2 rows)
+
+-- query all connections using a label shared by vertices and edges
+select sn, cn, dn from graph_table (g1 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn));
+ sn  |  cn  | dn  
+-----+------+-----
+ v12 | e122 | v21
+ v11 | e121 | v22
+ v11 | e131 | v33
+ v11 | e132 | v31
+ v22 | e231 | v32
+(5 rows)
+
+-- property graph with some of the elements, labels and properties same as the
+-- previous one. Test whether components from the specified property graph are
+-- used.
+create property graph g2
+vertex tables (
+	v1
+        label l1 properties ('g2.' || vname as elname),
+	v2 key (id1, id2)
+        label l1 properties ('g2.' || vname as elname),
+	v3
+        label l1 properties ('g2.' || vname as elname)
+)
+edge tables (
+	e1_2 key (id_1, id_2_1, id_2_2)
+		source key (id_1) references v1 (id)
+		destination key (id_2_1, id_2_2) references v2 (id1, id2)
+        label l1 properties ('g2.' || ename as elname),
+	e1_3
+		source key (id_1) references v1 (id)
+		destination key (id_3) references v3 (id)
+        label l1 properties ('g2.' || ename as elname),
+    e2_3 key (id_2_1, id_2_2, id_3)
+        source key (id_2_1, id_2_2) references v2 (id1, id2)
+        destination key (id_3) references v3 (id)
+        label l1 properties ('g2.' || ename as elname)
+);
+select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn));
+   sn   |   cn    |   dn   
+--------+---------+--------
+ g2.v12 | g2.e122 | g2.v21
+ g2.v11 | g2.e121 | g2.v22
+ g2.v11 | g2.e131 | g2.v33
+ g2.v11 | g2.e132 | g2.v31
+ g2.v22 | g2.e231 | g2.v32
+(5 rows)
+
+CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));
+SELECT pg_get_viewdef('customers_us'::regclass);
+                                                                          pg_get_viewdef                                                                           
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  SELECT customer_name                                                                                                                                            +
+    FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE ((c.address)::text = 'US'::text))-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));
+(1 row)
+
+-- test view/graph nesting
+CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
+SELECT * FROM customers;
+ customer_id |   name    | address 
+-------------+-----------+---------
+           1 | customer1 | US
+           2 | customer2 | CA
+           3 | customer3 | GL
+(3 rows)
+
+SELECT * FROM customers_view;
+ customer_id | name_redacted | address 
+-------------+---------------+---------
+           1 | redacted1     | US
+           2 | redacted2     | CA
+           3 | redacted3     | GL
+(3 rows)
+
+CREATE PROPERTY GRAPH myshop2
+    VERTEX TABLES (
+        products,
+        customers_view KEY (customer_id) LABEL customers,
+        orders
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers_view (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+    );
+CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE (myshop2 MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name_redacted AS customer_name_redacted));
+SELECT * FROM customers_us_redacted;
+ customer_name_redacted 
+------------------------
+ redacted1
+(1 row)
+
+-- leave for pg_upgrade/pg_dump tests
+--DROP SCHEMA graph_table_tests CASCADE;
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index fc42d418bf..432ba471fe 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -34,6 +34,7 @@ CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END
 CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig();
 CREATE POLICY genpol ON addr_nsp.gentable;
 CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$;
+CREATE PROPERTY GRAPH addr_nsp.gengraph;
 CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
@@ -98,7 +99,7 @@ DECLARE
 BEGIN
     FOR objtype IN VALUES
         ('table'), ('index'), ('sequence'), ('view'),
-        ('materialized view'), ('foreign table'),
+        ('materialized view'), ('foreign table'), ('property graph'),
         ('table column'), ('foreign table column'),
         ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'),
         ('table constraint'), ('domain constraint'), ('conversion'), ('default value'),
@@ -159,6 +160,12 @@ WARNING:  error for foreign table,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" d
 WARNING:  error for foreign table,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
 WARNING:  error for foreign table,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
 WARNING:  error for foreign table,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING:  error for property graph,{eins},{}: relation "eins" does not exist
+WARNING:  error for property graph,{eins},{integer}: relation "eins" does not exist
+WARNING:  error for property graph,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist
+WARNING:  error for property graph,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist
+WARNING:  error for property graph,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei"
+WARNING:  error for property graph,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei"
 WARNING:  error for table column,{eins},{}: column name must be qualified
 WARNING:  error for table column,{eins},{integer}: column name must be qualified
 WARNING:  error for table column,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist
@@ -398,6 +405,7 @@ WITH objects (type, name, args) AS (VALUES
     ('view', '{addr_nsp, genview}', '{}'),
     ('materialized view', '{addr_nsp, genmatview}', '{}'),
     ('foreign table', '{addr_nsp, genftable}', '{}'),
+    ('property graph', '{addr_nsp, gengraph}', '{}'),
     ('table column', '{addr_nsp, gentable, b}', '{}'),
     ('foreign table column', '{addr_nsp, genftable, a}', '{}'),
     ('aggregate', '{addr_nsp, genaggr}', '{int4}'),
@@ -474,6 +482,7 @@ view|addr_nsp|genview|addr_nsp.genview|t
 materialized view|addr_nsp|genmatview|addr_nsp.genmatview|t
 foreign table|addr_nsp|genftable|addr_nsp.genftable|t
 foreign table column|addr_nsp|genftable|addr_nsp.genftable.a|t
+property graph|addr_nsp|gengraph|addr_nsp.gengraph|t
 role|NULL|regress_addr_user|regress_addr_user|t
 server|NULL|addr_fserv|addr_fserv|t
 user mapping|NULL|NULL|regress_addr_user on server integer|t
@@ -518,7 +527,7 @@ DROP PUBLICATION addr_pub;
 DROP PUBLICATION addr_pub_schema;
 DROP SUBSCRIPTION regress_addr_sub;
 DROP SCHEMA addr_nsp CASCADE;
-NOTICE:  drop cascades to 14 other objects
+NOTICE:  drop cascades to 15 other objects
 DETAIL:  drop cascades to text search dictionary addr_ts_dict
 drop cascades to text search configuration addr_ts_conf
 drop cascades to text search template addr_ts_temp
@@ -533,6 +542,7 @@ drop cascades to function genaggr(integer)
 drop cascades to type gendomain
 drop cascades to function trig()
 drop cascades to function proc(integer)
+drop cascades to property graph gengraph
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
 --
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..3b41fc7ba0 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -266,3 +266,14 @@ NOTICE:  checking pg_subscription {subdbid} => pg_database {oid}
 NOTICE:  checking pg_subscription {subowner} => pg_authid {oid}
 NOTICE:  checking pg_subscription_rel {srsubid} => pg_subscription {oid}
 NOTICE:  checking pg_subscription_rel {srrelid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_element {pgepgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_element {pgerelid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_element {pgesrcvertexid} => pg_propgraph_element {oid}
+NOTICE:  checking pg_propgraph_element {pgedestvertexid} => pg_propgraph_element {oid}
+NOTICE:  checking pg_propgraph_element_label {pgellabelid} => pg_propgraph_label {oid}
+NOTICE:  checking pg_propgraph_element_label {pgelelid} => pg_propgraph_element {oid}
+NOTICE:  checking pg_propgraph_label {pglpgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_label_property {plppropid} => pg_propgraph_property {oid}
+NOTICE:  checking pg_propgraph_label_property {plpellabelid} => pg_propgraph_element_label {oid}
+NOTICE:  checking pg_propgraph_property {pgppgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_property {pgptypid} => pg_type {oid}
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f53a526f7c..aa7d1ecdce 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -48,7 +48,7 @@ test: create_index create_index_spgist create_view index_including index_includi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse
+test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse create_property_graph
 
 # ----------
 # sanity_check does a vacuum, affecting the sort order of SELECT *
@@ -78,7 +78,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role graph_table
 
 # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other
 test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8 collate.windows.win1252
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index de58d268d3..067d5c2b49 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -456,6 +456,40 @@ ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) test_opclass_o
 ALTER OPERATOR FAMILY alt_opf19 USING btree DROP FUNCTION 5 (int4, int4);
 DROP OPERATOR FAMILY alt_opf19 USING btree;
 
+--
+-- Property Graph
+--
+SET SESSION AUTHORIZATION regress_alter_generic_user1;
+CREATE PROPERTY GRAPH alt_graph1;
+CREATE PROPERTY GRAPH alt_graph2;
+CREATE PROPERTY GRAPH alt_graph3;
+
+ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph2; -- failed (name conflict)
+ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph4; -- OK
+ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user2;  -- failed (no role membership)
+ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user3;  -- OK
+ALTER PROPERTY GRAPH alt_graph4 SET SCHEMA alt_nsp2;  -- OK
+ALTER PROPERTY GRAPH alt_nsp2.alt_graph4 RENAME TO alt_graph2;  -- OK
+ALTER PROPERTY GRAPH alt_graph2 SET SCHEMA alt_nsp2;  -- failed (name conflict)
+
+SET SESSION AUTHORIZATION regress_alter_generic_user2;
+CREATE PROPERTY GRAPH alt_graph5;
+
+ALTER PROPERTY GRAPH alt_graph3 RENAME TO alt_graph5;  -- failed (not owner)
+ALTER PROPERTY GRAPH alt_graph5 RENAME TO alt_graph6;  -- OK
+ALTER PROPERTY GRAPH alt_graph3 OWNER TO regress_alter_generic_user2;  -- failed (not owner)
+ALTER PROPERTY GRAPH alt_graph6 OWNER TO regress_alter_generic_user3;  -- failed (no role membership)
+ALTER PROPERTY GRAPH alt_graph3 SET SCHEMA alt_nsp2;  -- failed (not owner)
+
+RESET SESSION AUTHORIZATION;
+
+SELECT nspname, relname, rolname
+  FROM pg_class c, pg_namespace n, pg_authid a
+  WHERE c.relnamespace = n.oid AND c.relowner = a.oid
+    AND n.nspname in ('alt_nsp1', 'alt_nsp2')
+    AND c.relkind = 'g'
+  ORDER BY nspname, relname;
+
 --
 -- Statistics
 --
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
new file mode 100644
index 0000000000..4f9b5c0349
--- /dev/null
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -0,0 +1,190 @@
+CREATE SCHEMA create_property_graph_tests;
+GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC;
+SET search_path = create_property_graph_tests;
+
+CREATE ROLE regress_graph_user1;
+CREATE ROLE regress_graph_user2;
+
+CREATE PROPERTY GRAPH g1;
+
+COMMENT ON PROPERTY GRAPH g1 IS 'a graph';
+
+CREATE PROPERTY GRAPH g1;  -- error: duplicate
+
+CREATE TABLE t1 (a int, b text);
+CREATE TABLE t2 (i int PRIMARY KEY, j int, k int);
+CREATE TABLE t3 (x int, y text, z text);
+
+CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i));
+CREATE TABLE e2 (a int, x int, t text);
+
+CREATE PROPERTY GRAPH g2
+    VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2)
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+
+-- test dependencies/object descriptions
+
+DROP TABLE t1;  -- fail
+ALTER TABLE t1 DROP COLUMN b;  -- non-key column; fail
+ALTER TABLE t1 DROP COLUMN a;  -- key column; fail
+
+-- like g2 but assembled with ALTER
+CREATE PROPERTY GRAPH g3;
+ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL);
+ALTER PROPERTY GRAPH g3
+    ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1)
+    ADD EDGE TABLES (
+        e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS ADD LABEL t3l3 PROPERTIES ALL COLUMNS;
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x;  -- error
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3;
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2);  -- fail (TODO: dubious error message)
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE;
+ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2);
+
+CREATE PROPERTY GRAPH g4
+    VERTEX TABLES (
+        t1 KEY (a) NO PROPERTIES,
+        t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k),
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz)
+    )
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i)
+            PROPERTIES ALL COLUMNS,
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+            PROPERTIES ALL COLUMNS
+    );
+
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk);
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
+
+CREATE TABLE t11 (a int PRIMARY KEY);
+CREATE TABLE t12 (b int PRIMARY KEY);
+CREATE TABLE t13 (
+    c int PRIMARY KEY,
+    d int REFERENCES t11,
+    e int REFERENCES t12
+);
+
+CREATE PROPERTY GRAPH g5
+    VERTEX TABLES (t11, t12)
+    EDGE TABLES (t13 SOURCE t11 DESTINATION t12);
+
+SELECT pg_get_propgraphdef('g5'::regclass);
+
+-- error cases
+CREATE PROPERTY GRAPH gx VERTEX TABLES (xx, yy);
+CREATE PROPERTY GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a));
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION tx
+    );
+COMMENT ON PROPERTY GRAPH gx IS 'not a graph';
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2)
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2  -- no foreign keys
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL foo PROPERTIES (a + 1 AS aa)
+                   LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+ALTER PROPERTY GRAPH g2
+    ADD VERTEX TABLES (
+        t1 AS t1x KEY (a) LABEL foo PROPERTIES (a + 1 AS aa)
+                          LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) PROPERTIES (b AS p1),
+        t2 PROPERTIES (k AS p1)  -- type mismatch
+    );
+ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE t1 ADD LABEL foo PROPERTIES (b AS k);  -- type mismatch
+
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS b, k)  -- mismatching number of properties on label
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a)  -- mismatching number of properties on label
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS j)  -- mismatching property names on label
+    );
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS yy, b AS zz);  -- mismatching number of properties on label
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS zz);  -- mismatching property names on label
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x);  -- mismatching number of properties on label
+
+
+ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1;
+SET ROLE regress_graph_user1;
+GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
+GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
+RESET ROLE;
+
+
+-- information schema
+
+SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
+SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias;
+SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position;
+SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position;
+SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name;
+SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name;
+SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name;
+SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name;
+SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name;
+SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name;
+
+
+\a\t
+SELECT pg_get_propgraphdef('g2'::regclass);
+SELECT pg_get_propgraphdef('g3'::regclass);
+SELECT pg_get_propgraphdef('g4'::regclass);
+
+SELECT pg_get_propgraphdef('pg_type'::regclass);  -- error
+\a\t
+
+\dG g1
+
+-- TODO
+\d g1
+\d+ g1
+
+DROP TABLE g2;  -- error: wrong object type
+
+DROP PROPERTY GRAPH g1;
+
+DROP PROPERTY GRAPH g1;  -- error: does not exist
+
+DROP PROPERTY GRAPH IF EXISTS g1;
+
+-- leave for pg_upgrade/pg_dump tests
+--DROP SCHEMA create_property_graph_tests CASCADE;
+
+DROP ROLE regress_graph_user1, regress_graph_user2;
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
new file mode 100644
index 0000000000..2b43b136ea
--- /dev/null
+++ b/src/test/regress/sql/graph_table.sql
@@ -0,0 +1,336 @@
+CREATE SCHEMA graph_table_tests;
+GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC;
+SET search_path = graph_table_tests;
+
+CREATE TABLE products (
+    product_no integer PRIMARY KEY,
+    name varchar,
+    price numeric
+);
+
+CREATE TABLE customers (
+    customer_id integer PRIMARY KEY,
+    name varchar,
+    address varchar
+);
+
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    ordered_when date
+);
+
+CREATE TABLE order_items (
+    order_items_id integer PRIMARY KEY,
+    order_id integer REFERENCES orders (order_id),
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+CREATE TABLE customer_orders (
+    customer_orders_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    order_id integer REFERENCES orders (order_id)
+);
+
+CREATE TABLE wishlists (
+    wishlist_id integer PRIMARY KEY,
+    wishlist_name varchar
+);
+
+CREATE TABLE wishlist_items (
+    wishlist_items_id integer PRIMARY KEY,
+    wishlist_id integer REFERENCES wishlists (wishlist_id),
+    product_no integer REFERENCES products (product_no)
+);
+
+CREATE TABLE customer_wishlists (
+    customer_wishlist_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    wishlist_id integer REFERENCES wishlists (wishlist_id)
+);
+
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products,
+        customers,
+        orders
+           DEFAULT LABEL
+            LABEL lists PROPERTIES (order_id as node_id, 'order'::varchar(10) as list_type),
+        wishlists
+           DEFAULT LABEL
+            LABEL lists PROPERTIES (wishlist_id as node_id, 'wishlist'::varchar(10) as list_type)
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (order_id as link_id, product_no),
+        wishlist_items KEY (wishlist_items_id)
+            SOURCE KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (wishlist_id as link_id, product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, order_id as link_id),
+        customer_wishlists KEY (customer_wishlist_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, wishlist_id as link_id)
+    );
+
+SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers|employees WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c.name AS customer_name));  -- error
+
+INSERT INTO products VALUES
+    (1, 'product1', 10),
+    (2, 'product2', 20),
+    (3, 'product3', 30);
+INSERT INTO customers VALUES
+    (1, 'customer1', 'US'),
+    (2, 'customer2', 'CA'),
+    (3, 'customer3', 'GL');
+INSERT INTO orders VALUES
+    (1, date '2024-01-01'),
+    (2, date '2024-01-02'),
+    (3, date '2024-01-03');
+INSERT INTO wishlists VALUES
+    (1, 'wishlist1'),
+    (2, 'wishlist2'),
+    (3, 'wishlist3');
+INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES
+    (1, 1, 1, 5),
+    (2, 1, 2, 10),
+    (3, 2, 1, 7);
+INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES
+    (1, 1, 1),
+    (2, 2, 2);
+INSERT INTO customer_wishlists (customer_wishlist_id, customer_id, wishlist_id) VALUES
+    (1, 2, 3),
+    (2, 3, 1),
+    (3, 3, 2);
+INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES
+    (1, 1, 2),
+    (2, 1, 3),
+    (3, 2, 1),
+    (4, 3, 1);
+
+-- single element path pattern
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
+-- graph element specification without label or variable
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[]->(o IS orders) COLUMNS (c.name AS customer_name));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c:customers)-[co:customer_orders]->(o:orders WHERE o.ordered_when = date '2024-01-02') COLUMNS (c.name, c.address));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when));
+SELECT * FROM GRAPH_TABLE (myshop MATCH ( o IS orders ) <- [ IS customer_orders ] - (c IS customers) COLUMNS ( c.name, o.ordered_when));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS cust_lists]->(l IS lists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY customer_name, product_name, list_type;
+-- label disjunction
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name)) ORDER BY customer_name, product_name;
+-- property not associated with labels queried results in error
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY 1, 2, 3;
+-- vertex to vertex connection abbreviation
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1;
+
+-- lateral test
+CREATE TABLE x1 (a int, b text);
+INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
+DROP TABLE x1;
+
+
+create table v1 (id int primary key,
+					vname varchar(10),
+					vprop1 int,
+					vprop2 int);
+
+create table v2 (id1 int,
+					id2 int,
+					vname varchar(10),
+					vprop1 int,
+					vprop2 int);
+
+create table v3 (id int primary key,
+					vname varchar(10),
+					vprop1 int,
+					vprop2 int);
+
+-- edge connecting v1 and v2
+create table e1_2 (id_1 int,
+					id_2_1 int,
+					id_2_2 int,
+					ename varchar(10),
+					eprop1 int);
+
+-- edge connecting v1 and v3
+create table e1_3 (id_1 int,
+					id_3 int,
+					ename varchar(10),
+					eprop1 int,
+					primary key (id_1, id_3));
+
+create table e2_3 (id_2_1 int,
+                    id_2_2 int,
+                    id_3 int,
+                    ename varchar(10),
+                    eprop1 int);
+
+create property graph g1
+vertex tables (
+	v1
+        label vl1 properties (vname, vprop1)
+        label l1 properties (vname as elname), -- label shared by vertexes as well as edges
+	v2 key (id1, id2)
+		label vl2 properties (vname, vprop2, 'vl2_prop'::varchar(10) as lprop1)
+        label vl3 properties (vname, vprop1, 'vl2_prop'::varchar(10) as lprop1)
+        label l1 properties (vname as elname),
+	v3
+		label vl3 properties (vname, vprop1, 'vl3_prop'::varchar(10) as lprop1)
+        label l1 properties (vname as elname)
+)
+-- edges with differing number of columns in destination keys
+edge tables (
+	e1_2 key (id_1, id_2_1, id_2_2)
+		source key (id_1) references v1 (id)
+		destination key (id_2_1, id_2_2) references v2 (id1, id2)
+		label el1 properties (eprop1, ename)
+        label l1 properties (ename as elname),
+	e1_3
+		source key (id_1) references v1 (id)
+		destination key (id_3) references v3 (id)
+		-- order of property names doesn't matter
+		label el1 properties (ename, eprop1)
+        label l1 properties (ename as elname),
+    e2_3 key (id_2_1, id_2_2, id_3)
+        source key (id_2_1, id_2_2) references v2 (id1, id2)
+        destination key (id_3) references v3 (id)
+        -- new property lprop2 not shared by el1
+        -- does not share eprop1 from by el1
+        label el2 properties (ename, eprop1 * 10 as lprop2)
+        label l1 properties (ename as elname)
+);
+
+insert into v1 values (1, 'v11', 10, 100),
+                      (2, 'v12', 20, 200),
+                      (3, 'v13', 30, 300);
+
+insert into v2 values (1000, 1, 'v21', 1010, 1100),
+                      (1000, 2, 'v22', 1020, 1200),
+                      (1000, 3, 'v23', 1030, 1300);
+
+insert into v3 values (2001, 'v31', 2010, 2100),
+                      (2002, 'v32', 2020, 2200),
+                      (2003, 'v33', 2030, 2300);
+
+insert into e1_2 values (1, 1000, 2, 'e121', 10001),
+                        (2, 1000, 1, 'e122', 10002);
+
+insert into e1_3 values (1, 2003, 'e131', 10003),
+                        (1, 2001, 'e132', 10004);
+insert into e2_3 values (1000, 2, 2002, 'e231', 10005);
+
+-- empty element path pattern, counts number of edges in the graph
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 as one));
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()->() COLUMNS (1 as one));
+-- Vertex element v2 has label vl3 which exposes property vprop1. But vl3 is
+-- not part of label expression. Instead v2 get bound through label vl2 which
+-- does not expose vprop1. The GRAPH_TABLE clause project vprop1.
+--
+-- TODO: This case fails since catalogs do not associated properties with
+-- elements directly. More code is needed to make it work.
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname,
+a.vprop1));
+-- vprop2 is associated with vl2 but not vl3
+select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, b.ename as conn, c.vname as dest, c.lprop1, c.vprop2, c.vprop1));
+
+-- Errors
+-- vl1 is not associated with property vprop2
+select src, src_vprop2, conn, dest from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, a.vprop2 as src_vprop2, b.ename as conn, c.vname as dest));
+-- property ename is associated with edge labels but not with a vertex label
+select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, src.ename as sename));
+-- vname is associated vertex labels but not an edge label
+select * from graph_table (g1 match (src)-[conn]->(dest) columns (conn.vname as cvname, conn.ename as cename));
+-- el1 is associated with edges but is only label used to qualify vertex
+select * from graph_table (g1 match (src is el1)-[conn]->(dest) columns (conn.ename as cename));
+-- el1 is associated with edges but is one of the labels used to qualify vertex
+select * from graph_table (g1 match (src is el1 | vl1)-[conn]->(dest) columns (conn.ename as cename));
+
+-- select all the properties across all the labels associated with a given type
+-- of graph element
+select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname, src.vprop1 as svp1, src.vprop2 as svp2, src.lprop1 as slp1, dest.vprop1 as dvp1, dest.vprop2 as dvp2, dest.lprop1 as dlp1, conn.eprop1 as cep1, conn.lprop2 as clp2));
+-- three label disjunction
+select * from graph_table (g1 match (src IS vl1 | vl2 | vl3)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname));
+-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination.
+with all_connected_vertices as (select svn, dvn from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svn, dest.vname as dvn))),
+    all_vertices as (select vn from graph_table (g1 match (vertex) columns (vertex.vname as vn)))
+select vn from all_vertices except (select svn from all_connected_vertices union select dvn from all_connected_vertices);
+-- query all connections using a label shared by vertices and edges
+select sn, cn, dn from graph_table (g1 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn));
+
+-- property graph with some of the elements, labels and properties same as the
+-- previous one. Test whether components from the specified property graph are
+-- used.
+create property graph g2
+vertex tables (
+	v1
+        label l1 properties ('g2.' || vname as elname),
+	v2 key (id1, id2)
+        label l1 properties ('g2.' || vname as elname),
+	v3
+        label l1 properties ('g2.' || vname as elname)
+)
+edge tables (
+	e1_2 key (id_1, id_2_1, id_2_2)
+		source key (id_1) references v1 (id)
+		destination key (id_2_1, id_2_2) references v2 (id1, id2)
+        label l1 properties ('g2.' || ename as elname),
+	e1_3
+		source key (id_1) references v1 (id)
+		destination key (id_3) references v3 (id)
+        label l1 properties ('g2.' || ename as elname),
+    e2_3 key (id_2_1, id_2_2, id_3)
+        source key (id_2_1, id_2_2) references v2 (id1, id2)
+        destination key (id_3) references v3 (id)
+        label l1 properties ('g2.' || ename as elname)
+);
+select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn));
+
+CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));
+
+SELECT pg_get_viewdef('customers_us'::regclass);
+
+-- test view/graph nesting
+
+CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
+SELECT * FROM customers;
+SELECT * FROM customers_view;
+
+CREATE PROPERTY GRAPH myshop2
+    VERTEX TABLES (
+        products,
+        customers_view KEY (customer_id) LABEL customers,
+        orders
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers_view (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+    );
+
+CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE (myshop2 MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name_redacted AS customer_name_redacted));
+
+SELECT * FROM customers_us_redacted;
+
+-- leave for pg_upgrade/pg_dump tests
+--DROP SCHEMA graph_table_tests CASCADE;
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 1a6c61f49d..93f9f9c704 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -37,6 +37,7 @@ CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END
 CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig();
 CREATE POLICY genpol ON addr_nsp.gentable;
 CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$;
+CREATE PROPERTY GRAPH addr_nsp.gengraph;
 CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw;
 CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
 ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
@@ -90,7 +91,7 @@ DECLARE
 BEGIN
     FOR objtype IN VALUES
         ('table'), ('index'), ('sequence'), ('view'),
-        ('materialized view'), ('foreign table'),
+        ('materialized view'), ('foreign table'), ('property graph'),
         ('table column'), ('foreign table column'),
         ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'),
         ('table constraint'), ('domain constraint'), ('conversion'), ('default value'),
@@ -163,6 +164,7 @@ WITH objects (type, name, args) AS (VALUES
     ('view', '{addr_nsp, genview}', '{}'),
     ('materialized view', '{addr_nsp, genmatview}', '{}'),
     ('foreign table', '{addr_nsp, genftable}', '{}'),
+    ('property graph', '{addr_nsp, gengraph}', '{}'),
     ('table column', '{addr_nsp, gentable, b}', '{}'),
     ('foreign table column', '{addr_nsp, genftable, a}', '{}'),
     ('aggregate', '{addr_nsp, genaggr}', '{int4}'),
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9e951a9e6f..faac436d2c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -4169,3 +4169,24 @@ yyscan_t
 z_stream
 z_streamp
 zic_t
+
+# TODO
+AlterPropGraphElementKind
+AlterPropGraphStmt
+CreatePropGraphStmt
+FormData_pg_propgraph_element
+FormData_pg_propgraph_element_label
+FormData_pg_propgraph_label
+FormData_pg_propgraph_label_property
+FormData_pg_propgraph_property
+GraphElementPattern
+GraphElementPatternKind
+GraphLabelRef
+GraphPattern
+GraphPropertyRef
+GraphTableParseState
+PropGraphEdge
+PropGraphLabelAndProperties
+PropGraphProperties
+PropGraphVertex
+RangeGraphTable
-- 
2.34.1

From 30be83604f169169863704d9f8ec26537cca9db8 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 17 Jul 2024 10:05:39 +0530
Subject: [PATCH 3/8] pgperltidy fixes

Fixes suggested by pgperltidy. To be squashed into the previous commit.

Ashutosh Bapat
---
 src/bin/pg_dump/t/002_pg_dump.pl | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9d6485aca1..1fc0109c19 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2914,13 +2914,14 @@ my %tests = (
 
 	'CREATE PROPERTY GRAPH propgraph' => {
 		create_order => 20,
-		create_sql   => 'CREATE PROPERTY GRAPH dump_test.propgraph;',
+		create_sql => 'CREATE PROPERTY GRAPH dump_test.propgraph;',
 		regexp => qr/^
 			\QCREATE PROPERTY GRAPH dump_test.propgraph\E;
 			/xm,
 		like =>
 		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
-		unlike => { exclude_dump_test_schema => 1, only_dump_measurement => 1, },
+		unlike =>
+		  { exclude_dump_test_schema => 1, only_dump_measurement => 1, },
 	},
 
 	'CREATE PUBLICATION pub1' => {
-- 
2.34.1

From 9ca4973bb41f2ac3e7aaea48cb908ad1f44c2c88 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 1 Aug 2024 10:52:35 +0530
Subject: [PATCH 7/8] Support cyclic path pattern.

A cyclic path pattern is a path patterns where an element pattern variable
repeats in multiple element path patterns. In such a path pattern the element
patterns with the same variable indicate the same element in the path.

Elements which share the variable name should have the same element type. The
element patterns sharing the same variable name should have same label
expression. The patterns may be have different conditions which are finally
ANDed since they all represent the same element.

While it's easy to imagine a repeated vertex pattern, a repeated edge pattern
is slightly complex. An edge connects only two vertices, and thus a repeated
edge pattern constrains the adjacent vertex patterns even if they have
different variable names. Such patterns are not supported.

Author: Ashutosh Bapat
---
 src/backend/rewrite/rewriteGraphTable.c   | 390 ++++++++++++++--------
 src/test/regress/expected/graph_table.out | 119 ++++++-
 src/test/regress/sql/graph_table.sql      |  52 ++-
 3 files changed, 407 insertions(+), 154 deletions(-)

diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 4b787a2e88..6e2e6ed38b 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -38,24 +38,45 @@
 #include "utils/syscache.h"

+/*
+ * Represents one path factor in a path.
+ *
+ * One path factor corresponds to one element pattern in a simple path.
+ *
+ * In a cyclic path, one path factor may correspond to one or more element
+ * patterns all pointing to the same graph element. The members of such a path
+ * factor are a combination of corresponding specifications in the element
+ * patterns.
+ */
+struct path_factor
+{
+	GraphElementPatternKind kind;
+	const char *variable;
+	Node	   *labelexpr;
+	Node	   *whereClause;
+	int			factorpos;		/* Position of this path factor in a path. */
+	List	   *labeloids;		/* OIDs of all the labels referenced in
+								 * labelexpr. */
+	/* Links to vertex path factors connected by this edge path factor. */
+	struct path_factor *src_pf;
+	struct path_factor *dest_pf;
+};
+
 /*
  * Represents one property graph element (vertex or edge) in the path.
  *
  * Label expression in an element pattern resolves into a set of elements. For
  * each of those elements we create one graph_path_element object.
  */
-struct graph_path_element
+struct path_element
 {
 	Oid			elemoid;
 	Oid			reloid;
-	int			rtindex;
-	List	   *labeloids;
 	Oid			srcvertexid;
-	int			srcelem_pos;
 	Oid			destvertexid;
-	int			destelem_pos;
 	List	   *qual_exprs;
-	GraphElementPattern *parent_gep;
+	/* Path factor from which this element is derived. */
+	struct path_factor *path_factor;
 };
 
 static Node *replace_property_refs(Oid propgraphid, Node *node, const List *mappings);
@@ -67,7 +88,7 @@ static List *generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List
 static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte);
 static Query *generate_union_from_pathqueries(List **pathqueries);
 static const char *get_gep_kind_name(GraphElementPatternKind gepkind);
-static List *get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos);
+static List *get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf);
 static bool is_property_associated_with_label(Oid labeloid, Oid propoid);
 static const char *get_graph_elem_kind_name(GraphElementPatternKind gepkind);
 static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex);
@@ -157,8 +178,9 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 {
 	List	   *pathqueries = NIL;
 	List	   *path_elem_lists = NIL;
-	ListCell   *lc;
-	int			elempos = 0;
+	int			factorpos = 0;
+	List	   *path_factors = NIL;
+	struct path_factor *prev_pf = NULL;
 
 	Assert(list_length(path_pattern) > 0);
 
@@ -166,9 +188,9 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 	 * For every element pattern in the given path pattern collect all the
 	 * graph elements that satisfy the element pattern.
 	 */
-	foreach(lc, path_pattern)
+	foreach_node(GraphElementPattern, gep, path_pattern)
 	{
-		GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc);
+		struct path_factor *pf = NULL;
 
 		if (gep->kind != VERTEX_PATTERN &&
 			gep->kind != EDGE_PATTERN_LEFT && gep->kind != EDGE_PATTERN_RIGHT)
@@ -177,13 +199,132 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 		if (gep->quantifier)
 			elog(ERROR, "element pattern quantifier not supported yet");
 
-		path_elem_lists = lappend(path_elem_lists,
-								  get_elements_for_gep(rte->relid, gep, elempos++));
+		/*
+		 * Element patterns with the same name represent the same element and
+		 * hence same path factor. They do not add a new graph element to the
+		 * query but affect the links of adjacent elements.
+		 */
+		foreach_ptr(struct path_factor, other, path_factors)
+		{
+			if (gep->variable && other->variable &&
+				strcmp(gep->variable, other->variable) == 0)
+			{
+				if (other->kind != gep->kind)
+					ereport(ERROR,
+					/* XXX: use correct error code. */
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("element patterns with same variable name \"%s\" but different element pattern types",
+									gep->variable)));
+
+				/*
+				 * If only one of the two element patterns has a label
+				 * expression use it. Otherwise make sure that both of them
+				 * have the same label expression. If they have different
+				 * label expressions they need to be conjuncted. Label
+				 * conjuction is not supported right now.
+				 */
+				if (!other->labelexpr)
+					other->labelexpr = gep->labelexpr;
+				else if (gep->labelexpr && !equal(other->labelexpr, gep->labelexpr))
+					ereport(ERROR,
+					/* XXX: Use correct error code. */
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("element patterns with same variable name \"%s\" but different label expressions",
+									gep->variable)));
+
+				/* Both sets of conditions apply to the element pattern. */
+				if (!other->whereClause)
+					other->whereClause = gep->whereClause;
+				else if (gep->whereClause)
+					other->whereClause = (Node *) makeBoolExpr(AND_EXPR,
+															   list_make2(other->whereClause, gep->whereClause),
+															   -1);
+				pf = other;
+				break;
+			}
+		}
+
+		if (!pf)
+		{
+			{
+				pf = palloc0_object(struct path_factor);
+				pf->factorpos = factorpos++;
+				pf->kind = gep->kind;
+				pf->labelexpr = gep->labelexpr;
+				pf->variable = gep->variable;
+				pf->whereClause = gep->whereClause;
+
+				path_factors = lappend(path_factors, pf);
+			}
+		}
+
+		/*
+		 * Setup adjacent path factors. If multiple edge patterns share the
+		 * same variable name, they constain the adjacent vertex patterns
+		 * since an edge can connect only one pair of vertexes and those
+		 * vertexes also need to repeated along with the edge (a walk). This
+		 * means that we have to coalesce the vertex patterns adjacent to a
+		 * repeated edge even though they have different variables.  E.g.
+		 * (a)-[b]->(c)-[b]<-(d) implies that (a) and (d) represent the same
+		 * vertex element pattern. This is slighly harder to implement and
+		 * probably less useful. Hence not supported for now.
+		 */
+		if (prev_pf)
+		{
+			if (prev_pf->kind == EDGE_PATTERN_RIGHT)
+			{
+				Assert(!IS_EDGE_PATTERN(pf->kind));
+				if (prev_pf->dest_pf && prev_pf->dest_pf != pf)
+					elog(ERROR, "An edge can not connect more than two vertexes even in a cyclic pattern.");
+				prev_pf->dest_pf = pf;
+			}
+			else if (prev_pf->kind == EDGE_PATTERN_LEFT)
+			{
+				Assert(!IS_EDGE_PATTERN(pf->kind));
+				if (prev_pf->src_pf && prev_pf->src_pf != pf)
+					elog(ERROR, "An edge can not connect more than two vertexes even in a cyclic pattern.");
+				prev_pf->src_pf = pf;
+			}
+			else if (prev_pf->kind == EDGE_PATTERN_ANY)
+			{
+				/* We don't support undirected edges yet. */
+				Assert(false);
+			}
+
+			if (pf->kind == EDGE_PATTERN_RIGHT)
+			{
+				Assert(!IS_EDGE_PATTERN(prev_pf->kind));
+				if (pf->src_pf && pf->src_pf != prev_pf)
+					elog(ERROR, "An edge can not connect more than two vertexes even in a cyclic pattern.");
+				pf->src_pf = prev_pf;
+			}
+			else if (pf->kind == EDGE_PATTERN_LEFT)
+			{
+				Assert(!IS_EDGE_PATTERN(prev_pf->kind));
+				if (pf->dest_pf && pf->dest_pf != prev_pf)
+					elog(ERROR, "An edge can not connect more than two vertexes even in a cyclic pattern.");
+				pf->dest_pf = prev_pf;
+			}
+			else if (pf->kind == EDGE_PATTERN_ANY)
+			{
+				/* We don't support undirected edges yet. */
+				Assert(false);
+			}
+		}
+
+		prev_pf = pf;
 	}
 
+	/*
+	 * Collect list of elements for each path factor. Do this after all the
+	 * edge links are setup correctly.
+	 */
+	foreach_ptr(struct path_factor, pf, path_factors)
+		path_elem_lists = lappend(path_elem_lists,
+								  get_path_elements_for_path_factor(rte->relid, pf));
+
 	pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
 															NIL, path_elem_lists, 0);
-
 	if (!pathqueries)
 		pathqueries = list_make1(generate_query_for_empty_path_pattern(rte));
 
@@ -196,15 +337,12 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 static List *
 generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_elem_lists, int elempos)
 {
-	List	   *gep_elems = list_nth_node(List, path_elem_lists, elempos);
-	ListCell   *lc;
+	List	   *path_elems = list_nth_node(List, path_elem_lists, elempos);
 
-	foreach(lc, gep_elems)
+	foreach_ptr(struct path_element, pe, path_elems)
 	{
-		struct graph_path_element *elem = lfirst(lc);
-
 		/* Update current path being built with current element. */
-		cur_path = lappend(cur_path, elem);
+		cur_path = lappend(cur_path, pe);
 
 		/*
 		 * If this is the last element in the path, generate query for the
@@ -238,50 +376,47 @@ generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries,
 static Query *
 generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 {
-	ListCell   *lc;
 	Query	   *path_query = makeNode(Query);
 	List	   *fromlist = NIL;
 	List	   *qual_exprs = NIL;
 
 	path_query->commandType = CMD_SELECT;
 
-	foreach(lc, graph_path)
+	foreach_ptr(struct path_element, pe, graph_path)
 	{
-		struct graph_path_element *gpe = (struct graph_path_element *) lfirst(lc);
-		GraphElementPattern *gep = gpe->parent_gep;
+		struct path_factor *pf = pe->path_factor;
 		RangeTblRef *rtr;
 		Relation	rel;
 		ParseNamespaceItem *pni;
 
-		Assert(gep->kind == VERTEX_PATTERN ||
-			   gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT);
-		Assert(!gep->quantifier);
+		Assert(pf->kind == VERTEX_PATTERN ||
+			   pf->kind == EDGE_PATTERN_LEFT || pf->kind == EDGE_PATTERN_RIGHT);
 
-		if (gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT)
+		if (pf->kind == EDGE_PATTERN_LEFT || pf->kind == EDGE_PATTERN_RIGHT)
 		{
-			struct graph_path_element *src_gpe = list_nth(graph_path, gpe->srcelem_pos);
-			struct graph_path_element *dest_gpe = list_nth(graph_path, gpe->destelem_pos);
+			struct path_element *src_pe;
+			struct path_element *dest_ge;
 
-			/*
-			 * Make sure that the source and destination elements of this edge
-			 * are placed at the expected position and have the corret
-			 * RangeTblEntry indexes as setup by create_gpe_for_element().
-			 */
-			Assert(gpe->srcelem_pos == src_gpe->rtindex - 1 &&
-				   gpe->destelem_pos == dest_gpe->rtindex - 1);
+			Assert(pf->src_pf && pf->dest_pf);
+			src_pe = list_nth(graph_path, pf->src_pf->factorpos);
+			dest_ge = list_nth(graph_path, pf->dest_pf->factorpos);
+
+			/* Make sure that the links of adjacent vertices are correct. */
+			Assert(pf->src_pf == src_pe->path_factor &&
+				   pf->dest_pf == dest_ge->path_factor);
 
 			/*
 			 * If the given edge element does not connect the adjacent vertex
 			 * elements in this path, the path is broken. Abandon this path as
 			 * it won't return any rows.
 			 */
-			if (src_gpe->elemoid != gpe->srcvertexid ||
-				dest_gpe->elemoid != gpe->destvertexid)
+			if (src_pe->elemoid != pe->srcvertexid ||
+				dest_ge->elemoid != pe->destvertexid)
 				return NULL;
 		}
 
 		/* Create RangeTblEntry for this element table. */
-		rel = table_open(gpe->reloid, AccessShareLock);
+		rel = table_open(pe->reloid, AccessShareLock);
 		pni = addRangeTableEntryForRelation(make_parsestate(NULL), rel, AccessShareLock,
 											NULL, true, false);
 		table_close(rel, NoLock);
@@ -297,17 +432,17 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 		 * holds true. That the elements' RangeTblEntrys are added in the
 		 * order in which they appear in the path.
 		 */
-		Assert(gpe->rtindex == rtr->rtindex);
+		Assert(pf->factorpos + 1 == rtr->rtindex);
 
-		if (gep->whereClause)
+		if (pf->whereClause)
 		{
 			Node	   *tr;
 
-			tr = replace_property_refs(rte->relid, gep->whereClause, list_make1(gpe));
+			tr = replace_property_refs(rte->relid, pf->whereClause, list_make1(pe));
 
 			qual_exprs = lappend(qual_exprs, tr);
 		}
-		qual_exprs = list_concat(qual_exprs, gpe->qual_exprs);
+		qual_exprs = list_concat(qual_exprs, pe->qual_exprs);
 	}
 
 	if (rte->graph_pattern->whereClause)
@@ -339,7 +474,6 @@ static Query *
 generate_query_for_empty_path_pattern(RangeTblEntry *rte)
 {
 	Query	   *query = makeNode(Query);
-	ListCell   *lc;
 
 	query->commandType = CMD_SELECT;
 
@@ -355,9 +489,8 @@ generate_query_for_empty_path_pattern(RangeTblEntry *rte)
 	 * columns as projected by GRAPH_TABLE clause. Do this by constructing a
 	 * target list full of NULL values.
 	 */
-	foreach(lc, rte->graph_table_columns)
+	foreach_node(TargetEntry, te, rte->graph_table_columns)
 	{
-		TargetEntry *te = copyObject(lfirst_node(TargetEntry, lc));
 		Node	   *nte = (Node *) te->expr;
 
 		te->expr = (Expr *) makeNullConst(exprType(nte), exprTypmod(nte), exprCollation(nte));
@@ -483,13 +616,9 @@ generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetl
 		 */
 		if (targetlist)
 		{
-			ListCell   *tl;
-
 			*targetlist = NIL;
-			foreach(tl, lquery->targetList)
+			foreach_node(TargetEntry, tle, lquery->targetList)
 			{
-				TargetEntry *tle = (TargetEntry *) lfirst(tl);
-
 				if (!tle->resjunk)
 					*targetlist = lappend(*targetlist, tle);
 			}
@@ -516,29 +645,29 @@ generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetl
  * If the type of graph element does not fit the element pattern kind, the
  * function returns NULL.
  */
-static struct graph_path_element *
-create_gpe_for_element(GraphElementPattern *gep, Oid elemoid, int elempos)
+static struct path_element *
+create_gpe_for_element(struct path_factor *pf, Oid elemoid)
 {
 	HeapTuple	eletup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elemoid));
 	Form_pg_propgraph_element pgeform;
-	struct graph_path_element *gpe;
+	struct path_element *pe;
 
 	if (!eletup)
 		elog(ERROR, "cache lookup failed for property graph element %u", elemoid);
 	pgeform = ((Form_pg_propgraph_element) GETSTRUCT(eletup));
 
-	if ((pgeform->pgekind == PGEKIND_VERTEX && gep->kind != VERTEX_PATTERN) ||
-		(pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(gep->kind)))
+	if ((pgeform->pgekind == PGEKIND_VERTEX && pf->kind != VERTEX_PATTERN) ||
+		(pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(pf->kind)))
 	{
 		ReleaseSysCache(eletup);
 		return NULL;
 	}
 
-	gpe = palloc0_object(struct graph_path_element);
-	gpe->parent_gep = gep;
-	gpe->elemoid = elemoid;
-	gpe->reloid = pgeform->pgerelid;
-	gpe->qual_exprs = NIL;
+	pe = palloc0_object(struct path_element);
+	pe->path_factor = pf;
+	pe->elemoid = elemoid;
+	pe->reloid = pgeform->pgerelid;
+	pe->qual_exprs = NIL;
 
 	/*
 	 * When the path containing this element will be converted into a query
@@ -550,54 +679,40 @@ create_gpe_for_element(GraphElementPattern *gep, Oid elemoid, int elempos)
 	 * as the number of paths this element appears in. Hence save the assumed
 	 * rtindex so that it can be verified later.
 	 */
-	gpe->rtindex = elempos + 1;
-
-	if (IS_EDGE_PATTERN(gep->kind))
+	if (IS_EDGE_PATTERN(pf->kind))
 	{
-		int			src_rtindex;
-		int			dest_rtindex;
 		List	   *edge_qual;
 
-		gpe->srcvertexid = pgeform->pgesrcvertexid;
-		gpe->destvertexid = pgeform->pgedestvertexid;
+		pe->srcvertexid = pgeform->pgesrcvertexid;
+		pe->destvertexid = pgeform->pgedestvertexid;
+		Assert(pf->src_pf && pf->dest_pf);
 
-		if (gep->kind == EDGE_PATTERN_RIGHT)
-		{
-			gpe->srcelem_pos = elempos - 1;
-			gpe->destelem_pos = elempos + 1;
-			src_rtindex = gpe->rtindex - 1;
-			dest_rtindex = gpe->rtindex + 1;
-		}
-		else if (gep->kind == EDGE_PATTERN_LEFT)
-		{
-			gpe->srcelem_pos = elempos + 1;
-			gpe->destelem_pos = elempos - 1;
-			src_rtindex = gpe->rtindex + 1;
-			dest_rtindex = gpe->rtindex - 1;
-		}
-		else
-		{
-			/* We don't support undirected edges yet. */
-			Assert(false);
-			gpe->srcelem_pos = elempos;
-			gpe->destelem_pos = elempos;
-			src_rtindex = gpe->rtindex;
-			dest_rtindex = gpe->rtindex;
-		}
-
-		edge_qual = build_edge_vertex_link_quals(eletup, gpe->rtindex, src_rtindex,
+		/*
+		 * When the path containing this element will be converted into a
+		 * query (generate_query_for_graph_path()) this element will be
+		 * converted into a RangeTblEntry. The RangeTblEntrys are created in
+		 * the same order in which elements appear in the path and thus get
+		 * consecutive RangeTable indexes. So we can safely assign the
+		 * RangeTable index of this element before creating RangeTblEntry for
+		 * it. This helps crafting the quals linking an edge to the adjacent
+		 * vertexes only once while we have access to the catalog entry of the
+		 * element. Otherwise, we need to craft the quals as many times as the
+		 * number of paths this element appears in, fetching the catalog entry
+		 * each time.
+		 */
+		edge_qual = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->src_pf->factorpos + 1,
 												 Anum_pg_propgraph_element_pgesrckey,
 												 Anum_pg_propgraph_element_pgesrcref);
-		gpe->qual_exprs = list_concat(gpe->qual_exprs, edge_qual);
-		edge_qual = build_edge_vertex_link_quals(eletup, gpe->rtindex, dest_rtindex,
+		pe->qual_exprs = list_concat(pe->qual_exprs, edge_qual);
+		edge_qual = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->dest_pf->factorpos + 1,
 												 Anum_pg_propgraph_element_pgedestkey,
 												 Anum_pg_propgraph_element_pgedestref);
-		gpe->qual_exprs = list_concat(gpe->qual_exprs, edge_qual);
+		pe->qual_exprs = list_concat(pe->qual_exprs, edge_qual);
 	}
 
 	ReleaseSysCache(eletup);
 
-	return gpe;
+	return pe;
 }
 
 static const char *
@@ -669,15 +784,10 @@ get_labels_for_expr(Oid propgraphid, Node *labelexpr)
 	{
 		BoolExpr   *be = castNode(BoolExpr, labelexpr);
 		List	   *label_exprs = be->args;
-		ListCell   *llc;
 
 		label_oids = NIL;
-		foreach(llc, label_exprs)
-		{
-			GraphLabelRef *glr = lfirst_node(GraphLabelRef, llc);
-
+		foreach_node(GraphLabelRef, glr, label_exprs)
 			label_oids = lappend_oid(label_oids, glr->labelid);
-		}
 	}
 	else
 		elog(ERROR, "unsupported label expression type: %d", (int) nodeTag(labelexpr));
@@ -698,22 +808,21 @@ get_labels_for_expr(Oid propgraphid, Node *labelexpr)
  * `elempos` is position of the element pattern in the path pattern.
  */
 static List *
-get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos)
+get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
 {
-	List	   *label_oids = get_labels_for_expr(propgraphid, gep->labelexpr);
+	List	   *label_oids = get_labels_for_expr(propgraphid, pf->labelexpr);
 	List	   *elem_oids_seen = NIL;
-	List	   *elem_gpe_oids = NIL;
-	List	   *elem_gpes = NIL;
-	ListCell   *lc;
+	List	   *pf_elem_oids = NIL;
+	List	   *path_elements = NIL;
+	List	   *unresolved_labels = NIL;
 	Relation	rel;
 	SysScanDesc scan;
 	ScanKeyData key[1];
 	HeapTuple	tup;
 
 	rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
-	foreach(lc, label_oids)
+	foreach_oid(labeloid, label_oids)
 	{
-		Oid			labeloid = lfirst_oid(lc);
 		bool		found = false;
 
 		ScanKeyInit(&key[0],
@@ -736,12 +845,12 @@ get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos)
 				 * the current label has at least one element, that satisfies
 				 * the given element pattern, associated with it.
 				 */
-				struct graph_path_element *gpe = create_gpe_for_element(gep, elem_oid, elempos);
+				struct path_element *pe = create_gpe_for_element(pf, elem_oid);
 
-				if (gpe)
+				if (pe)
 				{
-					elem_gpes = lappend(elem_gpes, gpe);
-					elem_gpe_oids = lappend_oid(elem_gpe_oids, elem_oid);
+					path_elements = lappend(path_elements, pe);
+					pf_elem_oids = lappend_oid(pf_elem_oids, elem_oid);
 					found = true;
 				}
 
@@ -749,11 +858,11 @@ get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos)
 				 * Add the graph element to the elements considered so far to
 				 * avoid processing same element associated with multiple
 				 * labels multiple times. Also avoids creating duplicate
-				 * GraphPathElements.
+				 * path_element structures.
 				 */
 				elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
 			}
-			else if (list_member_oid(elem_gpe_oids, elem_oid))
+			else if (list_member_oid(pf_elem_oids, elem_oid))
 			{
 				/*
 				 * The graph element is known to qualify the given element
@@ -774,30 +883,27 @@ get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos)
 			 * error if the label was explicitly specified in the element
 			 * pattern. Otherwise just Remove it from the list.
 			 */
-			if (!gep->labelexpr)
-				foreach_delete_current(label_oids, lc);
+			if (!pf->labelexpr)
+				unresolved_labels = lappend_oid(unresolved_labels, labeloid);
 			else
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_OBJECT),
 						 errmsg("can not find label \"%s\" in property graph \"%s\" for element type \"%s\"",
 								get_propgraph_label_name(labeloid),
 								get_rel_name(propgraphid),
-								get_graph_elem_kind_name(gep->kind))));
+								get_graph_elem_kind_name(pf->kind))));
 		}
 
 		systable_endscan(scan);
 	}
 	table_close(rel, AccessShareLock);
 
-	/* Update the filtered label list in each graph_path_element. */
-	foreach(lc, elem_gpes)
-	{
-		struct graph_path_element *gpe = lfirst(lc);
-
-		gpe->labeloids = label_oids;
-	}
-
-	return elem_gpes;
+	/*
+	 * Remember the OIDs of resolved labels for the given path factor for
+	 * later use .
+	 */
+	pf->labeloids = list_difference_oid(label_oids, unresolved_labels);
+	return path_elements;
 }
 
 static const char *
@@ -846,15 +952,13 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
 	{
 		GraphPropertyRef *gpr = (GraphPropertyRef *) node;
 		Node	   *n = NULL;
-		ListCell   *lc;
-		struct graph_path_element *found_mapping = NULL;
+		struct path_element *found_mapping = NULL;
+		struct path_factor *mapping_factor = NULL;
 		List	   *unrelated_labels = NIL;
 
-		foreach(lc, context->mappings)
+		foreach_ptr(struct path_element, m, context->mappings)
 		{
-			struct graph_path_element *m = lfirst(lc);
-
-			if (m->parent_gep->variable && strcmp(gpr->elvarname, m->parent_gep->variable) == 0)
+			if (m->path_factor->variable && strcmp(gpr->elvarname, m->path_factor->variable) == 0)
 			{
 				found_mapping = m;
 				break;
@@ -863,13 +967,14 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
 		if (!found_mapping)
 			elog(ERROR, "undefined element variable \"%s\"", gpr->elvarname);
 
+		mapping_factor = found_mapping->path_factor;
+
 		/*
 		 * Find property definition for given element through any of the
 		 * associated labels.
 		 */
-		foreach(lc, found_mapping->labeloids)
+		foreach_oid(labeloid, mapping_factor->labeloids)
 		{
-			Oid			labeloid = lfirst_oid(lc);
 			Oid			elem_labelid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
 													   Anum_pg_propgraph_element_label_oid,
 													   ObjectIdGetDatum(found_mapping->elemoid),
@@ -892,7 +997,7 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
 
 				n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
 																			tup, Anum_pg_propgraph_label_property_plpexpr)));
-				ChangeVarNodes(n, 1, found_mapping->rtindex, 0);
+				ChangeVarNodes(n, 1, mapping_factor->factorpos + 1, 0);
 
 				ReleaseSysCache(tup);
 			}
@@ -910,12 +1015,11 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
 		/* See if we can resolve the property in some other way. */
 		if (!n)
 		{
-			ListCell   *lcu;
 			bool		prop_associated = false;
 
-			foreach(lcu, unrelated_labels)
+			foreach_oid(loid, unrelated_labels)
 			{
-				if (is_property_associated_with_label(lfirst_oid(lcu), gpr->propid))
+				if (is_property_associated_with_label(loid, gpr->propid))
 				{
 					prop_associated = true;
 					break;
@@ -941,7 +1045,7 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
 				 * rows to the GRAPH_TABLE.
 				 */
 				n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
-											  found_mapping->rtindex);
+											  mapping_factor->factorpos + 1);
 
 				if (!n)
 				{
@@ -954,7 +1058,7 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
 
 		if (!n)
 			elog(ERROR, "property \"%s\" of element variable \"%s\" not found",
-				 get_propgraph_property_name(gpr->propid), found_mapping->parent_gep->variable);
+				 get_propgraph_property_name(gpr->propid), mapping_factor->variable);
 
 		return n;
 	}
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index de0d163e83..87ab3e31af 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -349,14 +349,6 @@ select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is
  v11 | e132 | v31  | vl3_prop |        |   2010
 (4 rows)
 
--- WHERE clause in graph pattern
-SELECT self, through FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname and a.vname <> b.vname COLUMNS (a.vname as self, b.vname as through));
- self | through 
-------+---------
- v12  | v21
- v21  | v12
-(2 rows)
-
 -- Errors
 -- vl1 is not associated with property vprop2
 select src, src_vprop2, conn, dest from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, a.vprop2 as src_vprop2, b.ename as conn, c.vname as dest));
@@ -420,6 +412,114 @@ select sn, cn, dn from graph_table (g1 match (src : l1)-[conn : l1]->(dest : l1)
  v22 | e231 | v32
 (6 rows)
 
+-- Tests for cyclic graph patterns
+-- Add some more cycles in graph
+CREATE TABLE e3_2 (id_3 int,
+                    id_2_1 int,
+                    id_2_2 int,
+                    ename varchar(10),
+                    eprop1 int);
+ALTER PROPERTY GRAPH g1 ADD EDGE TABLES (
+    e3_2 KEY (id_3, id_2_1, id_2_2)
+        SOURCE KEY (id_3) REFERENCES v3 (id)
+        DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+        LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+        LABEL l1 PROPERTIES (ename AS elname)
+);
+INSERT INTO e1_2 VALUES (3, 1000, 3, 'e123', 10007);
+INSERT INTO e2_1 VALUES (1000, 3, 3, 'e212', 10008);
+INSERT INTO e3_2 VALUES (2002, 1000, 2, 'e321', 10009);
+-- cyclic pattern using WHERE clause in graph pattern,
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v21  | v12     |    1010 |         20
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+ v32  | v22     |    2020 |       1020
+(6 rows)
+
+-- cyclic pattern using elements with same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v21  | v12     |    1010 |         20
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+ v32  | v22     |    2020 |       1020
+(6 rows)
+
+-- cyclic pattern with WHERE clause
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a where a.vprop1 < 2000)->(b where b.vprop1 > 20)->(a where a.vprop1 > 20) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v13  | v23     |      30 |       1030
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+(3 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b where b.vprop1 > 20)->(a where a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+(4 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a where a.vprop1 between 20 and 2000)->(b where b.vprop1 > 20)->(a where a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+(4 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a is l1)-[a is l1]->(b is l1) columns (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
+ERROR:  element patterns with same variable name "a" but different element pattern types
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a is vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;  -- error
+ERROR:  element patterns with same variable name "a" but different label expressions
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a is vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+(2 rows)
+
+-- add an edge with same vertex as source and destination to test loops
+CREATE TABLE e3_3 (src_id int,
+                    dest_id int,
+                    ename varchar(10),
+                    eprop1 int);
+ALTER PROPERTY GRAPH g1 ADD EDGE TABLES (
+    e3_3 KEY (src_id, dest_id)
+        SOURCE KEY (src_id) REFERENCES v3 (id)
+        DESTINATION KEY (src_id) REFERENCES v3 (id)
+        LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+        LABEL l1 PROPERTIES (ename AS elname)
+);
+INSERT INTO e3_3 VALUES (2003, 2003, 'e331', 10010);
+-- cyclic pattern with edge patterns with same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS aname, b.ename AS bname, c.vname AS cname, d.vname AS dname)); --error
+ERROR:  An edge can not connect more than two vertexes even in a cyclic pattern.
 -- property graph with some of the elements, labels and properties same as the
 -- previous one. Test whether components from the specified property graph are
 -- used.
@@ -451,10 +551,11 @@ select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1)
 --------+---------+--------
  g2.v12 | g2.e122 | g2.v21
  g2.v11 | g2.e121 | g2.v22
+ g2.v13 | g2.e123 | g2.v23
  g2.v11 | g2.e131 | g2.v33
  g2.v11 | g2.e132 | g2.v31
  g2.v22 | g2.e231 | g2.v32
-(5 rows)
+(6 rows)
 
 CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));
 SELECT pg_get_viewdef('customers_us'::regclass);
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 486594a993..f34616163a 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -262,8 +262,6 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname,
 a.vprop1));
 -- vprop2 is associated with vl2 but not vl3
 select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, b.ename as conn, c.vname as dest, c.lprop1, c.vprop2, c.vprop1));
--- WHERE clause in graph pattern
-SELECT self, through FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname and a.vname <> b.vname COLUMNS (a.vname as self, b.vname as through));
 
 -- Errors
 -- vl1 is not associated with property vprop2
@@ -289,6 +287,56 @@ select vn from all_vertices except (select svn from all_connected_vertices union
 -- query all connections using a label shared by vertices and edges
 select sn, cn, dn from graph_table (g1 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn));
 
+-- Tests for cyclic graph patterns
+-- Add some more cycles in graph
+CREATE TABLE e3_2 (id_3 int,
+                    id_2_1 int,
+                    id_2_2 int,
+                    ename varchar(10),
+                    eprop1 int);
+ALTER PROPERTY GRAPH g1 ADD EDGE TABLES (
+    e3_2 KEY (id_3, id_2_1, id_2_2)
+        SOURCE KEY (id_3) REFERENCES v3 (id)
+        DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+        LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+        LABEL l1 PROPERTIES (ename AS elname)
+);
+
+INSERT INTO e1_2 VALUES (3, 1000, 3, 'e123', 10007);
+INSERT INTO e2_1 VALUES (1000, 3, 3, 'e212', 10008);
+INSERT INTO e3_2 VALUES (2002, 1000, 2, 'e321', 10009);
+
+-- cyclic pattern using WHERE clause in graph pattern,
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+-- cyclic pattern using elements with same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+-- cyclic pattern with WHERE clause
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a where a.vprop1 < 2000)->(b where b.vprop1 > 20)->(a where a.vprop1 > 20) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b where b.vprop1 > 20)->(a where a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a where a.vprop1 between 20 and 2000)->(b where b.vprop1 > 20)->(a where a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a is l1)-[a is l1]->(b is l1) columns (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a is vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;  -- error
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a is vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+
+-- add an edge with same vertex as source and destination to test loops
+CREATE TABLE e3_3 (src_id int,
+                    dest_id int,
+                    ename varchar(10),
+                    eprop1 int);
+ALTER PROPERTY GRAPH g1 ADD EDGE TABLES (
+    e3_3 KEY (src_id, dest_id)
+        SOURCE KEY (src_id) REFERENCES v3 (id)
+        DESTINATION KEY (src_id) REFERENCES v3 (id)
+        LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+        LABEL l1 PROPERTIES (ename AS elname)
+);
+
+INSERT INTO e3_3 VALUES (2003, 2003, 'e331', 10010);
+-- cyclic pattern with edge patterns with same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS aname, b.ename AS bname, c.vname AS cname, d.vname AS dname)); --error
+
 -- property graph with some of the elements, labels and properties same as the
 -- previous one. Test whether components from the specified property graph are
 -- used.
-- 
2.34.1

From a74641fbe04ae80f09ea41e5c71ef9c63c1a288c Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 21 Aug 2024 19:41:48 +0530
Subject: [PATCH 8/8] Fixes following issues

1. A query containing GRAPH_TABLE reference used in a UDF causes segmentation
fault: Earlier version of patches used p_post_columnref_hook to transform a
property reference in a GRAPH_TABLE reference. But related hooks and members in
ParseState are used by other non-core modules like plpgsql. If a query inside a
plpgsql function has GRAPH_TABLE reference in it the usage of these hooks
conflicts. Since GRAPH_TABLE is a core feature, it's better to introduce a new
member ParseState::p_graph_table_pstate, of type GraphTableParseState, to hold
the namespace for the GRAPH_TABLE reference being transformed. It is inline
with ParseState::p_parent_cte or ParseState::p_queryEnv which have similar
purposes. With that change definition of GraphTableParseState is moved to
parsenodes.h where CommonTableExpr and QueryEnvironment are also defined. The
function using the ParseState::p_graph_table_pstate is renamed to
transformGraphTablePropertyRef() to be inline with names of other similar
functions. This change also allows GraphTableParseState argument to be removed
from a few functions.

2. ERROR:  unrecognized lock mode: 0 via ScanQueryForLocks(): After fixing the
first bug, I ran into above error. An RTE_GRAPH_TABLE is converted to
RTE_SUBQUERY after transformation with relid = OID of the property graph being
referenced in GRAPH_TABLE clause. Functions called from ScanQueryForLocks()
take locks when RangeTblRef::relid is valid; which is desirable since we want
to lock the property graph while it's being used. So set
RangeTblRef::rellockmode to AccessShareLock.

3. Prohibits subquery within GRAPH_TABLE reference: In order to support it the
hasSublinks flag needs to be transferred to the queries produced as a result of
rewriting graph table RTE. It needs a bit of code and some non-trivial testing.
So not done right now.

4. Support edge patterns in any direction: Such edge patterns satisfy edges in
either direction.

Additional changes
1. Test for a join between GRAPH_TABLE and a regular table.
2. Changed a few loops over lists to use foreach_node reducing and simplifying
code a bit.
3. Improved comments.

Author: Ashutosh Bapat
Reported By: Ajay Pal
---
 src/backend/parser/parse_clause.c         |  30 ++++-
 src/backend/parser/parse_expr.c           |   5 +
 src/backend/parser/parse_graphtable.c     |  43 +++----
 src/backend/parser/parse_relation.c       |   1 +
 src/backend/rewrite/rewriteGraphTable.c   | 148 +++++++++++++---------
 src/include/nodes/parsenodes.h            |   6 +
 src/include/parser/parse_graphtable.h     |  11 +-
 src/include/parser/parse_node.h           |   5 +
 src/test/regress/expected/graph_table.out |  81 +++++++++++-
 src/test/regress/sql/graph_table.sql      |  45 +++++++
 10 files changed, 277 insertions(+), 98 deletions(-)

diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 63f5dd5556..923f8b3fbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -917,6 +917,7 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 	List	   *colnames = NIL;
 	ListCell   *lc;
 	int			resno = 0;
+	bool		saved_hasSublinks;
 
 	rel = parserOpenTable(pstate, rgt->graph_name, AccessShareLock);
 	if (rel->rd_rel->relkind != RELKIND_PROPGRAPH)
@@ -930,12 +931,21 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 
 	gpstate->graphid = graphid;
 
-	pstate->p_post_columnref_hook = graph_table_property_reference;
-	pstate->p_ref_hook_state = gpstate;
+	/*
+	 * The syntax does not allow nested GRAPH_TABLE and this function
+	 * prohibits subquery within GRAPH_TABLE. There should be only one
+	 * GRAPH_TABLE being transformed at a time.
+	 */
+	Assert(!pstate->p_graph_table_pstate);
+	pstate->p_graph_table_pstate = gpstate;
+
 	Assert(!pstate->p_lateral_active);
 	pstate->p_lateral_active = true;
 
-	gp = transformGraphPattern(pstate, gpstate, rgt->graph_pattern);
+	saved_hasSublinks = pstate->p_hasSubLinks;
+	pstate->p_hasSubLinks = false;
+
+	gp = transformGraphPattern(pstate, rgt->graph_pattern);
 
 	foreach(lc, rgt->columns)
 	{
@@ -970,10 +980,20 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 
 	table_close(rel, NoLock);
 
-	pstate->p_pre_columnref_hook = NULL;
-	pstate->p_ref_hook_state = NULL;
+	pstate->p_graph_table_pstate = NULL;
 	pstate->p_lateral_active = false;
 
+	/*
+	 * If we support subqueries within GRAPH_TABLE, those need to be
+	 * propagated to the queries resulting from rewriting graph table RTE. We
+	 * don't do that right now, hence prohibit it for now.
+	 */
+	if (pstate->p_hasSubLinks)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("subqueries within GRAPH_TABLE reference are not supported")));
+	pstate->p_hasSubLinks = saved_hasSublinks;
+
 	return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true);
 }
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 38222e745e..2c1e74774d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -30,6 +30,7 @@
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_func.h"
+#include "parser/parse_graphtable.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
@@ -825,6 +826,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			break;
 	}
 
+	/* Try it as a graph table property reference. */
+	if (node == NULL)
+		node = transformGraphTablePropertyRef(pstate, cref);
+
 	/*
 	 * Now give the PostParseColumnRefHook, if any, a chance.  We pass the
 	 * translation-so-far so that it can throw an error if it wishes in the
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index 1a97168702..b088306b5b 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -33,12 +33,15 @@

 /*
- * Resolve a property reference.
+ * Transform a property reference.
  */
 Node *
-graph_table_property_reference(ParseState *pstate, ColumnRef *cref, Node *var)
+transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
 {
-	GraphTableParseState *gpstate = pstate->p_ref_hook_state;
+	GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
+
+	if (!gpstate)
+		return NULL;
 
 	if (list_length(cref->fields) == 2)
 	{
@@ -144,8 +147,10 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
  * Transform a GraphElementPattern.
  */
 static Node *
-transformGraphElementPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphElementPattern *gep)
+transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
 {
+	GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
+
 	if (gep->variable)
 		gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable)));
 
@@ -161,17 +166,13 @@ transformGraphElementPattern(ParseState *pstate, GraphTableParseState *gpstate,
  * Transform a path term (list of GraphElementPattern's).
  */
 static Node *
-transformPathTerm(ParseState *pstate, GraphTableParseState *gpstate, List *path_term)
+transformPathTerm(ParseState *pstate, List *path_term)
 {
 	List	   *result = NIL;
-	ListCell   *lc;
-
-	foreach(lc, path_term)
-	{
-		Node	   *n = transformGraphElementPattern(pstate, gpstate, lfirst_node(GraphElementPattern, lc));
 
-		result = lappend(result, n);
-	}
+	foreach_node(GraphElementPattern, gep, path_term)
+		result = lappend(result,
+						 transformGraphElementPattern(pstate, gep));
 
 	return (Node *) result;
 }
@@ -180,17 +181,12 @@ transformPathTerm(ParseState *pstate, GraphTableParseState *gpstate, List *path_
  * Transform a path pattern list (list of path terms).
  */
 static Node *
-transformPathPatternList(ParseState *pstate, GraphTableParseState *gpstate, List *path_pattern)
+transformPathPatternList(ParseState *pstate, List *path_pattern)
 {
 	List	   *result = NIL;
-	ListCell   *lc;
 
-	foreach(lc, path_pattern)
-	{
-		Node	   *n = transformPathTerm(pstate, gpstate, lfirst(lc));
-
-		result = lappend(result, n);
-	}
+	foreach_node(List, path_term, path_pattern)
+		result = lappend(result, transformPathTerm(pstate, path_term));
 
 	return (Node *) result;
 }
@@ -199,9 +195,12 @@ transformPathPatternList(ParseState *pstate, GraphTableParseState *gpstate, List
  * Transform a GraphPattern.
  */
 Node *
-transformGraphPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphPattern *graph_pattern)
+transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern)
 {
-	graph_pattern->path_pattern_list = (List *) transformPathPatternList(pstate, gpstate, graph_pattern->path_pattern_list);
+	List	   *path_pattern_list = castNode(List,
+											 transformPathPatternList(pstate, graph_pattern->path_pattern_list));
+
+	graph_pattern->path_pattern_list = path_pattern_list;
 	graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_WHERE);
 	assign_expr_collations(pstate, graph_pattern->whereClause);
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 5ea9fa993b..d222c958c1 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2154,6 +2154,7 @@ addRangeTableEntryForGraphTable(ParseState *pstate,
 	rte->graph_pattern = graph_pattern;
 	rte->graph_table_columns = columns;
 	rte->alias = alias;
+	rte->rellockmode = AccessShareLock;
 
 	eref = alias ? copyObject(alias) : makeAlias(refname, NIL);
 
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 6e2e6ed38b..1ea5a5caeb 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -41,12 +41,11 @@
 /*
  * Represents one path factor in a path.
  *
- * One path factor corresponds to one element pattern in a simple path.
+ * In a non-cyclic path, one path factor corresponds to one element pattern.
  *
  * In a cyclic path, one path factor may correspond to one or more element
- * patterns all pointing to the same graph element. The members of such a path
- * factor are a combination of corresponding specifications in the element
- * patterns.
+ * patterns sharing the same variable name, thus pointing to the same graph
+ * element.
  */
 struct path_factor
 {
@@ -66,17 +65,20 @@ struct path_factor
  * Represents one property graph element (vertex or edge) in the path.
  *
  * Label expression in an element pattern resolves into a set of elements. For
- * each of those elements we create one graph_path_element object.
+ * each of those elements we create one path_element object.
  */
 struct path_element
 {
+	/* Path factor from which this element is derived. */
+	struct path_factor *path_factor;
 	Oid			elemoid;
 	Oid			reloid;
+	/* Source and destination vertex elements for an edge element. */
 	Oid			srcvertexid;
 	Oid			destvertexid;
-	List	   *qual_exprs;
-	/* Path factor from which this element is derived. */
-	struct path_factor *path_factor;
+	/* Source and destination conditions for an edge element. */
+	List	   *src_quals;
+	List	   *dest_quals;
 };
 
 static Node *replace_property_refs(Oid propgraphid, Node *node, const List *mappings);
@@ -187,23 +189,22 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 	/*
 	 * For every element pattern in the given path pattern collect all the
 	 * graph elements that satisfy the element pattern.
+	 *
+	 * Element patterns with the same name represent the same element and
+	 * hence same path factor. They do not add a new graph element to the
+	 * query but affect the links of adjacent elements. Merge such elements
+	 * patterns into a single path factor.
 	 */
 	foreach_node(GraphElementPattern, gep, path_pattern)
 	{
 		struct path_factor *pf = NULL;
 
-		if (gep->kind != VERTEX_PATTERN &&
-			gep->kind != EDGE_PATTERN_LEFT && gep->kind != EDGE_PATTERN_RIGHT)
-			elog(ERROR, "unsupported element pattern kind: %s", get_gep_kind_name(gep->kind));
+		if (gep->kind != VERTEX_PATTERN && !IS_EDGE_PATTERN(gep->kind))
+			elog(ERROR, "unsupported element pattern kind: \"%s\"", get_gep_kind_name(gep->kind));
 
 		if (gep->quantifier)
 			elog(ERROR, "element pattern quantifier not supported yet");
 
-		/*
-		 * Element patterns with the same name represent the same element and
-		 * hence same path factor. They do not add a new graph element to the
-		 * query but affect the links of adjacent elements.
-		 */
 		foreach_ptr(struct path_factor, other, path_factors)
 		{
 			if (gep->variable && other->variable &&
@@ -227,12 +228,14 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 					other->labelexpr = gep->labelexpr;
 				else if (gep->labelexpr && !equal(other->labelexpr, gep->labelexpr))
 					ereport(ERROR,
-					/* XXX: Use correct error code. */
-							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-							 errmsg("element patterns with same variable name \"%s\" but different label expressions",
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("element patterns with same variable name \"%s\" but different label expressions are not supported",
 									gep->variable)));
 
-				/* Both sets of conditions apply to the element pattern. */
+				/*
+				 * Conditions from both elements patterns constrain the graph
+				 * element. Combine by ANDing them.
+				 */
 				if (!other->whereClause)
 					other->whereClause = gep->whereClause;
 				else if (gep->whereClause)
@@ -259,19 +262,27 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 		}
 
 		/*
-		 * Setup adjacent path factors. If multiple edge patterns share the
-		 * same variable name, they constain the adjacent vertex patterns
-		 * since an edge can connect only one pair of vertexes and those
-		 * vertexes also need to repeated along with the edge (a walk). This
-		 * means that we have to coalesce the vertex patterns adjacent to a
-		 * repeated edge even though they have different variables.  E.g.
-		 * (a)-[b]->(c)-[b]<-(d) implies that (a) and (d) represent the same
+		 * Setup links to the previous path factor. If the previous path
+		 * factor is an edge, this path factor represents an adjacent vertex;
+		 * source vertex for an edge pointing left or destination vertex for
+		 * an edge pointing right. Edge pointing in any direction is treated
+		 * similar to that pointing in right direction here. When constructing
+		 * a query, in generate_query_for_graph_path() we will swap source and
+		 * destination elements if the edge element turns out to be and edge
+		 * pointing in left direction.
+		 *
+		 * If multiple edge patterns share the same variable name, they
+		 * constain the adjacent vertex patterns since an edge can connect
+		 * only one pair of vertexes. Those vertex patterns also need to
+		 * repeated and merged along with the repeated edge (a walk of graph)
+		 * even though they have different variables.  E.g.
+		 * (a)-[b]->(c)<-[b]-(d) implies that (a) and (d) represent the same
 		 * vertex element pattern. This is slighly harder to implement and
 		 * probably less useful. Hence not supported for now.
 		 */
 		if (prev_pf)
 		{
-			if (prev_pf->kind == EDGE_PATTERN_RIGHT)
+			if (prev_pf->kind == EDGE_PATTERN_RIGHT || prev_pf->kind == EDGE_PATTERN_ANY)
 			{
 				Assert(!IS_EDGE_PATTERN(pf->kind));
 				if (prev_pf->dest_pf && prev_pf->dest_pf != pf)
@@ -285,13 +296,8 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 					elog(ERROR, "An edge can not connect more than two vertexes even in a cyclic pattern.");
 				prev_pf->src_pf = pf;
 			}
-			else if (prev_pf->kind == EDGE_PATTERN_ANY)
-			{
-				/* We don't support undirected edges yet. */
-				Assert(false);
-			}
 
-			if (pf->kind == EDGE_PATTERN_RIGHT)
+			if (pf->kind == EDGE_PATTERN_RIGHT || pf->kind == EDGE_PATTERN_ANY)
 			{
 				Assert(!IS_EDGE_PATTERN(prev_pf->kind));
 				if (pf->src_pf && pf->src_pf != prev_pf)
@@ -305,11 +311,6 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 					elog(ERROR, "An edge can not connect more than two vertexes even in a cyclic pattern.");
 				pf->dest_pf = prev_pf;
 			}
-			else if (pf->kind == EDGE_PATTERN_ANY)
-			{
-				/* We don't support undirected edges yet. */
-				Assert(false);
-			}
 		}
 
 		prev_pf = pf;
@@ -389,31 +390,62 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 		Relation	rel;
 		ParseNamespaceItem *pni;
 
-		Assert(pf->kind == VERTEX_PATTERN ||
-			   pf->kind == EDGE_PATTERN_LEFT || pf->kind == EDGE_PATTERN_RIGHT);
+		Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
 
-		if (pf->kind == EDGE_PATTERN_LEFT || pf->kind == EDGE_PATTERN_RIGHT)
+		/* Add conditions representing edge connnections. */
+		if (IS_EDGE_PATTERN(pf->kind))
 		{
 			struct path_element *src_pe;
-			struct path_element *dest_ge;
+			struct path_element *dest_pe;
+			List	   *src_quals;
+			List	   *dest_quals;
 
 			Assert(pf->src_pf && pf->dest_pf);
 			src_pe = list_nth(graph_path, pf->src_pf->factorpos);
-			dest_ge = list_nth(graph_path, pf->dest_pf->factorpos);
+			dest_pe = list_nth(graph_path, pf->dest_pf->factorpos);
 
 			/* Make sure that the links of adjacent vertices are correct. */
 			Assert(pf->src_pf == src_pe->path_factor &&
-				   pf->dest_pf == dest_ge->path_factor);
+				   pf->dest_pf == dest_pe->path_factor);
 
 			/*
 			 * If the given edge element does not connect the adjacent vertex
 			 * elements in this path, the path is broken. Abandon this path as
 			 * it won't return any rows.
+			 *
+			 * For an edge element pattern pointing in any direction, try
+			 * swapping the source and destination vertex elements.
 			 */
 			if (src_pe->elemoid != pe->srcvertexid ||
-				dest_ge->elemoid != pe->destvertexid)
-				return NULL;
+				dest_pe->elemoid != pe->destvertexid)
+			{
+				if (pf->kind == EDGE_PATTERN_ANY &&
+					dest_pe->elemoid == pe->srcvertexid &&
+					src_pe->elemoid == pe->destvertexid)
+				{
+					dest_quals = copyObject(pe->src_quals);
+					src_quals = copyObject(pe->dest_quals);
+
+					/* Swap the source and destination varnos in the quals. */
+					ChangeVarNodes((Node *) dest_quals, pe->path_factor->src_pf->factorpos + 1,
+								   pe->path_factor->dest_pf->factorpos + 1, 0);
+					ChangeVarNodes((Node *) src_quals, pe->path_factor->dest_pf->factorpos + 1,
+								   pe->path_factor->src_pf->factorpos + 1, 0);
+				}
+				else
+					return NULL;
+			}
+			else
+			{
+				src_quals = copyObject(pe->src_quals);
+				dest_quals = copyObject(pe->dest_quals);
+			}
+
+			qual_exprs = list_concat(qual_exprs, src_quals);
+			qual_exprs = list_concat(qual_exprs, dest_quals);
 		}
+		else
+			Assert(!pe->src_quals && !pe->dest_quals);
 
 		/* Create RangeTblEntry for this element table. */
 		rel = table_open(pe->reloid, AccessShareLock);
@@ -442,7 +474,6 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 
 			qual_exprs = lappend(qual_exprs, tr);
 		}
-		qual_exprs = list_concat(qual_exprs, pe->qual_exprs);
 	}
 
 	if (rte->graph_pattern->whereClause)
@@ -457,7 +488,7 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 	path_query->jointree = makeFromExpr(fromlist,
 										(Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1));
 
-	/* Each path query projects the columns specified in the GRAH_TABLE clause */
+	/* Each path query projects the COLUMNS specified in the GRAH_TABLE. */
 	path_query->targetList = castNode(List,
 									  replace_property_refs(rte->relid,
 															(Node *) rte->graph_table_columns,
@@ -667,7 +698,6 @@ create_gpe_for_element(struct path_factor *pf, Oid elemoid)
 	pe->path_factor = pf;
 	pe->elemoid = elemoid;
 	pe->reloid = pgeform->pgerelid;
-	pe->qual_exprs = NIL;
 
 	/*
 	 * When the path containing this element will be converted into a query
@@ -681,8 +711,6 @@ create_gpe_for_element(struct path_factor *pf, Oid elemoid)
 	 */
 	if (IS_EDGE_PATTERN(pf->kind))
 	{
-		List	   *edge_qual;
-
 		pe->srcvertexid = pgeform->pgesrcvertexid;
 		pe->destvertexid = pgeform->pgedestvertexid;
 		Assert(pf->src_pf && pf->dest_pf);
@@ -700,14 +728,12 @@ create_gpe_for_element(struct path_factor *pf, Oid elemoid)
 		 * number of paths this element appears in, fetching the catalog entry
 		 * each time.
 		 */
-		edge_qual = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->src_pf->factorpos + 1,
-												 Anum_pg_propgraph_element_pgesrckey,
-												 Anum_pg_propgraph_element_pgesrcref);
-		pe->qual_exprs = list_concat(pe->qual_exprs, edge_qual);
-		edge_qual = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->dest_pf->factorpos + 1,
-												 Anum_pg_propgraph_element_pgedestkey,
-												 Anum_pg_propgraph_element_pgedestref);
-		pe->qual_exprs = list_concat(pe->qual_exprs, edge_qual);
+		pe->src_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->src_pf->factorpos + 1,
+													 Anum_pg_propgraph_element_pgesrckey,
+													 Anum_pg_propgraph_element_pgesrcref);
+		pe->dest_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->dest_pf->factorpos + 1,
+													  Anum_pg_propgraph_element_pgedestkey,
+													  Anum_pg_propgraph_element_pgedestref);
 	}
 
 	ReleaseSysCache(eletup);
@@ -727,7 +753,7 @@ get_gep_kind_name(GraphElementPatternKind gepkind)
 		case EDGE_PATTERN_RIGHT:
 			return "edge pointing right";
 		case EDGE_PATTERN_ANY:
-			return "undirected edge";
+			return "edge pointing any direction";
 		case PAREN_EXPR:
 			return "nested path pattern";
 	}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c591b566e2..3d5e5a82f0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -4359,4 +4359,10 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct GraphTableParseState
+{
+	Oid			graphid;
+	List	   *variables;
+} GraphTableParseState;
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h
index af0f550cd2..4cefd5acf9 100644
--- a/src/include/parser/parse_graphtable.h
+++ b/src/include/parser/parse_graphtable.h
@@ -14,18 +14,11 @@
 #ifndef PARSE_GRAPHTABLE_H
 #define PARSE_GRAPHTABLE_H
 
-#include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
 #include "parser/parse_node.h"
 
-typedef struct GraphTableParseState
-{
-	Oid			graphid;
-	List	   *variables;
-} GraphTableParseState;
+extern Node *transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref);
 
-extern Node *graph_table_property_reference(ParseState *pstate, ColumnRef *cref, Node *var);
-
-extern Node *transformGraphPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphPattern *graph_pattern);
+extern Node *transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern);
 
 #endif							/* PARSE_GRAPHTABLE_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 3246fbe156..949ab1a494 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -177,6 +177,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * p_resolve_unknowns: resolve unknown-type SELECT output columns as type TEXT
  * (this is true by default).
  *
+ * p_graph_table_pstate: Namespace for the GRAPH_TABLE reference being
+ * transformed, if any.
+ *
  * p_hasAggs, p_hasWindowFuncs, etc: true if we've found any of the indicated
  * constructs in the query.
  *
@@ -219,6 +222,8 @@ struct ParseState
 									 * type text */
 
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
+	GraphTableParseState *p_graph_table_pstate; /* Current graph table
+												 * namespace, if any */
 
 	/* Flags telling about things found in the query: */
 	bool		p_hasAggs;
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 87ab3e31af..3796297cb0 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -349,6 +349,25 @@ select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is
  v11 | e132 | v31  | vl3_prop |        |   2010
 (4 rows)
 
+-- edges directed in both ways - to and from v2
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl2)-[conn]-(dest) COLUMNS (src.vname AS sname, conn.ename AS cname, dest.vname AS dname));
+ sname | cname | dname 
+-------+-------+-------
+ v21   | e122  | v12
+ v22   | e121  | v11
+ v21   | e211  | v12
+ v22   | e231  | v32
+(4 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl2)-(dest) COLUMNS (src.vname AS sname, dest.vname AS dname));
+ sname | dname 
+-------+-------
+ v21   | v12
+ v22   | v11
+ v21   | v12
+ v22   | v32
+(4 rows)
+
 -- Errors
 -- vl1 is not associated with property vprop2
 select src, src_vprop2, conn, dest from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, a.vprop2 as src_vprop2, b.ename as conn, c.vname as dest));
@@ -483,7 +502,7 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a where a.vprop1 between 20 and 2000)->(b w
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a is l1)-[a is l1]->(b is l1) columns (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
 ERROR:  element patterns with same variable name "a" but different element pattern types
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a is vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;  -- error
-ERROR:  element patterns with same variable name "a" but different label expressions
+ERROR:  element patterns with same variable name "a" but different label expressions are not supported
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
  self | through | self_p1 | through_p1 
 ------+---------+---------+------------
@@ -604,5 +623,65 @@ SELECT * FROM customers_us_redacted;
  redacted1
 (1 row)
 
+-- GRAPH_TABLE in UDFs
+CREATE FUNCTION out_degree(sname varchar) RETURNS varchar AS $$
+DECLARE
+    out_degree int;
+BEGIN
+    SELECT count(*) INTO out_degree
+        FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)->() COLUMNS (src.vname));
+    RETURN out_degree;
+END;
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION direct_connections(sname varchar)
+RETURNS TABLE (cname varchar, dname varchar)
+AS $$
+    SELECT cname, dname
+        FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)-[conn]->(dst)
+                             COLUMNS (conn.ename AS cname, dst.vname AS dname));
+$$ LANGUAGE SQL;
+SELECT sname, out_degree(sname) FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname));
+ sname | out_degree 
+-------+------------
+ v11   | 3
+ v12   | 1
+ v13   | 1
+(3 rows)
+
+SELECT sname, cname, dname
+    FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname)),
+         LATERAL direct_connections(sname);
+ sname | cname | dname 
+-------+-------+-------
+ v11   | e121  | v22
+ v11   | e131  | v33
+ v11   | e132  | v31
+ v12   | e122  | v21
+ v13   | e123  | v23
+(5 rows)
+
+-- GRAPH_TABLE joined to a regular table
+SELECT *
+    FROM customers co,
+         GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = co.address)-[IS customer_orders]->(o IS orders)
+                              COLUMNS (cg.name_redacted AS customer_name_redacted))
+    WHERE co.customer_id = 1;
+ customer_id |   name    | address | customer_name_redacted 
+-------------+-----------+---------+------------------------
+           1 | customer1 | US      | redacted1
+(1 row)
+
+-- query within graph table
+SELECT sname, dname
+    FROM GRAPH_TABLE (g1 MATCH (src)->(dest)
+                         WHERE src.vprop1 > (SELECT max(v1.vprop1) FROM v1)
+                         COLUMNS(src.vname as sname, dest.vname as dname));
+ERROR:  subqueries within GRAPH_TABLE reference are not supported
+SELECT sname, dname
+    FROM GRAPH_TABLE (g1 MATCH (src)->(dest)
+                         WHERE out_degree(src.vname) > (SELECT max(out_degree(nname))
+                                                            FROM GRAPH_TABLE (g1 MATCH (node) COLUMNS (node.vname AS nname)))
+                         COLUMNS(src.vname as sname, dest.vname as dname));
+ERROR:  subqueries within GRAPH_TABLE reference are not supported
 -- leave for pg_upgrade/pg_dump tests
 --DROP SCHEMA graph_table_tests CASCADE;
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index f34616163a..905be9df01 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -262,6 +262,9 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname,
 a.vprop1));
 -- vprop2 is associated with vl2 but not vl3
 select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, b.ename as conn, c.vname as dest, c.lprop1, c.vprop2, c.vprop1));
+-- edges directed in both ways - to and from v2
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl2)-[conn]-(dest) COLUMNS (src.vname AS sname, conn.ename AS cname, dest.vname AS dname));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl2)-(dest) COLUMNS (src.vname AS sname, dest.vname AS dname));
 
 -- Errors
 -- vl1 is not associated with property vprop2
@@ -394,5 +397,47 @@ CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE (myshop2 MATCH (c
 
 SELECT * FROM customers_us_redacted;
 
+-- GRAPH_TABLE in UDFs
+CREATE FUNCTION out_degree(sname varchar) RETURNS varchar AS $$
+DECLARE
+    out_degree int;
+BEGIN
+    SELECT count(*) INTO out_degree
+        FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)->() COLUMNS (src.vname));
+    RETURN out_degree;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION direct_connections(sname varchar)
+RETURNS TABLE (cname varchar, dname varchar)
+AS $$
+    SELECT cname, dname
+        FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)-[conn]->(dst)
+                             COLUMNS (conn.ename AS cname, dst.vname AS dname));
+$$ LANGUAGE SQL;
+
+SELECT sname, out_degree(sname) FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname));
+SELECT sname, cname, dname
+    FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname)),
+         LATERAL direct_connections(sname);
+
+-- GRAPH_TABLE joined to a regular table
+SELECT *
+    FROM customers co,
+         GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = co.address)-[IS customer_orders]->(o IS orders)
+                              COLUMNS (cg.name_redacted AS customer_name_redacted))
+    WHERE co.customer_id = 1;
+
+-- query within graph table
+SELECT sname, dname
+    FROM GRAPH_TABLE (g1 MATCH (src)->(dest)
+                         WHERE src.vprop1 > (SELECT max(v1.vprop1) FROM v1)
+                         COLUMNS(src.vname as sname, dest.vname as dname));
+SELECT sname, dname
+    FROM GRAPH_TABLE (g1 MATCH (src)->(dest)
+                         WHERE out_degree(src.vname) > (SELECT max(out_degree(nname))
+                                                            FROM GRAPH_TABLE (g1 MATCH (node) COLUMNS (node.vname AS nname)))
+                         COLUMNS(src.vname as sname, dest.vname as dname));
+
 -- leave for pg_upgrade/pg_dump tests
 --DROP SCHEMA graph_table_tests CASCADE;
-- 
2.34.1

---

From: Andreas Karlsson <[email-masked]>
Date: Tue, 29 Oct 2024 20:55:25 +0100

> found. The commit message describes the fixes in detail.

Hi,

I found a potential bug in the parsing of the left and right arrows. 
They can be broken up in - > and < - respectively. Does the SQL/PGQ 
standard really allow this?

I found this while working on a patch of our own and I was trying to 
figure out how you guys had solved this very same problem that we ran 
into, and if you had done so in a better way. The fundamental problem is 
that parsing the left arrow as one token is a bit tricky due to how 
PostgreSQL treats operators ending with minus or plus.

I have attached our very ugly solution for it (broken out from our 
patch) in case it helps you. Feel free to use it or ignore it. We do not 
plan to work on this right now since you are already working on the same 
problem.

I especially dislike the static variable in our patch. And as far as I 
understand it you can avoid the static by changing the lexer to use the 
push parser so it can emit multiple terminal tokens from one parsed 
token, but I have not looked into push parsers and have no idea how this 
would affect performance.

https://www.gnu.org/software/bison/manual/html_node/Push-Decl.html

Examples:

# SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 as one));
  count
-------
     32
(1 row)

# SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]- >() COLUMNS (1 as one));
  count
-------
     32
(1 row)

# SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS 
customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when));
    name    | ordered_when
-----------+--------------
  customer1 | 2024-01-01
  customer2 | 2024-01-02
(2 rows)

# SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)< -[IS 
customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when));
    name    | ordered_when
-----------+--------------
  customer1 | 2024-01-01
  customer2 | 2024-01-02
(2 rows)

Andreas
From 07c3e1908de413db9fa383165eac78df0a80ab50 Mon Sep 17 00:00:00 2001
From: Andreas Karlsson <[email-masked]>
Date: Tue, 29 Oct 2024 20:23:24 +0100
Subject: [PATCH] Broken out tokeniziation of arrows

---
 src/backend/parser/gram.y         | 20 ++++++++++++++---
 src/backend/parser/scan.l         | 37 +++++++++++++++++++++++++++++++
 src/fe_utils/psqlscan.l           |  5 +++++
 src/interfaces/ecpg/preproc/pgc.l | 34 ++++++++++++++++++++++++++++
 src/pl/plpgsql/src/pl_gram.y      |  1 +
 5 files changed, 94 insertions(+), 3 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dd458182f02..8f07a1c8c0c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -677,6 +677,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %token <ival>	ICONST PARAM
 %token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
 %token			LESS_EQUALS GREATER_EQUALS NOT_EQUALS
+%token			LEFT_ARROW_LESS LEFT_ARROW_MINUS RIGHT_ARROW
 
 /*
  * If you want to make any keyword changes, update the keyword table in
@@ -817,7 +818,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		AND
 %right		NOT
 %nonassoc	IS ISNULL NOTNULL	/* IS sets precedence for IS NULL, etc */
-%nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
+%nonassoc	'<' LEFT_ARROW_LESS '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
 
@@ -870,8 +871,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 			SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
-%left		Op OPERATOR		/* multi-character ops and user-defined operators */
-%left		'+' '-'
+%left		Op OPERATOR RIGHT_ARROW	/* multi-character ops and user-defined operators */
+%left		'+' '-' LEFT_ARROW_MINUS
 %left		'*' '/' '%'
 %left		'^'
 /* Unary Operators */
@@ -14842,6 +14843,8 @@ a_expr:		c_expr									{ $$ = $1; }
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "+", NULL, $2, @1); }
 			| '-' a_expr					%prec UMINUS
 				{ $$ = doNegate($2, @1); }
+			| LEFT_ARROW_MINUS a_expr		%prec UMINUS
+				{ $$ = doNegate($2, @1); }
 			| a_expr '+' a_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "+", $1, $3, @2); }
 			| a_expr '-' a_expr
@@ -14856,6 +14859,8 @@ a_expr:		c_expr									{ $$ = $1; }
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "^", $1, $3, @2); }
 			| a_expr '<' a_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", $1, $3, @2); }
+			| a_expr LEFT_ARROW_LESS a_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", $1, $3, @2); }
 			| a_expr '>' a_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $3, @2); }
 			| a_expr '=' a_expr
@@ -14866,6 +14871,8 @@ a_expr:		c_expr									{ $$ = $1; }
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
 			| a_expr NOT_EQUALS a_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
+			| a_expr RIGHT_ARROW a_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
 
 			| a_expr qual_Op a_expr				%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
@@ -15335,6 +15342,8 @@ b_expr:		c_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "^", $1, $3, @2); }
 			| b_expr '<' b_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", $1, $3, @2); }
+			| b_expr LEFT_ARROW_LESS b_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", $1, $3, @2); }
 			| b_expr '>' b_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $3, @2); }
 			| b_expr '=' b_expr
@@ -15345,6 +15354,8 @@ b_expr:		c_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
 			| b_expr NOT_EQUALS b_expr
 				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
+			| b_expr RIGHT_ARROW b_expr
+				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
 			| b_expr qual_Op b_expr				%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
 			| qual_Op b_expr					%prec Op
@@ -16503,16 +16514,19 @@ all_Op:		Op										{ $$ = $1; }
 
 MathOp:		 '+'									{ $$ = "+"; }
 			| '-'									{ $$ = "-"; }
+			| LEFT_ARROW_MINUS						{ $$ = "-"; }
 			| '*'									{ $$ = "*"; }
 			| '/'									{ $$ = "/"; }
 			| '%'									{ $$ = "%"; }
 			| '^'									{ $$ = "^"; }
 			| '<'									{ $$ = "<"; }
+			| LEFT_ARROW_LESS						{ $$ = "<"; }
 			| '>'									{ $$ = ">"; }
 			| '='									{ $$ = "="; }
 			| LESS_EQUALS							{ $$ = "<="; }
 			| GREATER_EQUALS						{ $$ = ">="; }
 			| NOT_EQUALS							{ $$ = "<>"; }
+			| RIGHT_ARROW							{ $$ = "->"; }
 		;
 
 qual_Op:	Op
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index 994ed9995ac..d7ed04011b0 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -366,6 +366,7 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+right_arrow		"->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -454,6 +455,10 @@ other			.
 
 %%
 
+%{
+		static bool inleftarrow = false;
+%}
+
 {whitespace}	{
 					/* ignore */
 				}
@@ -892,8 +897,18 @@ other			.
 					return NOT_EQUALS;
 				}
 
+{right_arrow}	{
+					SET_YYLLOC();
+					return RIGHT_ARROW;
+				}
+
 {self}			{
 					SET_YYLLOC();
+					if (yytext[0] == '-' && inleftarrow)
+					{
+						inleftarrow = false;
+						return LEFT_ARROW_MINUS;
+					}
 					return yytext[0];
 				}
 
@@ -919,6 +934,26 @@ other			.
 					if (slashstar)
 						nchars = slashstar - yytext;
 
+					if (nchars == 2 && yytext[0] == '<' && yytext[1] == '-')
+					{
+						/* Strip the unwanted chars from the token */
+						yyless(1);
+
+						inleftarrow = true;
+
+						return LEFT_ARROW_LESS;
+					}
+
+					if (nchars == 1 && yytext[0] == '-' && inleftarrow)
+					{
+						/* Strip the unwanted chars from the token */
+						if (nchars < yyleng)
+							yyless(nchars);
+
+						inleftarrow = false;
+						return LEFT_ARROW_MINUS;
+					}
+
 					/*
 					 * For SQL compatibility, '+' and '-' cannot be the
 					 * last char of a multi-char operator unless the operator
@@ -989,6 +1024,8 @@ other			.
 								return NOT_EQUALS;
 							if (yytext[0] == '!' && yytext[1] == '=')
 								return NOT_EQUALS;
+							if (yytext[0] == '-' && yytext[1] == '>')
+								return RIGHT_ARROW;
 						}
 					}
 
diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l
index 8e8b049e15f..6f8fd7cd258 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -302,6 +302,7 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+right_arrow     "->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -661,6 +662,10 @@ other			.
 					ECHO;
 				}
 
+{right_arrow}	{
+					ECHO;
+				}
+
 	/*
 	 * These rules are specific to psql --- they implement parenthesis
 	 * counting and detection of command-ending semicolon.  These must
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index f3c03482aec..d1fd12fe5af 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -335,6 +335,7 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+right_arrow		"->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -463,6 +464,8 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 %%
 
 %{
+		static bool inleftarrow = false;
+
 		/* code to execute during start of each call of yylex() */
 		char *newdefsymbol = NULL;
 
@@ -854,6 +857,10 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 					return NOT_EQUALS;
 				}
 
+{right_arrow}	{
+					return RIGHT_ARROW;
+				}
+
 {informix_special} {
 					/* are we simulating Informix? */
 					if (INFORMIX_MODE)
@@ -871,6 +878,11 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 					 */
 					if (yytext[0] == ';' && struct_level == 0)
 						BEGIN(C);
+					if (yytext[0] == '-' && inleftarrow)
+					{
+						inleftarrow = false;
+						return LEFT_ARROW_MINUS;
+					}
 					return yytext[0];
 				}
 
@@ -896,6 +908,26 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 					if (slashstar)
 						nchars = slashstar - yytext;
 
+					if (nchars == 2 && yytext[0] == '<' && yytext[1] == '-')
+					{
+						/* Strip the unwanted chars from the token */
+						yyless(1);
+
+						inleftarrow = true;
+
+						return LEFT_ARROW_LESS;
+					}
+
+					if (nchars == 1 && yytext[0] == '-' && inleftarrow)
+					{
+						/* Strip the unwanted chars from the token */
+						if (nchars < yyleng)
+							yyless(nchars);
+
+						inleftarrow = false;
+						return LEFT_ARROW_MINUS;
+					}
+
 					/*
 					 * For SQL compatibility, '+' and '-' cannot be the
 					 * last char of a multi-char operator unless the operator
@@ -968,6 +1000,8 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 								return NOT_EQUALS;
 							if (yytext[0] == '!' && yytext[1] == '=')
 								return NOT_EQUALS;
+							if (yytext[0] == '-' && yytext[1] == '>')
+								return RIGHT_ARROW;
 						}
 					}
 
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 8182ce28aa1..c5cea379554 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -237,6 +237,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <ival>	ICONST PARAM
 %token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
 %token			LESS_EQUALS GREATER_EQUALS NOT_EQUALS
+%token			LEFT_ARROW_LESS LEFT_ARROW_MINUS RIGHT_ARROW
 
 /*
2.45.2

---

From: Ashutosh Bapat <[email-masked]>
Date: Tue, 5 Nov 2024 21:11:24 +0530

> found. The commit message describes the fixes in detail.

Here's an updated patchset based on the latest HEAD.

0001
So far I was keeping the 0001 patch the same as the one Peter posted
at [1]. But it has accumulated few conflicts and compilation failures
over the months. Thus that patch by itself is not of much use. In this
patch set 0001 is that patch + fixes for conflicts, compilation
failures and perltidy adjustments accumulated over the months. This
allows that patch to be applied and tested independently. Commit
message describes the fixes.

0002 -  adds support for WHERE clause in graph pattern

0003 - adds full support for cyclic path patterns

0004 - fixes all the bugs reported by Ajay and also the one found by
me. Commit message describes all the issues

0005
Tests privileges with GRAPH_TABLE. Table level privileges are being
honoured by previous patches by creating approprate RTEPermissionInfos
for the underlying tables when rewriting GRAPH_TABLE entry. But those
patches do not honour column privileges. This patch adds the columns
used in the GRAPH_TABLE to RTEPermissionInfos::selectedCols to honour
column level privileges. Below are the access rules for property graph
and the tables underlying GRAPH_TABLE entry.

1. A user needs SELECT privilege to access a property graph in the query.
2. The property graph itself acts similar to a security invoker view
in the sense that the current_user should have privileges to access
the tables and columns, underlying the graph pattern. For example, a
property graph g1 contains graph element tables e1 and e2. Assume two
users u1 and u2 with privileges to access e1 and e2 respectively.
Let's also assume that they have privileges to access the property
graph g1. u1, as the current user, would be able to run query
involving g1 successfully as long as it does *not* require access to
e2.  Similarly u2 would be able to run a query involving g1
successfully as long as it does *not* require access to e1.
3. If a property graph is referenced in a security definer view,
access to the property graph is governed by the privileges of the
owner of the view but the access to the base relations underlying the
graph pattern are governed by the privileges of the user executing the
query. This is similar to how access to the base relations underlying
a security invoker view referenced from a security definer view is
handled. access
4. We have not implemented security definer property graphs since
SQL/PGQ standard does not mention those.

Per SQL/PGQ standard, a property graph can contain any relation as
long as it has SELECT privilege on that relation ("Access rules"
subsection of section 11.19 <property graph definition>". Current
implementation of property graph DDL does not allow a table owned by a
user other than the owner property graph to be added to the property
graph. I haven't changed that in this patch. But in case we implement
what the standard suggests, it will become much easier to add queries
to test privileges and access vs property graph containing views. So I
have left those test aside for now.

0006 - is a temporary fix to provide collation to the operator joining
edges to their adjacent tables. It is required for some tests added in
0007.

0007
Adds queries to test RLS against GRAPH_TABLE. rowsecurity.sql has an
extensive set of tests for RLS. The commits add new queries equivalent
to existing ones but using GRAPH_TABLE construct. The tests covering
simple tables, inheritance and partitioned tables establish that the
RLS is applied even when the tables are accessed through GRAPH_TABLE.
That is good enough to assume that the RLS policies will be applied
and handled correctly when interacting with other features like
prepared statement invalidation, security barrier views, CTE, INSERT
... SELECT, COPY, joins, non-target relations etc. This assumption is
based on the current implementation which rewrites GRAPH_TABLE RTE
well before the RLS policies are added. In case the implementation
changes in future, these extensive set of new queries will provide the
extensive coverage. We might decide to reduce the number of new
queries later. But for now, I have kept all those queries in this
patch. Hence a separate patch.

Work in 0005 and 0006 together is intended to make sure a bad actor
doesn't exploit property graphs to access data which is not available
to them otherwise.

Next I will work on finishing 0006 and implement the complete code to
construct quals joining edges to their adjacent tables.

[1] https://www.postgresql.org/message-id/a855795d-e697-4fa5-8698-d20122126567%40eisentraut.org

--
Best Wishes,
Ashutosh Bapat

---

From: Vik Fearing <[email-masked]>
Date: Tue, 19 Nov 2024 17:38:36 +0100

> Here's an updated patchset based on the latest HEAD.

I have been looking at this patchset from a user's and standards' 
perspective and I am quite pleased with what I am seeing for the most 
part.  I have not been looking much at the code itself, although I do 
plan on reviewing some of the code in the future.

There are a few things that stick out to me.

1.
I don't see any way to view the structure of of a property graph.  For 
example:

postgres=# CREATE TABLE objects (id INTEGER, color VARCHAR, shape 
VARCHAR, size INTEGER);
CREATE TABLE
postgres=# CREATE PROPERTY GRAPH propgraph VERTEX TABLES (objects KEY 
(id) PROPERTIES ALL COLUMNS);
CREATE PROPERTY GRAPH
postgres=# \dG propgraph
                       List of relations
       Schema       |   Name    | Type      |    Owner
-------------------+-----------+----------------+-------------
  graph_table_tests | propgraph | property graph | vik.fearing
(1 row)

postgres=# \d propgraph
Property graph "graph_table_tests.propgraph"
  Column | Type
--------+------

I don't really know what to do with that.

2.
There is a missing newline in the \? help of psql.
     HELP0("  \\dFt[+] [PATTERN]      list text search templates\n");
     HELP0("  \\dg[S+] [PATTERN]      list roles\n");
     HELP0("  \\dG[S+] [PATTERN]      list property graphs");   <---
     HELP0("  \\di[S+] [PATTERN]      list indexes\n");
     HELP0("  \\dl[+]                 list large objects, same as 
\\lo_list\n");

3.
The noise word "ARE" is missing from the <element table properties 
alternatives>clause.
There is also no support for the EXCEPT clause, but I imagine that can 
be added at a later time.

4.
I notice that tables in pg_catalog are not allowed in a property graph.  
What are the reasons for this?  It is true that this might cause some 
problems with upgrades if a column is removed, but it shouldn't cause 
trouble for columns being added.  That case works with user tables.

5.

The ascii art characters (I am loathe to call them operators) allow junk 
in between them.  For example:

     MATCH (c) -[:lbl]-> (d)

can be written as

     MATCH (c) -
         [:lbl] -
         /* a comment here */
         > (d)

Is that intentional?

----

I will continue to review this feature from the user's perspective.  
Thank you for working on it, I am very excited to get this in.

-- 

Vik Fearing

---

From: Ashutosh Bapat <[email-masked]>
Date: Fri, 22 Nov 2024 19:29:16 +0530

On Tue, Nov 19, 2024 at 10:08 PM Vik Fearing <[email-masked]> wrote:
> I have been looking at this patchset from a user's and standards' perspective and I am quite pleased with what I am seeing for the most part.  I have not been looking much at the code itself, although I do plan on reviewing some of the code in the future.

Thanks for looking at the patches.

> I don't see any way to view the structure of of a property graph.  For example:
>
>
> postgres=# CREATE TABLE objects (id INTEGER, color VARCHAR, shape VARCHAR, size INTEGER);
> CREATE TABLE
> postgres=# CREATE PROPERTY GRAPH propgraph VERTEX TABLES (objects KEY (id) PROPERTIES ALL COLUMNS);
> CREATE PROPERTY GRAPH
> postgres=# \dG propgraph
>                       List of relations

Yes. It is on my TODO list. IMO we should just print the first line
property graph "...". There are no predefined columns in this
relation. And somehow redirect them to use \dG instead.
>     HELP0("  \\dl[+]                 list large objects, same as \\lo_list\n");

Will fix that in the next set. I notice that \dGS+ doesn't provide a
lot of information.
\dG+
                                 List of relations
 Schema | Name |      Type      |   Owner    | Persistence |  Size   |
Description
--------+------+----------------+------------+-------------+---------+-------------
 public | g1   | property graph | ashutoshpg | permanent   | 0 bytes |
(1 row)

Maybe it should print elements, their labels and properties.

>
>
> 3.
> The noise word "ARE" is missing from the <element table properties alternatives> clause.

Agreed. Possibly in v2 since it doesn't affect the functionality.

> There is also no support for the EXCEPT clause, but I imagine that can be added at a later time.
>

There's a patch proposed at [1], but there are some things missing
there. I guess it will be added at a later time.

>
> 4.
> I notice that tables in pg_catalog are not allowed in a property graph.  What are the reasons for this?  It is true that this might cause some problems with upgrades if a column is removed, but it shouldn't cause trouble for columns being added.  That case works with user tables.
>

I don't know the reason. We don't allow tables belonging to other
users to be part of the property graph as explained in [3]. Those two
might have the same context. Maybe Peter can explain.

> 5.
>
> The ascii art characters (I am loathe to call them operators) allow junk in between them.  For example:
>
>
>     MATCH (c) -[:lbl]-> (d)
>
> can be written as
>
>
>     MATCH (c) -
>         [:lbl] -
>         /* a comment here */
>         > (d)
>
>
> Is that intentional?

This looks similar to [2]. I think it has to do with the way we parse
operators. But I haven't investigated it.

>
> I will continue to review this feature from the user's perspective.  Thank you for working on it, I am very excited to get this in.

Thanks again.

Here's the latest version of patches rebased on
aac831cafa6f3106dfcbd3298757801c299351fc. I have fixed merge
conflicts, compilation issues and also fixed the failure seen on CI
from the previous patch set.

0001 - 0005 are the same as the previous set.
0007 - has RLS tests. It is the same as 0006 from the previous patch set.

0006 - is new addressing collation and edge-vertex link qual creation.
This patch adds code to store collation and typmod to
pg_propgraph_property catalog and propagate it to property graph
references in GRAPH_TABLE. Collation is used by
assign_query_collations and assign_expr_collations to propagate
collation up the query and expression tree respectively. typmod is
used to report typmod of property reference from exprTypemod().

While finishing code to create vertex-edge link quals, I found that we
need to find and store the operator to be used for key matching in
those quals. I think we have to do something similar to what primary
key handling code does - find the equality operator when creating edge
descriptor and store it in pg_propgraph_element. I am not sure whether
we should add a dependency on the operator. I will look into this
next.

[1] https://www.postgresql.org/message-id/[email-masked]
[2] https://www.postgresql.org/message-id/[email-masked]
[3] https://www.postgresql.org/message-id/CAExHW5serNyfY4v9oy6us=[email-masked]

-- 
Best Wishes,
Ashutosh Bapat
From 24ce4e1d5f60d503431fd99ecff0ae4154d6d978 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 8 Jul 2024 11:53:25 +0530
Subject: [PATCH 3/8] support WHERE clause in graph pattern

---

index ee11594d2cf..4b787a2e88f 100644

index 813fc9aa255..de0d163e830 100644

 

 
index 2b43b136eac..486594a9934 100644

 
 -- Errors

From 8a4095eea1a6b1e6c2203b9dff76097f9dc15731 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 1 Aug 2024 10:52:35 +0530
Subject: [PATCH 4/8] Support cyclic path pattern.

Author: Ashutosh Bapat
---

index 4b787a2e88f..6e2e6ed38b8 100644

+/*

 

 
+	/*

-	foreach(lc, gep_elems)
 
 		/*

 

-			/*
 
 			/*

-		if (gep->whereClause)
+		if (pf->whereClause)
 		{
 			Node	   *tr;

 

 
 
 	if (!eletup)

 
 	/*

 
 	ReleaseSysCache(eletup);
 
-	return gpe;
+	return pe;
 }
 
 static const char *
 
 		label_oids = NIL;

 		ScanKeyInit(&key[0],
 
-				if (gpe)
+				if (pe)
 				{

 		systable_endscan(scan);
 	}

 static const char *

 
 
 				ReleaseSysCache(tup);
 			}

 				if (!n)
 				{
 
 		if (!n)
 
 		return n;
 	}
index de0d163e830..87ab3e31af8 100644

 
index 486594a9934..f34616163aa 100644
 
 -- Errors

From 52f4aae127c5a7aeb0b11d5474f0d2e3a879186d Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 21 Aug 2024 19:41:48 +0530
Subject: [PATCH 5/8] Fixes following issues

Additional changes

Author: Ashutosh Bapat
Reported By: Ajay Pal
---

diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 04bf5647f90..0ae0e81f081 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -915,6 +915,7 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 	List	   *colnames = NIL;
 	ListCell   *lc;
 	int			resno = 0;
+	bool		saved_hasSublinks;
 
 	rel = parserOpenTable(pstate, rgt->graph_name, AccessShareLock);
 	if (rel->rd_rel->relkind != RELKIND_PROPGRAPH)
@@ -928,12 +929,21 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)

 
 
 	foreach(lc, rgt->columns)
 	{
@@ -968,10 +978,20 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)

 
+	/*
 
index 8415d632483..40415ea2db8 100644
@@ -29,6 +29,7 @@
@@ -823,6 +824,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 			break;
 	}
 
index 1a971687024..b088306b5b3 100644

 /*

 
 
 	return (Node *) result;
 }

 	return (Node *) result;
 }
 
index 86f29a1e5c1..52b12d2228e 100644

index 6e2e6ed38b8..1ea5a5caeb1 100644

 
 		if (gep->quantifier)
 
-		/*

 		/*

 		prev_pf = pf;

 

 			/*

 

 	/*
 
 	ReleaseSysCache(eletup);
index c5162fedcd1..0ae1d15a1ce 100644
@@ -4397,4 +4397,10 @@ typedef struct DropSubscriptionStmt
 
index af0f550cd2c..4cefd5acf9d 100644

 
 
 #endif							/* PARSE_GRAPHTABLE_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f1e6f035ce6..34e275716c0 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -193,6 +193,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * p_resolve_unknowns: resolve unknown-type SELECT output columns as type TEXT
  * (this is true by default).
  *
+ * p_graph_table_pstate: Namespace for the GRAPH_TABLE reference being
+ * transformed, if any.
+ *
  * p_hasAggs, p_hasWindowFuncs, etc: true if we've found any of the indicated
  * constructs in the query.
  *
@@ -238,6 +241,8 @@ struct ParseState
 									 * type text */

index 87ab3e31af8..3796297cb07 100644

+-- GRAPH_TABLE in UDFs
index f34616163aa..905be9df01b 100644
 
 -- Errors

+-- GRAPH_TABLE in UDFs

From 1f6e9550c39f32b5ab5d49ca789ed49dfa8269d5 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 17 Oct 2024 14:59:26 +0530
Subject: [PATCH 6/8] Access permissions on property graph

Access to the property graph referenced in GRAPH_TABLE clause and the
base relations underlying the graph pattern specified in that clause are
governed by the following rules.

2. The property graph itself acts similar to a security invoker view in
   the sense that the current_user should have privileges to access the
   tables and columns, underlying the graph pattern. For example, a
   property graph g1 contains graph element tables e1 and e2. Assume two
   users u1 and u2 with privileges to access e1 and e2 respectively.
   Let's also assume that they have privileges to access the property
   graph g1. u1, as the current user, would be able to run query
   involving g1 successfully as long as it does *not* require access to
   e2.  Similarly u2 would be able to run query involving g1
   successfully as long as it does *not* require access to e1.

3. If a property graph is referenced in a security definer view, access
   to the property graph is governed by the privileges of the owner of
   the view but the access to the base relations underlying the graph
   pattern are governed by the privileges of the user executing the
   query. This is similar to how access to the base relations
   underlying a security invoker view referenced from a security definer
   view is handled.
   access

Note:

The privileges tests defined views which are owned by users other than
the table owners. Right now, property graph can not contain relations
which are not owned by its owner. Hence I have not added tests for
testing view access through property graphs. Per SQL/PGQ standard, a
property graph can contain any relation as long as it has SELECT
privilege on that relation. Once we implement what standard says, we can
add the tests with views and preferrably add the views as edge tables.

Author: Ashutosh Bapat <[email-masked]>
Discussion: https://www.postgresql.org/message-id/flat/[email-masked]
---
 doc/src/sgml/ref/create_property_graph.sgml |  8 ++
 src/backend/rewrite/rewriteGraphTable.c     | 18 ++++
 src/test/regress/expected/privileges.out    | 91 +++++++++++++++++++++
 src/test/regress/sql/privileges.sql         | 58 +++++++++++++
 4 files changed, 175 insertions(+)

diff --git a/doc/src/sgml/ref/create_property_graph.sgml b/doc/src/sgml/ref/create_property_graph.sgml
index f88d1194cbc..ae301b52e0a 100644
--- a/doc/src/sgml/ref/create_property_graph.sgml
+++ b/doc/src/sgml/ref/create_property_graph.sgml
@@ -276,6 +276,14 @@ CREATE PROPERTY GRAPH g1
    Property graphs are queried using the <literal>GRAPH_TABLE</literal> clause
    of <xref linkend="sql-select"/>.
   </para>
+
+  <para>
+   Access to the base relations underlying the <literal>GRAPH_TABLE</literal>
+   clause is determined by the permissions of the user executing the query,
+   rather than the property graph owner. Thus, the user of a property graph must
+   have the relevant permissions on the property graph and base relations
+   underlying the <literal>GRAPH_TABLE</literal> clause.
+  </para>
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 1ea5a5caeb1..9c14fa3b1b7 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -21,6 +21,7 @@
 #include "catalog/pg_propgraph_property.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
 #include "parser/analyze.h"
 #include "parser/parse_node.h"
 #include "parser/parse_relation.h"
@@ -380,6 +381,7 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 	Query	   *path_query = makeNode(Query);
 	List	   *fromlist = NIL;
 	List	   *qual_exprs = NIL;
+	List	   *vars;

@@ -493,6 +495,22 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
 									  replace_property_refs(rte->relid,
 															(Node *) rte->graph_table_columns,
 															graph_path));
+
+	/*
+	 * Mark the columns being accessed in the path query as requiring SELECT
+	 * privilege. Any lateral columns should have been handled when the
+	 * corresponding ColumnRefs were transformed. Ignore those here.
+	 */
+	vars = pull_vars_of_level((Node *) list_make2(qual_exprs, path_query->targetList), 0);
+	foreach_node(Var, var, vars)
+	{
+		RTEPermissionInfo *perminfo = getRTEPermissionInfo(path_query->rteperminfos,
+														   rt_fetch(var->varno, path_query->rtable));
+
+		/* Must offset the attnum to fit in a bitmapset */
+		perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+												var->varattno - FirstLowInvalidHeapAttributeNumber);
+	}
 	return path_query;
 }
 
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 75912f690c2..ad111c361ec 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2948,9 +2948,100 @@ revoke select on dep_priv_test from regress_priv_user4 cascade;
 
 set session role regress_priv_user1;
 drop table dep_priv_test;
+--
+-- Property graphs
+--
+set session role regress_priv_user1;
+create property graph ptg1
+	vertex tables (
+		atest5 key (four)
+			default label properties (four)
+			label lttc properties (three as lttck),
+		atest1 key (a)
+			default label
+			label lttc properties (a as lttck),
+		atest2 key (col1)
+			default label
+			label ltv properties (col1 as ltvk));
+-- select privileges on property graph as well as table
+select * from graph_table (ptg1 match ( : atest5) COLUMNS (1 as value)) limit 0; -- ok
+ value 
+-------
+(0 rows)
+
+grant select on ptg1 to regress_priv_user2;
+set session role regress_priv_user2;
+select * from graph_table (ptg1 match ( : atest1) COLUMNS (1 as value)) limit 0; -- ok
+ value 
+-------
+(0 rows)
+
+-- select privileges on property graph but not table
+select * from graph_table (ptg1 match ( : atest5) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for table atest5
+select * from graph_table (ptg1 match ( : lttc) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for table atest5
+set session role regress_priv_user3;
+-- select privileges on table but not property graph
+select * from graph_table (ptg1 match ( : atest1) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for property graph ptg1
+-- select privileges on neither
+select * from graph_table (ptg1 match ( : atest5) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for property graph ptg1
+-- column privileges
+set session role regress_priv_user1;
+select * from graph_table (ptg1 match (v : lttc) COLUMNS (v.lttck)) limit 0; -- ok
+ lttck 
+-------
+(0 rows)
+
+grant select on ptg1 to regress_priv_user4;
+set session role regress_priv_user4;
+select * from graph_table (ptg1 match (a : atest5) COLUMNS (a.four)) limit 0; -- ok
+ four 
+------
+(0 rows)
+
+select * from graph_table (ptg1 match (v : lttc) COLUMNS (v.lttck)) limit 0; -- fail
+ERROR:  permission denied for table atest5
+-- access property graph through security definer view
+set session role regress_priv_user4;
+create view atpgv1 as select * from graph_table (ptg1 match ( : atest1) COLUMNS (1 as value)) limit 0;
+grant select on atpgv1 to regress_priv_user3;
+select * from atpgv1; -- ok
+ value 
+-------
+(0 rows)
+
+set session role regress_priv_user3;
+select * from atpgv1; -- ok
+ value 
+-------
+(0 rows)
+
+set session role regress_priv_user4;
+create view atpgv2 as select * from graph_table (ptg1 match (v : ltv) COLUMNS (v.ltvk)) limit 0;
+-- though the session user is the owner of the view and also has access to the
+-- property graph, it does not have access to a table referenced in the graph
+-- pattern
+select * from atpgv2; -- fail
+ERROR:  permission denied for table atest2
+grant select on atpgv2 to regress_priv_user2;
+-- The user who otherwise does not have access to the property graph, gets
+-- access to it through a security definer view and uses it successfully since
+-- it has access to the tables referenced in the graph pattern.
+set session role regress_priv_user2;
+select * from atpgv2; -- ok
+ ltvk 
+------
+(0 rows)
+
 -- clean up
 \c
 drop sequence x_seq;
+drop view atpgv1;
+drop view atpgv2;
+drop property graph ptg1;
 DROP AGGREGATE priv_testagg1(int);
 DROP FUNCTION priv_testfunc2(int);
 DROP FUNCTION priv_testfunc4(boolean);
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index d886cc088c9..d571082b71a 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1746,6 +1746,60 @@ revoke select on dep_priv_test from regress_priv_user4 cascade;
 set session role regress_priv_user1;
 drop table dep_priv_test;
 
+--
+-- Property graphs
+--
+set session role regress_priv_user1;
+create property graph ptg1
+	vertex tables (
+		atest5 key (four)
+			default label properties (four)
+			label lttc properties (three as lttck),
+		atest1 key (a)
+			default label
+			label lttc properties (a as lttck),
+		atest2 key (col1)
+			default label
+			label ltv properties (col1 as ltvk));
+-- select privileges on property graph as well as table
+select * from graph_table (ptg1 match ( : atest5) COLUMNS (1 as value)) limit 0; -- ok
+grant select on ptg1 to regress_priv_user2;
+set session role regress_priv_user2;
+select * from graph_table (ptg1 match ( : atest1) COLUMNS (1 as value)) limit 0; -- ok
+-- select privileges on property graph but not table
+select * from graph_table (ptg1 match ( : atest5) COLUMNS (1 as value)) limit 0; -- fails
+select * from graph_table (ptg1 match ( : lttc) COLUMNS (1 as value)) limit 0; -- fails
+set session role regress_priv_user3;
+-- select privileges on table but not property graph
+select * from graph_table (ptg1 match ( : atest1) COLUMNS (1 as value)) limit 0; -- fails
+-- select privileges on neither
+select * from graph_table (ptg1 match ( : atest5) COLUMNS (1 as value)) limit 0; -- fails
+-- column privileges
+set session role regress_priv_user1;
+select * from graph_table (ptg1 match (v : lttc) COLUMNS (v.lttck)) limit 0; -- ok
+grant select on ptg1 to regress_priv_user4;
+set session role regress_priv_user4;
+select * from graph_table (ptg1 match (a : atest5) COLUMNS (a.four)) limit 0; -- ok
+select * from graph_table (ptg1 match (v : lttc) COLUMNS (v.lttck)) limit 0; -- fail
+-- access property graph through security definer view
+set session role regress_priv_user4;
+create view atpgv1 as select * from graph_table (ptg1 match ( : atest1) COLUMNS (1 as value)) limit 0;
+grant select on atpgv1 to regress_priv_user3;
+select * from atpgv1; -- ok
+set session role regress_priv_user3;
+select * from atpgv1; -- ok
+set session role regress_priv_user4;
+create view atpgv2 as select * from graph_table (ptg1 match (v : ltv) COLUMNS (v.ltvk)) limit 0;
+-- though the session user is the owner of the view and also has access to the
+-- property graph, it does not have access to a table referenced in the graph
+-- pattern
+select * from atpgv2; -- fail
+grant select on atpgv2 to regress_priv_user2;
+-- The user who otherwise does not have access to the property graph, gets
+-- access to it through a security definer view and uses it successfully since
+-- it has access to the tables referenced in the graph pattern.
+set session role regress_priv_user2;
+select * from atpgv2; -- ok
 
 -- clean up
 
@@ -1753,6 +1807,10 @@ drop table dep_priv_test;
 
 drop sequence x_seq;
 
+drop view atpgv1;
+drop view atpgv2;
+drop property graph ptg1;
+
 DROP AGGREGATE priv_testagg1(int);
 DROP FUNCTION priv_testfunc2(int);
 DROP FUNCTION priv_testfunc4(boolean);
-- 
2.34.1

From 6fe315a506992bf0de0321d65b29c98401d91aee Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Thu, 27 Jun 2024 14:10:44 +0200
Subject: [PATCH 2/8] WIP: SQL Property Graph Queries (SQL/PGQ)

Description of fixes added to the original patch

1. Fixes suggested by pgperltidy.

2. binary_upgrade_set_pg_class_oid() call fix: After
   6e1c4a03a978ed3574124d8f2be22ba2e5a4b1e9
   binary_upgrade_set_pg_class_oid() doesn't need the (now non-existent)
   last argument.

3. decompile_column_index_array() call fix:
   89f908a6d0ac1337c868625008c9598487d184e7 added a new parameter
   withPeriod to decompile_column_index_array(). This function is used
   to decompile keys in property graph definition. KEYs in property
   graph definition are not temporal and thus do not need to specify
   PERIOD in their definition. Hence pass `false` to
   decompile_column_index_array() when called for while decompiling a
   property graph definition.

4. Spurious column not found error: Attribute name from a heap tuple
   needs to be copied before releasing the resources held by the tuple
   and the table scan.

5. Merge conflict fixes in parser.pl: after ECPG related refactoring
   from commits around 6b005499447512abfa1e4add87dcf3e2859c4f9e.

6. Merge conflict in alter.c and aclchk.c when merging with HEAD =
   aac831cafa6f3106dfcbd3298757801c299351fc

7. Fix compilation error in propgraph_element_get_key():
RelationGetPrimaryKeyIndex(), called by propgraph_element_get_key() requires a
second argument specifying whether DEFERRED primary key constraints are
acceptable. SQL/PGQ standard section 9.12, Syntax Rule 4 mentions only primary
key and does not discuss DEFERREDness. Fix compilation failure by passing "true"
to RelationGetPrimaryKeyIndex() as second argument. No adjustment to the
documentation is needed since it mentioned primary key without DEFERREDness.
---
 src/backend/parser/parse_relation.c           |   98 +-
 src/bin/pg_dump/t/002_pg_dump.pl              |   12 +
 src/bin/psql/tab-complete.in.c                |   67 +-
 src/interfaces/ecpg/preproc/parse.pl          |    6 +-
 104 files changed, 10917 insertions(+), 202 deletions(-)

index 59bb833f48d..83ebcc48af8 100644
 
+     <row>
 
@@ -6280,6 +6306,449 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
  </sect1>

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 08155b156a5..e1188046c2e 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1960,7 +1960,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <para>
        Allows <command>SELECT</command> from
        any column, or specific column(s), of a table, view, materialized
-       view, or other table-like object.
+       view, property graph, or other table-like object.
        Also allows use of <command>COPY TO</command>.
        This privilege is also needed to reference existing column values in
        <command>UPDATE</command>, <command>DELETE</command>,
@@ -5298,6 +5298,229 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

diff --git a/doc/src/sgml/features.sgml b/doc/src/sgml/features.sgml
index 966fd398827..1abe6ccd3d5 100644
--- a/doc/src/sgml/features.sgml
+++ b/doc/src/sgml/features.sgml
@@ -70,10 +70,10 @@
 
  <para>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 13ccbe7d78c..f86bb53e7ea 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26040,6 +26040,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        </para></entry>
       </row>
 
+      <row>
index 9442b0718c0..c92b65b03a5 100644

diff --git a/doc/src/sgml/keywords/sql2023-16-nonreserved.txt b/doc/src/sgml/keywords/sql2023-16-nonreserved.txt
new file mode 100644
index 00000000000..39756c6067d
--- /dev/null
+++ b/doc/src/sgml/keywords/sql2023-16-nonreserved.txt
@@ -0,0 +1,27 @@
+ACYCLIC
+BINDINGS
+BOUND
+DESTINATION
+DIFFERENT
+DIRECTED
+EDGE
+EDGES
+ELEMENTS
+LABEL
+LABELED
+NODE
+PATHS
+PROPERTIES
+PROPERTY
+PROPERTY_GRAPH_CATALOG
+PROPERTY_GRAPH_NAME
+PROPERTY_GRAPH_SCHEMA
+RELATIONSHIP
+RELATIONSHIPS
+SHORTEST
+SINGLETONS
+STEP
+TABLES
+TRAIL
+VERTEX
+WALK
diff --git a/doc/src/sgml/keywords/sql2023-16-reserved.txt b/doc/src/sgml/keywords/sql2023-16-reserved.txt
new file mode 100644
index 00000000000..3bdd7e2b272
--- /dev/null
+++ b/doc/src/sgml/keywords/sql2023-16-reserved.txt
@@ -0,0 +1,12 @@
+ALL_DIFFERENT
+BINDING_COUNT
+ELEMENT_ID
+ELEMENT_NUMBER
+EXPORT
+GRAPH
+GRAPH_TABLE
+MATCHNUM
+PATH_LENGTH
+PATH_NAME
+PROPERTY_EXISTS
+SAME
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 372cce1a487..41a6f423c64 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -863,6 +863,11 @@ ORDER BY p;
      to columns provided by preceding <literal>FROM</literal> items in any case.
     </para>
 
+    <para>
 
  </sect1>
 
index f5be638867a..efbbf283c49 100644
index c819c7bb4e3..60218fcd01c 100644
index 00000000000..604c5180117
index 5b43c56b133..7b251476e24 100644
index 00000000000..f88d1194cbc
index 00000000000..31cb77a2af1
index 999f657d5c0..1132c1608ce 100644
 
index e42073ed748..dfc7ac5f0f0 100644
@@ -1276,7 +1276,7 @@ SELECT $1 \parse stmt1
 
         <listitem>
         <para>
@@ -1316,9 +1316,9 @@ SELECT $1 \parse stmt1
@@ -1586,6 +1586,7 @@ SELECT $1 \parse stmt1
 
@@ -1594,10 +1595,10 @@ SELECT $1 \parse stmt1
 
         <listitem>
         <para>
index 8df492281a1..948ac534446 100644
 
index e5e5fb483e9..9b97085a3fd 100644
index d7089eac0be..1c6ee998414 100644
 
+     <varlistentry>
index ff85ace83fc..6de3afa7b16 100644
index aaf96a965e4..02019162cd5 100644
@@ -291,6 +291,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
@@ -535,6 +538,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
@@ -605,6 +612,7 @@ ExecGrantStmt_oids(InternalGrant *istmt)
@@ -687,6 +695,7 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_PROPGRAPH:
 
 			/*
 			 * Here, we don't use get_object_address().  It requires that the
@@ -804,6 +813,10 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames)
@@ -1005,6 +1018,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
@@ -2699,6 +2716,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
@@ -2825,6 +2845,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
@@ -2961,6 +2984,7 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid,
index 0489cbabcb8..d7070d62c11 100644
index 76c78c0d184..d6b08f99137 100644
@@ -3009,3 +3009,367 @@ CREATE VIEW user_mappings AS
     FROM _pg_user_mappings;
 
index 7520a982151..55330ed1595 100644
@@ -949,6 +1036,7 @@ get_object_address(ObjectType objtype, Node *object,
@@ -1361,6 +1449,13 @@ get_relation_by_qualified_name(ObjectType objtype, List *object,
@@ -2276,6 +2371,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
@@ -2395,6 +2491,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
@@ -3925,6 +4022,182 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
@@ -4110,6 +4383,10 @@ getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
@@ -4599,6 +4876,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
@@ -4680,6 +4969,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
@@ -5840,6 +6132,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
@@ -6146,6 +6450,8 @@ get_relkind_objtype(char relkind)
index e05b0bbb2e0..748f06b3ff4 100644
index c002f37202f..b49aad541a1 100644
index 48f7348f91c..6260d6b79ed 100644
index a45f3bb6b83..dbe9048deda 100644
 
 		case OBJECT_COLUMN:
@@ -532,6 +533,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
+		case OBJECT_PROPGRAPH:
 			address = AlterTableNamespace(stmt,
 										  oldSchemaAddr ? &oldNspOid : NULL);
 			break;
@@ -865,6 +867,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_OPCLASS:
 		case OBJECT_OPFAMILY:
 		case OBJECT_PROCEDURE:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_ROUTINE:
 		case OBJECT_STATISTIC_EXT:
 		case OBJECT_TABLESPACE:
@@ -873,11 +876,26 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 			{
 				ObjectAddress address;
 
+					Relation	relation;
 
index 85eec7e3947..8d6faf1edd7 100644
index dcfc1dbaffd..c4d3ba59d47 100644
@@ -2170,6 +2170,7 @@ stringify_grant_objtype(ObjectType objtype)
@@ -2254,6 +2255,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
index 6dd00a4abde..34dd12d7d56 100644
index 00000000000..8430bfda7b7
+		Oid			pkidx = RelationGetPrimaryKeyIndex(element_rel, false);
index 5607273bf9f..1933bd1ca3f 100644
index 1af2e2bffb2..b61aa0423cc 100644
@@ -307,6 +307,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
 
@@ -1542,6 +1548,10 @@ RemoveRelations(DropStmt *drop)
 			relkind = RELKIND_FOREIGN_TABLE;
 			break;
 
+		case OBJECT_PROPGRAPH:
+			relkind = RELKIND_PROPGRAPH;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized drop object type: %d",
 				 (int) drop->removeType);
@@ -14888,6 +14898,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
 		case RELKIND_PARTITIONED_TABLE:
+		case RELKIND_PROPGRAPH:
 			/* ok to change owner */
 			break;
 		case RELKIND_INDEX:
@@ -18431,6 +18442,11 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a composite type", rv->relname)));
 
index 5ca856fd279..a8e34ef54d9 100644
@@ -603,7 +603,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos,
 
@@ -1122,6 +1122,12 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
@@ -1186,6 +1192,13 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
index 3060847b133..a26cfe09f8c 100644
@@ -2117,6 +2126,7 @@ expression_tree_walker_impl(Node *node,
@@ -2655,6 +2665,26 @@ expression_tree_walker_impl(Node *node,
@@ -2848,6 +2878,12 @@ range_table_entry_walker_impl(RangeTblEntry *rte,
@@ -3890,6 +3926,10 @@ range_table_mutator_impl(List *rtable,
@@ -4513,6 +4553,18 @@ raw_expression_tree_walker_impl(Node *node,
@@ -4671,6 +4723,26 @@ raw_expression_tree_walker_impl(Node *node,
index 9827cf16be4..795525419b4 100644
index be5f19dd7f6..0e42d14e1c8 100644
index 172edb643a4..3676d2080dc 100644
 
+		case RTE_GRAPH_TABLE:
+			/* shouldn't happen here */
+			break;
+
 		case RTE_RESULT:
 			/* RESULT RTEs, in themselves, are no problem. */
 			break;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 33b10d72cb5..922771727b3 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1333,6 +1333,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 				case RTE_GROUP:
 					/* these can't contain any lateral references */
 					break;
+				case RTE_GRAPH_TABLE:
+					/* shouldn't happen here */
+					Assert(false);
+					break;
 			}
 		}
 	}
@@ -2398,6 +2402,10 @@ replace_vars_in_jointree(Node *jtnode,
 						/* these shouldn't be marked LATERAL */
 						Assert(false);
 						break;
+					case RTE_GRAPH_TABLE:
+						/* shouldn't happen here */
+						Assert(false);
+						break;
 				}
 			}
 		}
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 3162a01f302..0fa237a4a90 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -23,6 +23,7 @@ OBJS = \
 	parse_enr.o \
 	parse_expr.o \
 	parse_func.o \
+	parse_graphtable.o \
 	parse_jsontable.o \
 	parse_merge.o \
 	parse_node.o \
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 506e0631615..df61f08cea6 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2150,7 +2150,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		char		selectName[32];
 		ParseNamespaceItem *nsitem;
 		RangeTblRef *rtr;
-		ListCell   *tl;
 
 		/*
 		 * Transform SelectStmt into a Query.
@@ -2190,6 +2189,8 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		 */
 		if (targetlist)
 		{
+			ListCell   *tl;
+
 			*targetlist = NIL;
 			foreach(tl, selectQuery->targetList)
 			{
@@ -2224,8 +2225,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List	   *ltargetlist;
 		List	   *rtargetlist;
-		ListCell   *ltl;
-		ListCell   *rtl;
 		const char *context;
 		bool		recursive = (pstate->p_parent_cte &&
 								 pstate->p_parent_cte->cterecursive);
@@ -2260,161 +2259,170 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 											 false,
 											 &rtargetlist);
 
-		/*
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
 
-			/*

 
-			/*
 
-			/* emit results */
 
-			/*

-				/*

-			/*
 
-		return (Node *) op;
+		/*
 
index 67eb96396af..c0d76d5d842 100644
@@ -284,6 +284,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -664,6 +665,36 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -707,18 +738,18 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE

 

 
@@ -739,7 +770,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
 
@@ -751,12 +782,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PERIOD PLACING PLAN PLANS POLICY
 
 	QUOTE QUOTES

@@ -776,7 +807,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING

 
@@ -874,7 +905,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
 			SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
-%left		Op OPERATOR		/* multi-character ops and user-defined operators */
+%left		Op OPERATOR RIGHT_ARROW '|'	/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
 %left		'^'
@@ -1001,6 +1032,7 @@ stmt:
 			| AlterOperatorStmt
 			| AlterTypeStmt
 			| AlterPolicyStmt
+			| AlterPropGraphStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
 			| AlterTableStmt
@@ -1041,6 +1073,7 @@ stmt:
 			| AlterOpFamilyStmt
 			| CreatePolicyStmt
 			| CreatePLangStmt
+			| CreatePropGraphStmt
 			| CreateSchemaStmt
 			| CreateSeqStmt
 			| CreateStmt
@@ -6991,6 +7024,7 @@ object_type_any_name:
 			| MATERIALIZED VIEW						{ $$ = OBJECT_MATVIEW; }
 			| INDEX									{ $$ = OBJECT_INDEX; }
 			| FOREIGN TABLE							{ $$ = OBJECT_FOREIGN_TABLE; }
+			| PROPERTY GRAPH						{ $$ = OBJECT_PROPGRAPH; }
 			| COLLATION								{ $$ = OBJECT_COLLATION; }
 			| CONVERSION_P							{ $$ = OBJECT_CONVERSION; }
 			| STATISTICS							{ $$ = OBJECT_STATISTIC_EXT; }
@@ -7847,6 +7881,15 @@ privilege_target:
 					n->objs = $2;
 					$$ = n;
 				}
+			| PROPERTY GRAPH qualified_name_list
+				{
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
+
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_PROPGRAPH;
+					n->objs = $3;
+					$$ = n;
+				}
 			| SCHEMA name_list
 				{
 					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -9170,6 +9213,366 @@ opt_if_exists: IF_P EXISTS						{ $$ = true; }
 		;

@@ -9470,6 +9873,16 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
@@ -10095,6 +10508,26 @@ AlterObjectSchemaStmt:
@@ -10438,6 +10871,15 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
@@ -13573,6 +14015,17 @@ table_ref:	relation_expr opt_alias_clause
@@ -14917,6 +15370,10 @@ a_expr:		c_expr									{ $$ = $1; }
 
@@ -15396,6 +15853,10 @@ b_expr:		c_expr
@@ -16564,6 +17025,8 @@ MathOp:		 '+'									{ $$ = "+"; }
 
 qual_Op:	Op
@@ -17155,6 +17618,213 @@ json_array_aggregate_order_by_clause_opt:
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
+
+/*****************************************************************************
+ *
+ *	graph patterns
+ *
+ *****************************************************************************/
+
+graph_pattern:
+			path_pattern_list where_clause
+				{
+					GraphPattern *gp = makeNode(GraphPattern);
+
+					gp->path_pattern_list = $1;
+					gp->whereClause = $2;
+					$$ = (Node *) gp;
+				}
+		;
+
+path_pattern_list:
+			path_pattern							{ $$ = list_make1($1); }
+			| path_pattern_list ',' path_pattern	{ $$ = lappend($1, $3); }
+		;
+
+path_pattern:
+			path_pattern_expression					{ $$ = $1; }
+		;
+
+/*
+ * path pattern expression
+ */
+
+path_pattern_expression:
+			path_term								{ $$ = $1; }
+			/* | path_multiset_alternation */
+			/* | path_pattern_union */
+		;
+
+path_term:
+			path_factor								{ $$ = list_make1($1); }
+			| path_term path_factor					{ $$ = lappend($1, $2); }
+		;
+
+path_factor:
+			path_primary opt_graph_pattern_quantifier
+				{
+					GraphElementPattern *gep = (GraphElementPattern *) $1;
+
+					gep->quantifier = $2;
+				}
+		;
+
+path_primary:
+			'(' opt_colid opt_is_label_expression where_clause ')'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = VERTEX_PATTERN;
+					gep->variable = $2;
+					gep->labelexpr = $3;
+					gep->whereClause = $4;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge pointing left: <-[ xxx ]- */
+			| '<' '-' '[' opt_colid opt_is_label_expression where_clause ']' '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_LEFT;
+					gep->variable = $4;
+					gep->labelexpr = $5;
+					gep->whereClause = $6;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge pointing right: -[ xxx ]-> */
+			| '-' '[' opt_colid opt_is_label_expression where_clause ']' '-' '>'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->variable = $3;
+					gep->labelexpr = $4;
+					gep->whereClause = $5;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '-' '[' opt_colid opt_is_label_expression where_clause ']' RIGHT_ARROW
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->variable = $3;
+					gep->labelexpr = $4;
+					gep->whereClause = $5;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge any direction: -[ xxx ]- */
+			| '-' '[' opt_colid opt_is_label_expression where_clause ']' '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_ANY;
+					gep->variable = $3;
+					gep->labelexpr = $4;
+					gep->whereClause = $5;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* abbreviated edge patterns */
+			| '<' '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_LEFT;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '-' '>'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| RIGHT_ARROW
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '-'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_ANY;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			| '(' path_pattern_expression where_clause ')'
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = PAREN_EXPR;
+					gep->subexpr = $2;
+					gep->whereClause = $3;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+		;
+
+opt_colid:
+			ColId			{ $$ = $1; }
+			| /*EMPTY*/		{ $$ = NULL; }
+		;
+
+opt_is_label_expression:
+			IS label_expression		{ $$ = $2; }
+			| ':' label_expression	{ $$ = $2; }
+			| /*EMPTY*/				{ $$ = NULL; }
+		;
+
+/*
+ * graph pattern quantifier
+ */
+
+opt_graph_pattern_quantifier:
+			'{' Iconst '}'					{ $$ = list_make2_int($2, $2); }
+			| '{' ',' Iconst '}'			{ $$ = list_make2_int(0, $3); }
+			| '{' Iconst ',' Iconst '}'		{ $$ = list_make2_int($2, $4); }
+			| /*EMPTY*/						{ $$ = NULL; }
+		;
+
+/*
+ * label expression
+ */
+
+label_expression:
+			label_term
+			| label_disjunction
+		;
+
+label_disjunction:
+			label_expression '|' label_term
+				{ $$ = makeOrExpr($1, $3, @2); }
+		;
+
+label_term:
+			name
+				{ $$ = makeColumnRef($1, NIL, @1, yyscanner); }
+		;
+
+
 /*****************************************************************************
  *
  *	target list for SELECT
@@ -17675,6 +18345,7 @@ unreserved_keyword:
 			| DELIMITERS
 			| DEPENDS
 			| DEPTH
+			| DESTINATION
 			| DETACH
 			| DICTIONARY
 			| DISABLE_P
@@ -17684,6 +18355,7 @@ unreserved_keyword:
 			| DOUBLE_P
 			| DROP
 			| EACH
+			| EDGE
 			| EMPTY_P
 			| ENABLE_P
 			| ENCODING
@@ -17713,6 +18385,7 @@ unreserved_keyword:
 			| GENERATED
 			| GLOBAL
 			| GRANTED
+			| GRAPH
 			| GROUPS
 			| HANDLER
 			| HEADER_P
@@ -17777,6 +18450,7 @@ unreserved_keyword:
 			| NFKC
 			| NFKD
 			| NO
+			| NODE
 			| NORMALIZED
 			| NOTHING
 			| NOTIFY
@@ -17819,6 +18493,8 @@ unreserved_keyword:
 			| PROCEDURE
 			| PROCEDURES
 			| PROGRAM
+			| PROPERTIES
+			| PROPERTY
 			| PUBLICATION
 			| QUOTE
 			| QUOTES
@@ -17830,6 +18506,7 @@ unreserved_keyword:
 			| REFERENCING
 			| REFRESH
 			| REINDEX
+			| RELATIONSHIP
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
@@ -17919,6 +18596,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERTEX
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -17957,6 +18635,7 @@ col_name_keyword:
 			| EXISTS
 			| EXTRACT
 			| FLOAT_P
+			| GRAPH_TABLE
 			| GREATEST
 			| GROUPING
 			| INOUT
@@ -18248,6 +18927,7 @@ bare_label_keyword:
 			| DEPENDS
 			| DEPTH
 			| DESC
+			| DESTINATION
 			| DETACH
 			| DICTIONARY
 			| DISABLE_P
@@ -18259,6 +18939,7 @@ bare_label_keyword:
 			| DOUBLE_P
 			| DROP
 			| EACH
+			| EDGE
 			| ELSE
 			| EMPTY_P
 			| ENABLE_P
@@ -18296,6 +18977,8 @@ bare_label_keyword:
 			| GENERATED
 			| GLOBAL
 			| GRANTED
+			| GRAPH
+			| GRAPH_TABLE
 			| GREATEST
 			| GROUPING
 			| GROUPS
@@ -18391,6 +19074,7 @@ bare_label_keyword:
 			| NFKC
 			| NFKD
 			| NO
+			| NODE
 			| NONE
 			| NORMALIZE
 			| NORMALIZED
@@ -18446,6 +19130,8 @@ bare_label_keyword:
 			| PROCEDURE
 			| PROCEDURES
 			| PROGRAM
+			| PROPERTIES
+			| PROPERTY
 			| PUBLICATION
 			| QUOTE
 			| QUOTES
@@ -18459,6 +19145,7 @@ bare_label_keyword:
 			| REFERENCING
 			| REFRESH
 			| REINDEX
+			| RELATIONSHIP
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
@@ -18573,6 +19260,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERTEX
 			| VIEW
 			| VIEWS
 			| VOLATILE
diff --git a/src/backend/parser/meson.build b/src/backend/parser/meson.build
index 573d70b3d1b..cbcfa07b405 100644
--- a/src/backend/parser/meson.build
+++ b/src/backend/parser/meson.build
@@ -10,6 +10,7 @@ backend_sources += files(
   'parse_enr.c',
   'parse_expr.c',
   'parse_func.c',
+  'parse_graphtable.c',
   'parse_jsontable.c',
   'parse_merge.c',
   'parse_node.c',
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index efa730c1676..b7480b18acb 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -580,6 +580,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
@@ -970,6 +978,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 
 			/*
index 979926b6052..04bf5647f90 100644
@@ -65,6 +66,8 @@ static ParseNamespaceItem *transformRangeFunction(ParseState *pstate,
@@ -898,6 +901,80 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
 
+/*
@@ -1121,6 +1198,18 @@ transformFromClauseItem(ParseState *pstate, Node *n,
index 44529bb49e6..33a1f3f2413 100644
 
index c2806297aa4..8415d632483 100644
@@ -575,6 +575,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 
@@ -1858,6 +1859,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 			/*
@@ -3197,6 +3201,8 @@ ParseExprKindName(ParseExprKind exprKind)
 
 			/*
index 9b23344a3b1..6409ddf1816 100644
 
 			/*
index 00000000000..1a971687024
index 8075b1b8a1b..86f29a1e5c1 100644
 
+ParseNamespaceItem *
@@ -3013,6 +3105,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
@@ -3392,10 +3485,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 		case RTE_VALUES:
 		case RTE_CTE:
 		case RTE_GROUP:
+		case RTE_GRAPH_TABLE:
 
 			/*
-			 * Subselect, Table Functions, Values, CTE, GROUP RTEs never have
-			 * dropped columns
+			 * Subselect, Table Functions, Values, CTE, GROUP RTEs, Property
+			 * graph references never have dropped columns
 			 */
 			result = false;
 			break;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 76bf88c3ca2..68521efd7a0 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -360,6 +360,10 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
 			tle->resorigtbl = rte->relid;
 			tle->resorigcol = attnum;
 			break;
+		case RTE_GRAPH_TABLE:
+			tle->resorigtbl = rte->relid;
+			tle->resorigcol = InvalidAttrNumber;
+			break;
 		case RTE_SUBQUERY:
 			/* Subselect-in-FROM: copy up from the subselect */
 			if (attnum != InvalidAttrNumber)
@@ -1581,6 +1585,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 		case RTE_RELATION:
 		case RTE_VALUES:
 		case RTE_NAMEDTUPLESTORE:
+		case RTE_GRAPH_TABLE:
 		case RTE_RESULT:
 
 			/*
index 994ed9995ac..4f9cce5c604 100644
 
 /*
 
@@ -892,6 +894,11 @@ other			.
 					return NOT_EQUALS;
 				}
 
+{right_arrow}	{
+					SET_YYLLOC();
+					return RIGHT_ARROW;
+				}
+
 {self}			{
 					SET_YYLLOC();
 					return yytext[0];
@@ -969,7 +976,7 @@ other			.
 						 * that the "self" rule would have.
 						 */
 						if (nchars == 1 &&
-							strchr(",()[].;:+-*/%^<>=", yytext[0]))
+							strchr(",()[].;:|+-*/%^<>=", yytext[0]))
 							return yytext[0];
 						/*
 						 * Likewise, if what we have left is two chars, and
@@ -989,6 +996,8 @@ other			.
 								return NOT_EQUALS;
 							if (yytext[0] == '!' && yytext[1] == '=')
 								return NOT_EQUALS;
+							if (yytext[0] == '-' && yytext[1] == '>')
+								return RIGHT_ARROW;
 						}
 					}
 
index 4680752e6a7..09070047b7e 100644
 
 OBJS = \
 	rewriteDefine.o \
+	rewriteGraphTable.o \
 	rewriteHandler.o \
 	rewriteManip.o \
 	rewriteRemove.o \
diff --git a/src/backend/rewrite/meson.build b/src/backend/rewrite/meson.build
index 23043ca6e56..2bea20233a4 100644
--- a/src/backend/rewrite/meson.build
+++ b/src/backend/rewrite/meson.build
@@ -2,6 +2,7 @@
 
index 00000000000..ee11594d2cf
index 063afd4933e..934e469e0dd 100644
@@ -2042,6 +2043,16 @@ fireRIRrules(Query *parsetree, List *activeRIRs)

+		/*
index f28bf371059..b9ef8117a37 100644
@@ -1739,6 +1742,14 @@ ProcessUtilitySlow(ParseState *pstate,
 				commandCollected = true;
 				break;
 
+			case T_CreatePropGraphStmt:
+				address = CreatePropGraph(pstate, (CreatePropGraphStmt *) parsetree);
+				break;
+
+			case T_AlterPropGraphStmt:
+				address = AlterPropGraph(pstate, (AlterPropGraphStmt *) parsetree);
+				break;
+
 			case T_CreateTransformStmt:
 				address = CreateTransform((CreateTransformStmt *) parsetree);
 				break;
@@ -2007,6 +2018,7 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel)
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
 		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_PROPGRAPH:
 			RemoveRelations(stmt);
 			break;
 		default:
@@ -2283,6 +2295,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_PROCEDURE:
 			tag = CMDTAG_ALTER_PROCEDURE;
 			break;
+		case OBJECT_PROPGRAPH:
+			tag = CMDTAG_ALTER_PROPERTY_GRAPH;
+			break;
 		case OBJECT_ROLE:
 			tag = CMDTAG_ALTER_ROLE;
 			break;
@@ -2559,6 +2574,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_INDEX:
 					tag = CMDTAG_DROP_INDEX;
 					break;
+				case OBJECT_PROPGRAPH:
+					tag = CMDTAG_DROP_PROPERTY_GRAPH;
+					break;
 				case OBJECT_TYPE:
 					tag = CMDTAG_DROP_TYPE;
 					break;
@@ -2940,6 +2958,14 @@ CreateCommandTag(Node *parsetree)
 			}
 			break;
 
@@ -3637,6 +3663,14 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
index 2194ab3dfa5..164c0b6e43b 100644
@@ -359,6 +364,9 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
@@ -1594,6 +1602,331 @@ pg_get_querydef(Query *query, bool pretty)
 	return buf.data;
 }
 
+/*
+			decompile_column_index_array(datum, pgeform->pgerelid, false, buf);
+				decompile_column_index_array(srckey, pgeform->pgerelid, false, buf);
+				decompile_column_index_array(srcref, pgeform2->pgerelid, false, buf);
+				decompile_column_index_array(destkey, pgeform->pgerelid, false, buf);
+				decompile_column_index_array(destref, pgeform2->pgerelid, false, buf);
@@ -7476,6 +7809,171 @@ get_utility_query_def(Query *query, deparse_context *context)
 	}
 }
 
+
+/*
@@ -8077,6 +8575,7 @@ get_name_for_var_field(Var *var, int fieldno,
 
 			/*
@@ -10474,6 +10973,14 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_tablefunc((TableFunc *) node, context, showimplicit);
 			break;
 
@@ -12332,6 +12839,36 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
index a85dc0d891f..3bf331b21aa 100644
 
 	return subname;
 }
+
+char *
index 33d323085f5..19d6c0c2b8a 100644
@@ -496,7 +496,8 @@ flagInhAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tblinfo, int numTables
 
index 8c20c263c4b..254b47a7ef7 100644
index c30aafbe70d..38fee029816 100644
@@ -1693,10 +1693,10 @@ expand_table_name_patterns(Archive *fout,
@@ -2863,6 +2863,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 
@@ -6984,7 +6987,8 @@ getTables(Archive *fout, int *numTables)
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15970,8 +15974,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
 		reltypename = "VIEW";
 
@@ -15997,6 +15999,47 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
@@ -16072,8 +16115,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
@@ -16807,6 +16848,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
@@ -18746,6 +18789,16 @@ getDependencies(Archive *fout)
 
+	/*
 
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index aa1564cd450..df224eb5b6c 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2948,6 +2948,18 @@ my %tests = (
 		},
 	},
 
index 1f3cbb11f7c..300ad49e409 100644
@@ -867,7 +867,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
@@ -998,6 +998,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
index 5bfebad64d5..b8d8386ece4 100644
@@ -1028,6 +1028,7 @@ permissionsList(const char *pattern, bool showSystem)
@@ -1038,6 +1039,7 @@ permissionsList(const char *pattern, bool showSystem)
 
@@ -1129,6 +1131,7 @@ permissionsList(const char *pattern, bool showSystem)
 
@@ -2008,6 +2011,10 @@ describeOneTableDetails(const char *schemaname,
@@ -3099,6 +3106,32 @@ describeOneTableDetails(const char *schemaname,
 		}
 	}
 
@@ -3950,6 +3983,7 @@ describeRoleGrants(const char *pattern, bool showSystem)
@@ -3961,6 +3995,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	PQExpBufferData buf;
 	PGresult   *res;
@@ -3969,8 +4004,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	bool		translate_columns[] = {false, false, true, false, false, false, false, false, false};

 	initPQExpBuffer(&buf);
 
@@ -3987,6 +4022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
@@ -4000,6 +4036,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
@@ -4083,6 +4120,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "''");	/* dummy */
 	appendPQExpBufferStr(&buf, ")\n");
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3f4afc2d141..c78e5c3f441 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -247,6 +247,7 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\dFp[+] [PATTERN]      list text search parsers\n");
 	HELP0("  \\dFt[+] [PATTERN]      list text search templates\n");
 	HELP0("  \\dg[S+] [PATTERN]      list roles\n");
+	HELP0("  \\dG[S+] [PATTERN]      list property graphs");
 	HELP0("  \\di[S+] [PATTERN]      list indexes\n");
 	HELP0("  \\dl[+]                 list large objects, same as \\lo_list\n");
 	HELP0("  \\dL[S+] [PATTERN]      list procedural languages\n");
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index fad2277991d..af96786403e 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -795,6 +795,14 @@ static const SchemaQuery Query_for_list_of_partitioned_indexes = {
 	.result = "c.relname",
 };

 /* All relations */
@@ -1277,6 +1285,7 @@ static const pgsql_thing_t words_after_create[] = {
@@ -2639,6 +2648,20 @@ match_previous_words(int pattern_id,
 
@@ -3117,7 +3140,7 @@ match_previous_words(int pattern_id,
@@ -3155,6 +3178,8 @@ match_previous_words(int pattern_id,
@@ -3473,6 +3498,25 @@ match_previous_words(int pattern_id,

 /* CREATE PUBLICATION */
 	else if (Matches("CREATE", "PUBLICATION", MatchAny))
@@ -4167,6 +4211,12 @@ match_previous_words(int pattern_id,
 	else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
 
+	/* DROP PROPERTY GRAPH */
+	else if (Matches("DROP", "PROPERTY"))
+		COMPLETE_WITH("GRAPH");
+	else if (Matches("DROP", "PROPERTY", "GRAPH"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
+
 	/* DROP RULE */
 	else if (Matches("DROP", "RULE", MatchAny))
 		COMPLETE_WITH("ON");
@@ -4411,6 +4461,7 @@ match_previous_words(int pattern_id,
 											"LARGE OBJECT",
 											"PARAMETER",
 											"PROCEDURE",
+											"PROPERTY GRAPH",
 											"ROUTINE",
 											"SCHEMA",
 											"SEQUENCE",
@@ -4539,6 +4590,14 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH("FROM");
 	}
 
+/* GRAPH_TABLE */
@@ -4862,8 +4921,10 @@ match_previous_words(int pattern_id,
 
@@ -5278,6 +5339,8 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH("OBJECT");
index 8e8b049e15f..d1384f6e58a 100644
 
 /*
 
@@ -661,6 +663,10 @@ other			.
 					ECHO;
 				}
 
+{right_arrow}	{
+					ECHO;
+				}
+
 	/*
index 167f91a6e3f..e126aa199b3 100644

index f70d1daba52..de218c3e99e 100644
 
index 0fc2c093b0d..729a2d70153 100644
 
index cbbe8acd382..8bc2cc0af42 100644
@@ -3871,6 +3871,9 @@
 
index 00000000000..2bc2066b6c1
index 00000000000..91851a19277
index 00000000000..c6b711351b5
index 00000000000..a9595215675
index 00000000000..b8921ace30c
index 00000000000..2440bd4a60c
index 0f9462493e3..c5162fedcd1 100644
@@ -684,6 +684,19 @@ typedef struct RangeTableFuncCol
 
+/*
@@ -950,6 +963,42 @@ typedef struct PartitionCmd
 	bool		concurrent;
 } PartitionCmd;
 
+/*
@@ -1022,6 +1071,7 @@ typedef enum RTEKind
@@ -1183,6 +1233,12 @@ typedef struct RangeTblEntry
 	 */
 	TableFunc  *tablefunc;
 
+	/*
+	 * Fields valid for a graph table RTE (else NULL):
+	 */
+	GraphPattern *graph_pattern;
+	List	   *graph_table_columns;
+
 	/*
 	 * Fields valid for a values RTE (else NIL):
 	 */
@@ -2295,6 +2351,7 @@ typedef enum ObjectType
 	OBJECT_PARAMETER_ACL,
 	OBJECT_POLICY,
 	OBJECT_PROCEDURE,
+	OBJECT_PROPGRAPH,
 	OBJECT_PUBLICATION,
 	OBJECT_PUBLICATION_NAMESPACE,
 	OBJECT_PUBLICATION_REL,
@@ -4037,6 +4094,88 @@ typedef struct CreateCastStmt
 	bool		inout;
 } CreateCastStmt;
 
index b0ef1952e8f..a246bd9dfd6 100644
@@ -2128,6 +2128,28 @@ typedef struct InferenceElem
 
+/*
index 28b66fccb43..385f7fdfa17 100644

 #endif							/* ANALYZE_H */
index 899d64ad55f..fab75f16b7a 100644
@@ -356,6 +361,8 @@ PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -369,6 +376,7 @@ PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD, BARE_LABEL)
@@ -488,6 +496,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
index 00000000000..af0f550cd2c
index 2375e95c107..f1e6f035ce6 100644

index 91fd8e243b5..69ca190d5e4 100644
index 00000000000..0c0319f5cfa
index 7fdcec6dd93..5582483d06c 100644
index 731d84b2a93..b6813d7292e 100644
index 20446f6f836..82d0bbb267d 100644
 
+extern char *get_propgraph_label_name(Oid labeloid);
+extern char *get_propgraph_property_name(Oid propoid);
+
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
 #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 86943ae2537..b49a8a3ffb8 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -256,9 +256,9 @@ sub main
 			$has_if_command = 1 if /^\s*if/;
 		}
 
-		# Make sure any braces are split into separate fields
-		s/{/ { /g;
-		s/}/ } /g;
+		# Make sure any (unquoted) braces are split into separate fields
+		s/(?<!')\{(?!')/ { /g;
+		s/(?<!')\}(?!')/ } /g;
 
 		# Likewise for comment start/end markers
 		s|\/\*| /* |g;
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index f3c03482aec..e7d3cad7487 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -335,6 +335,8 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+/* Note there is no need for left_arrow, since "<-" is not a single operator. */
+right_arrow		"->"
 
 /*
 
@@ -854,6 +856,10 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 					return NOT_EQUALS;
 				}
 
+{right_arrow}	{
+					return RIGHT_ARROW;
+				}
+
 {informix_special} {
@@ -947,7 +953,7 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 
 						/*
@@ -968,6 +974,8 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 								return NOT_EQUALS;
 							if (yytext[0] == '!' && yytext[1] == '=')
 								return NOT_EQUALS;
+							if (yytext[0] == '-' && yytext[1] == '>')
+								return RIGHT_ARROW;
 						}
 					}
 
index 8182ce28aa1..8f6146e8dc3 100644
 
 /*
index ae54cb254f9..ab9ce9e9acd 100644
index 00000000000..43316fbc029
index 00000000000..813fc9aa255
index fc42d418bf1..432ba471fe3 100644
index 215eb899be3..3b41fc7ba04 100644
index 81e4222d26a..603219fedb1 100644
 
 # ----------
 # sanity_check does a vacuum, affecting the sort order of SELECT *
@@ -78,7 +78,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps
+test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role without_overlaps graph_table
 
index de58d268d31..067d5c2b490 100644
 
+--
+-- Property Graph
+--
index 00000000000..4f9b5c0349b
index 00000000000..2b43b136eac
index 1a6c61f49d5..93f9f9c704f 100644
index 08521d51a9b..5751b82975d 100644
@@ -4187,3 +4187,24 @@ yyscan_t

From dd3cf8d4ad1ca3058684c957e348eb4134860a54 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 28 Oct 2024 16:36:27 +0530
Subject: [PATCH 7/8] Property collation and edge-vertex link support

The commit has following changes.

1. collation of a property
--------------------------
The values with differing collations can not be collated. This means
that we will not able to compare, sort or order values of properties
with the same name, if they have different collations in different
elements or labels.  This restricts that property's usage in graph table
query. Hence the collation of all the properties with the same name
needs to be the same. Enforce this.

Often the collation of a property reference is required before
graph_table is rewritten. For example, assign_query_collation() is
called during transformSelectStmt() which happens before calling
rewriteGraphTable(). Hence the collation of a property needs to be
accessible before any property reference is accessed. Hence the
collation of a property is stored in pg_propgraph_property.

Note: This is not explicitly specified in Section 9.15, "Consistency check of a
tabular property graph descriptor", syntax rule 4.c.iii.2 of SQL/PGQ standard.

2. collation of source and destination keys of an edge
------------------------------------------------------
If collations of edge key and the corresponding referenced key differ, an edge
may end up being adjacent to undesirable vertex. Prohibit such a case.
This is different from how foreign keys, in which case the collation of
referenced key is used and that of referencing key is ignored.

Note: This is not specified in Section 9.14, "Creation of an edge table
descriptor".

3. WIP creation of edge-vertex link quals
-----------------------------------------
When creating an edge element make sure that there exists an equality
operator that can be used to match vertex and edge keys. Fail the DDL if
such an operator does not exist.

In build_edge_vertex_link_quals(),  the types of keys of referenced
elements need to use the OID of referenced element's relation OID. Also
use make_op() instead of hand-crafting OpExpr.

TODO:
Similar to primary keys, we need to save the operator used for equating
keys in the catalog, use it when crafting the edge-vertex qual. To avoid
it being dropped we need to add a dependency?

4. Tests
--------
Adds tests for above items.

The collation tests, test that

a. the collation is correctly set for the columns projected by
GRAPH_TABLE clause.

b. quals corresponding to the various WHERE clause in GRAPH_TABLE have
correct collation set

c. quals corresponding to the edge-vertex links have correct collation
set.

Note: These tests combined with tests in collate.sql and other collation
specific tests indicate that we have covered all scenarios testing
collations in the context of GRAPH_TABLE. More collation specific tests
may be added as required.

Ashutosh Bapat
---
 src/backend/catalog/information_schema.sql    |   8 +-
 src/backend/commands/propgraphcmds.c          | 136 ++++++++-
 src/backend/nodes/nodeFuncs.c                 |   5 +-
 src/backend/parser/parse_collate.c            |   8 +-
 src/backend/parser/parse_graphtable.c         |  16 +-
 src/backend/rewrite/rewriteGraphTable.c       |  71 +++--
 src/include/catalog/pg_propgraph_property.h   |   6 +
 src/include/nodes/primnodes.h                 |   2 +
 .../expected/create_property_graph.out        | 261 +++++++++++++++---
 src/test/regress/expected/graph_table.out     | 111 +++++++-
 src/test/regress/expected/oidjoins.out        |   1 +
 .../regress/sql/create_property_graph.sql     |  77 ++++++
 src/test/regress/sql/graph_table.sql          |  41 ++-
 13 files changed, 633 insertions(+), 110 deletions(-)

diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index d6b08f99137..cc94339aa26 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -3271,9 +3271,9 @@ CREATE VIEW pg_property_data_types AS
            CAST(null AS sql_identifier) AS character_set_catalog,
            CAST(null AS sql_identifier) AS character_set_schema,
            CAST(null AS sql_identifier) AS character_set_name,
-           CAST(null AS sql_identifier) AS collation_catalog, -- FIXME
-           CAST(null AS sql_identifier) AS collation_schema, -- FIXME
-           CAST(null AS sql_identifier) AS collation_name, -- FIXME
+           CAST(current_database() AS sql_identifier) AS collation_catalog,
+           CAST(nc.nspname AS sql_identifier) AS collation_schema,
+           CAST(c.collname AS sql_identifier) AS collation_name,
            CAST(null AS cardinal_number) AS numeric_precision,
            CAST(null AS cardinal_number) AS numeric_precision_radix,
            CAST(null AS cardinal_number) AS numeric_scale,
@@ -3297,6 +3297,8 @@ CREATE VIEW pg_property_data_types AS
          JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON pgp.pgptypid = t.oid
          LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON (bt.typnamespace = nbt.oid))
            ON (t.typtype = 'd' AND t.typbasetype = bt.oid)
+         LEFT JOIN (pg_collation c JOIN pg_namespace nc ON (c.collnamespace = nc.oid))
+           ON pgp.pgpcollation = c.oid AND (nc.nspname, c.collname) <> ('pg_catalog', 'default')
 
     WHERE pg.relkind = 'g'
           AND (NOT pg_is_other_temp_schema(npg.oid))
diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 8430bfda7b7..090c4209dc2 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -20,6 +20,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_propgraph_element.h"
 #include "catalog/pg_propgraph_element_label.h"
 #include "catalog/pg_propgraph_label.h"
@@ -28,6 +29,8 @@
 #include "commands/propgraphcmds.h"
 #include "commands/tablecmds.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "utils/array.h"
@@ -69,7 +72,7 @@ static void propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols,
 										Relation edge_rel, Relation ref_rel,
 										const char *aliasname, int location, const char *type,
 										ArrayType **outkey, ArrayType **outref);
-static ArrayType *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
+static AttrNumber *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
 static ArrayType *array_from_attnums(int numattrs, const AttrNumber *attnums);
 static Oid	insert_element_record(ObjectAddress pgaddress, struct element_info *einfo);
 static Oid	insert_label_record(Oid graphid, Oid peoid, const char *label);
@@ -336,7 +339,8 @@ propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation e
 	}
 	else
 	{
-		a = array_from_column_list(pstate, key_clause, location, element_rel);
+		a = array_from_attnums(list_length(key_clause),
+							   array_from_column_list(pstate, key_clause, location, element_rel));
 	}
 
 	return a;
@@ -362,6 +366,11 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 							const char *aliasname, int location, const char *type,
 							ArrayType **outkey, ArrayType **outref)
 {
+	int			nkeys;
+	AttrNumber *keyattnums;
+	AttrNumber *refattnums;
+	int			i;
+
 	Assert((keycols && refcols) || (!keycols && !refcols));
 
 	if (keycols)
@@ -372,8 +381,9 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 					errmsg("mismatching number of columns in %s vertex definition of edge \"%s\"", type, aliasname),
 					parser_errposition(pstate, location));
 
-		*outkey = array_from_column_list(pstate, keycols, location, edge_rel);
-		*outref = array_from_column_list(pstate, refcols, location, ref_rel);
+		nkeys = list_length(keycols);
+		keyattnums = array_from_column_list(pstate, keycols, location, edge_rel);
+		refattnums = array_from_column_list(pstate, refcols, location, ref_rel);
 	}
 	else
 	{
@@ -404,25 +414,82 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 
 		Assert(fk);
 
-		*outkey = array_from_attnums(fk->nkeys, fk->conkey);
-		*outref = array_from_attnums(fk->nkeys, fk->confkey);
+		nkeys = fk->nkeys;
+		keyattnums = fk->conkey;
+		refattnums = fk->confkey;
+	}
+
+	for (i = 0; i < nkeys; i++)
+	{
+		Oid			keytype;
+		int32		keytypmod;
+		Oid			keycoll;
+		Oid			reftype;
+		int32		reftypmod;
+		Oid			refcoll;
+		Operator	qualop;
+		List	   *op = list_make2(makeString("pg_catalog"), makeString("="));
+
+		get_atttypetypmodcoll(RelationGetRelid(edge_rel), keyattnums[i], &keytype, &keytypmod, &keycoll);
+		get_atttypetypmodcoll(RelationGetRelid(ref_rel), refattnums[i], &reftype, &reftypmod, &refcoll);
+
+		/*
+		 * If an equality operator does not exist for data types of edge and
+		 * vertex keys, we can not construct a qual to link edge to its
+		 * adjancent vertexes. Prohibit such case.
+		 *
+		 * XXX: this won't prohibit cases where all values of one type are not
+		 * coercible to the other type. To fix that case, we will need type
+		 * compatibility check similar to foreign key constraints.
+		 *
+		 * XXX: Also, even if this check passes, the required operator may
+		 * still be dropped later. How to avoid that case?
+		 */
+		qualop = oper(NULL, op, keytype, reftype, true, -1);
+		if (!HeapTupleIsValid(qualop))
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("no operator exists for %s key comparison of edge \"%s\": %s",
+						   type, aliasname,
+						   op_signature_string(op, keytype, reftype)),
+					parser_errposition(pstate, location));
+		else
+			ReleaseSysCache(qualop);
+
+		/*
+		 * If collations of key attribute and referenced attribute are
+		 * different, an edge may end up being adjacent to undesired vertexes.
+		 * Prohibit such a case.
+		 */
+		if (keycoll != refcoll &&
+			keycoll != DEFAULT_COLLATION_OID && refcoll != DEFAULT_COLLATION_OID &&
+			OidIsValid(keycoll) && OidIsValid(refcoll))
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("collation mismatch in %s key of edge \"%s\": %s vs. %s",
+						   type, aliasname,
+						   get_collation_name(keycoll), get_collation_name(refcoll)),
+					parser_errposition(pstate, location));
 	}
+
+	*outkey = array_from_attnums(nkeys, keyattnums);
+	*outref = array_from_attnums(nkeys, refattnums);
 }
 
 /*
  * Convert list of column names in the specified relation into an array of
  * column numbers.
  */
-static ArrayType *
+static AttrNumber *
 array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel)
 {
 	int			numattrs;
-	Datum	   *attnumsd;
+	AttrNumber *attnums;
 	int			i;
 	ListCell   *lc;
 
 	numattrs = list_length(colnames);
-	attnumsd = palloc_array(Datum, numattrs);
+	attnums = palloc_array(AttrNumber, numattrs);
 
 	i = 0;
 	foreach(lc, colnames)
@@ -438,14 +505,14 @@ array_from_column_list(ParseState *pstate, const List *colnames, int location, R
 					 errmsg("column \"%s\" of relation \"%s\" does not exist",
 							colname, get_rel_name(relid)),
 					 parser_errposition(pstate, location)));
-		attnumsd[i++] = Int16GetDatum(attnum);
+		attnums[i++] = attnum;
 	}
 
 	for (int j = 0; j < numattrs; j++)
 	{
 		for (int k = j + 1; k < numattrs; k++)
 		{
-			if (DatumGetInt16(attnumsd[j]) == DatumGetInt16(attnumsd[k]))
+			if (attnums[j] == attnums[k])
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("graph key columns list must not contain duplicates"),
@@ -453,7 +520,7 @@ array_from_column_list(ParseState *pstate, const List *colnames, int location, R
 		}
 	}
 
-	return construct_array_builtin(attnumsd, numattrs, INT2OID);
+	return attnums;
 }
 
 static ArrayType *
@@ -768,6 +835,7 @@ insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGra
 	table_close(rel, NoLock);
 
 	tp = transformTargetList(pstate, proplist, EXPR_KIND_PROPGRAPH_PROPERTY);
+	assign_expr_collations(pstate, (Node *) tp);
 
 	foreach(lc, tp)
 	{
@@ -786,9 +854,15 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 {
 	Oid			propoid;
 	Oid			exprtypid;
+	int32		exprtypmod;
+	Oid			exprcollation;
 	Oid			proptypid;
+	int32		proptypmod;
+	Oid			propcollation;
 
 	exprtypid = exprType((const Node *) expr);
+	exprcollation = exprCollation((const Node *) expr);
+	exprtypmod = exprTypmod((const Node *) expr);
 
 	/*
 	 * Insert into pg_propgraph_property if not already existing.
@@ -805,6 +879,8 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 		ObjectAddress referenced;
 
 		proptypid = exprtypid;
+		proptypmod = exprtypmod;
+		propcollation = exprcollation;
 
 		rel = table_open(PropgraphPropertyRelationId, RowExclusiveLock);
 
@@ -814,6 +890,8 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 		namestrcpy(&propnamedata, propname);
 		values[Anum_pg_propgraph_property_pgpname - 1] = NameGetDatum(&propnamedata);
 		values[Anum_pg_propgraph_property_pgptypid - 1] = ObjectIdGetDatum(proptypid);
+		values[Anum_pg_propgraph_property_pgptypmod - 1] = Int32GetDatum(proptypmod);
+		values[Anum_pg_propgraph_property_pgpcollation - 1] = ObjectIdGetDatum(propcollation);
 
 		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 		CatalogTupleInsert(rel, tup);
@@ -830,7 +908,13 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 	}
 	else
 	{
-		proptypid = GetSysCacheOid1(PROPGRAPHPROPOID, Anum_pg_propgraph_property_pgptypid, ObjectIdGetDatum(propoid));
+		HeapTuple	pgptup = SearchSysCache1(PROPGRAPHPROPOID, ObjectIdGetDatum(propoid));
+		Form_pg_propgraph_property pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
+
+		proptypid = pgpform->pgptypid;
+		proptypmod = pgpform->pgptypmod;
+		propcollation = pgpform->pgpcollation;
+		ReleaseSysCache(pgptup);
 	}
 
 	/*
@@ -847,6 +931,32 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 				errdetail("In a property graph, a property of the same name has to have the same data type in each label."));
 	}
 
+	/* Similarly for collation */
+	if (propcollation != exprcollation)
+	{
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("property \"%s\" collation mismatch: %s vs. %s",
+					   propname, get_collation_name(propcollation), get_collation_name(exprcollation)),
+				errdetail("In a property graph, a property of the same name has to have the same collation in each label."));
+	}
+
+	/*
+	 * And typmod. It does not seem to be necessary to enforce typmod
+	 * consistency across properties with the same name. But when properties
+	 * with the same name have different typmods, it is not clear which one
+	 * should be used as the typmod of the graph property when typmod of a
+	 * property is requested before fetching any of the property expressions.
+	 */
+	if (proptypmod != exprtypmod)
+	{
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("property \"%s\" data type modifier mismatch: %d vs. %d",
+					   propname, proptypmod, exprtypmod),
+				errdetail("In a property graph, a property of the same name has to have the same type modifier in each label."));
+	}
+
 	/*
 	 * Insert into pg_propgraph_label_property
 	 */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index a26cfe09f8c..b38c8921603 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -535,8 +535,7 @@ exprTypmod(const Node *expr)
 		case T_PlaceHolderVar:
 			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 		case T_GraphPropertyRef:
-			/* TODO */
-			return -1;
+			return ((const GraphPropertyRef *) expr)->typmod;
 		default:
 			break;
 	}
@@ -1057,7 +1056,7 @@ exprCollation(const Node *expr)
 			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
 			break;
 		case T_GraphPropertyRef:
-			coll = DEFAULT_COLLATION_OID;	/* FIXME */
+			coll = ((const GraphPropertyRef *) expr)->collation;
 			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 33a1f3f2413..f91cece808f 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -546,6 +546,7 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 		case T_CaseTestExpr:
 		case T_SetToDefault:
 		case T_CurrentOfExpr:
+		case T_GraphPropertyRef:
 
 			/*
 			 * General case for childless expression nodes.  These should
@@ -571,13 +572,6 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 			location = exprLocation(node);
 			break;
 
-		case T_GraphPropertyRef:
-			/* FIXME */
-			collation = DEFAULT_COLLATION_OID;
-			strength = COLLATE_IMPLICIT;
-			location = -1;
-			break;
-
 		default:
 			{
 				/*
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index b088306b5b3..a8769a67b6a 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -56,18 +56,24 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
 		if (list_member(gpstate->variables, field1))
 		{
 			GraphPropertyRef *gpr = makeNode(GraphPropertyRef);
-			Oid			propid;
+			HeapTuple	pgptup;
+			Form_pg_propgraph_property pgpform;
 
-			propid = GetSysCacheOid2(PROPGRAPHPROPNAME, Anum_pg_propgraph_property_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
-			if (!propid)
+			pgptup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
+			if (!HeapTupleIsValid(pgptup))
 				ereport(ERROR,
 						errcode(ERRCODE_SYNTAX_ERROR),
 						errmsg("property \"%s\" does not exist", propname));
+			pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
 
 			gpr->location = cref->location;
 			gpr->elvarname = elvarname;
-			gpr->propid = propid;
-			gpr->typeId = GetSysCacheOid1(PROPGRAPHPROPOID, Anum_pg_propgraph_property_pgptypid, ObjectIdGetDatum(propid));
+			gpr->propid = pgpform->oid;
+			gpr->typeId = pgpform->pgptypid;
+			gpr->typmod = pgpform->pgptypmod;
+			gpr->collation = pgpform->pgpcollation;
+
+			ReleaseSysCache(pgptup);
 
 			return (Node *) gpr;
 		}
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 9c14fa3b1b7..914b829ae47 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -23,7 +23,9 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
+#include "parser/parse_collate.h"
 #include "parser/parse_node.h"
+#include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "parser/parse_relation.h"
@@ -83,7 +85,7 @@ struct path_element
 };
 
 static Node *replace_property_refs(Oid propgraphid, Node *node, const List *mappings);
-static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum);
+static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum);
 static List *generate_queries_for_path_pattern(RangeTblEntry *rte, List *element_patterns);
 static Query *generate_query_for_graph_path(RangeTblEntry *rte, List *path);
 static Node *generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist);
@@ -373,6 +375,23 @@ generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries,
 /*
  * Construct a query representing given graph path.
  *
+ * The query contains:
+ *
+ * 1. targetlist corresponding the COLUMNS clause of GRAPH_TABLE clause
+ *
+ * 2. quals corresponding to the WHERE clause of individual elements, WHERE
+ * clause in GRAPH_TABLE clause and quals representing edge-vertex links.
+ *
+ * 3. fromlist containing all elements in the path
+ *
+ * The collations of property expressions are obtained from the catalog and
+ * substituted in place of a property reference. The collations of expressions
+ * in COLUMNS and WHERE clauses are assigned before rewriting the graph table.
+ * The collations of the edge-vertex link quals are assigned when crafting those
+ * quals. Thus everything in the query that requires collation assignment has
+ * been taken care of already. So no collation assignment is required in this
+ * function.
+ *
  * More details in the prologue of generate_queries_for_path_pattern().
  */
 static Query *
@@ -552,6 +571,12 @@ generate_query_for_empty_path_pattern(RangeTblEntry *rte)
 /*
  * Construct a query which is UNION of given path queries.
  *
+ * The UNION query derives collations of its targetlist entries from the
+ * corresponding targetlist entries of the path queries and projects it. The
+ * targetlists of path queries being UNION'ed already have collations assigned.
+ * The same collations are used for targetlist of UNION query. Thus there is no
+ * separate collation assignment required in this function.
+ *
  * The function destroys given pathqueries list while constructing
  * SetOperationStmt recrursively. Hence the function always returns with
  * `pathqueries` set to NIL.
@@ -747,9 +772,11 @@ create_gpe_for_element(struct path_factor *pf, Oid elemoid)
 		 * each time.
 		 */
 		pe->src_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->src_pf->factorpos + 1,
+													 pe->srcvertexid,
 													 Anum_pg_propgraph_element_pgesrckey,
 													 Anum_pg_propgraph_element_pgesrcref);
 		pe->dest_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->dest_pf->factorpos + 1,
+													  pe->destvertexid,
 													  Anum_pg_propgraph_element_pgedestkey,
 													  Anum_pg_propgraph_element_pgedestref);
 	}
@@ -1125,7 +1152,7 @@ replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
  * Build join qualification expressions between edge and vertex tables.
  */
 static List *
-build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum)
+build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum)
 {
 	List	   *quals = NIL;
 	Form_pg_propgraph_element pgeform;
@@ -1134,6 +1161,11 @@ build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNum
 			   *d2;
 	int			n1,
 				n2;
+	ParseState *pstate = make_parsestate(NULL);
+	HeapTuple	reftup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(refid));
+	Oid			refrelid = ((Form_pg_propgraph_element) GETSTRUCT(reftup))->pgerelid;
+
+	ReleaseSysCache(reftup);
 
 	pgeform = (Form_pg_propgraph_element) GETSTRUCT(edgetup);
 
@@ -1150,26 +1182,33 @@ build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNum
 	{
 		AttrNumber	keyattn = DatumGetInt16(d1[i]);
 		AttrNumber	refattn = DatumGetInt16(d2[i]);
+		Var		   *keyvar;
+		Var		   *refvar;
 		Oid			atttypid;
-		TypeCacheEntry *typentry;
-		OpExpr	   *op;
+		int32		atttypmod;
+		Oid			attcoll;
+		Expr	   *linkqual;
+
+		get_atttypetypmodcoll(pgeform->pgerelid, keyattn, &atttypid, &atttypmod, &attcoll);
+		keyvar = makeVar(edgerti, keyattn, atttypid, atttypmod, attcoll, 0);
+		get_atttypetypmodcoll(refrelid, refattn, &atttypid, &atttypmod, &attcoll);
+		refvar = makeVar(refrti, refattn, atttypid, atttypmod, attcoll, 0);
 
 		/*
-		 * TODO: Assumes types the same on both sides; no collations yet. Some
-		 * of this could probably be shared with foreign key triggers.
+		 * We do not allow keys with conflicting collations (see
+		 * propgraph_edge_get_ref_keys()). Hence the qual constructed here
+		 * should not cause assign_expr_collations() to throw an error. But in
+		 * case we change that in future we should coerce edge key collation
+		 * to that of vertex key collation similar to how foreign key
+		 * constraints are crafted.
 		 */
-		atttypid = get_atttype(pgeform->pgerelid, keyattn);
-		typentry = lookup_type_cache(atttypid, TYPECACHE_EQ_OPR);
-
-		op = makeNode(OpExpr);
-		op->location = -1;
-		op->opno = typentry->eq_opr;
-		op->opresulttype = BOOLOID;
-		op->args = list_make2(makeVar(edgerti, keyattn, atttypid, -1, 0, 0),
-							  makeVar(refrti, refattn, atttypid, -1, 0, 0));
-		quals = lappend(quals, op);
+		linkqual = make_op(pstate, list_make2(makeString("pg_catalog"), makeString("=")),
+						   (Node *) keyvar, (Node *) refvar, NULL, -1);
+		quals = lappend(quals, linkqual);
 	}
 
+	assign_expr_collations(pstate, (Node *) quals);
+
 	return quals;
 }
 
diff --git a/src/include/catalog/pg_propgraph_property.h b/src/include/catalog/pg_propgraph_property.h
index b8921ace30c..240b34f0390 100644
--- a/src/include/catalog/pg_propgraph_property.h
+++ b/src/include/catalog/pg_propgraph_property.h
@@ -36,6 +36,12 @@ CATALOG(pg_propgraph_property,8306,PropgraphPropertyRelationId)
 
 	/* data type of the property */
 	Oid			pgptypid BKI_LOOKUP_OPT(pg_type);
+
+	/* typemod of the property */
+	int32		pgptypmod;
+
+	/* collation of the property */
+	Oid			pgpcollation BKI_LOOKUP_OPT(pg_collation);
 } FormData_pg_propgraph_property;
 
 /* ----------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index a246bd9dfd6..5a485ee471f 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2147,6 +2147,8 @@ typedef struct GraphPropertyRef
 	const char *elvarname;
 	Oid			propid;
 	Oid			typeId;
+	int32		typmod;
+	Oid			collation;
 	ParseLoc	location;
 } GraphPropertyRef;
 
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 43316fbc029..d9aa2e79cc7 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -187,6 +187,94 @@ GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
 GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
 ERROR:  invalid privilege type UPDATE for property graph
 RESET ROLE;
+-- collation
+CREATE TABLE tc1 (a int, b text);
+CREATE TABLE tc2 (a int, b text);
+CREATE TABLE tc3 (a int, b text COLLATE "C");
+CREATE TABLE ec1 (ek1 int, ek2 int, eb text);
+CREATE TABLE ec2 (ek1 int, ek2 int, eb text COLLATE "POSIX");
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a), tc3 KEY (a)); -- fail
+ERROR:  property "b" collation mismatch: default vs. C
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)); -- fail
+ERROR:  property "eb" collation mismatch: default vs. POSIX
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a) DEFAULT LABEL PROPERTIES (a), tc3 KEY (b))
+    EDGE TABLES (
+        ec2 KEY (ek1, eb)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (eb) REFERENCES tc3 (b)); -- fail
+ERROR:  collation mismatch in DESTINATION key of edge "ec2": POSIX vs. C
+LINE 4:         ec2 KEY (ek1, eb)
+                ^
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a));
+ALTER PROPERTY GRAPH gc1 ADD VERTEX TABLES (tc3 KEY (a)); -- fail
+ERROR:  property "b" collation mismatch: default vs. C
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+ALTER PROPERTY GRAPH gc1 ADD EDGE TABLES (
+            ec2 KEY (ek1, ek2)
+                SOURCE KEY (ek1) REFERENCES tc1 (a)
+                DESTINATION KEY (ek2) REFERENCES tc2 (a)); -- fail
+ERROR:  property "eb" collation mismatch: default vs. POSIX
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+ALTER PROPERTY GRAPH gc1
+    ADD VERTEX TABLES (
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b COLLATE pg_catalog.DEFAULT AS b));
+ALTER PROPERTY GRAPH gc1 ADD EDGE TABLES (
+            ec2 KEY (ek1, ek2)
+                SOURCE KEY (ek1) REFERENCES tc1 (a)
+                DESTINATION KEY (ek2) REFERENCES tc2 (a)
+                DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb));
+DROP PROPERTY GRAPH gc1;
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (
+        tc1 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar COLLATE "C" AS b),
+        tc2 KEY (a) DEFAULT LABEL PROPERTIES (a, (b COLLATE "C")::varchar AS b),
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar AS b))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb));
+-- type incosistency check
+CREATE TABLE v1 (a int primary key, b text);
+CREATE TABLE e(k1 text, k2 text, c text);
+CREATE TABLE v2 (m text, n text);
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+        SOURCE KEY (k1) REFERENCES v1(a)
+        DESTINATION KEY (k2) REFERENCES v2(m)); -- fail
+ERROR:  no operator exists for SOURCE key comparison of edge "e": text pg_catalog.= integer
+LINE 4:         e KEY (k1, k2)
+                ^
+ALTER TABLE e DROP COLUMN k1, ADD COLUMN k1 bigint primary key;
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+        SOURCE KEY (k1) REFERENCES v1(a)
+        DESTINATION KEY (k2) REFERENCES v2(m));
 -- information schema
 SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name 
@@ -196,7 +284,9 @@ SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
  regression             | create_property_graph_tests | g3
  regression             | create_property_graph_tests | g4
  regression             | create_property_graph_tests | g5
-(5 rows)
+ regression             | create_property_graph_tests | gc1
+ regression             | create_property_graph_tests | gt
+(7 rows)
 
 SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias;
  property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | element_table_kind | table_catalog |        table_schema         | table_name | element_table_definition 
@@ -216,7 +306,15 @@ SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name,
  regression             | create_property_graph_tests | g5                  | t11                 | VERTEX             | regression    | create_property_graph_tests | t11        | 
  regression             | create_property_graph_tests | g5                  | t12                 | VERTEX             | regression    | create_property_graph_tests | t12        | 
  regression             | create_property_graph_tests | g5                  | t13                 | EDGE               | regression    | create_property_graph_tests | t13        | 
-(15 rows)
+ regression             | create_property_graph_tests | gc1                 | ec1                 | EDGE               | regression    | create_property_graph_tests | ec1        | 
+ regression             | create_property_graph_tests | gc1                 | ec2                 | EDGE               | regression    | create_property_graph_tests | ec2        | 
+ regression             | create_property_graph_tests | gc1                 | tc1                 | VERTEX             | regression    | create_property_graph_tests | tc1        | 
+ regression             | create_property_graph_tests | gc1                 | tc2                 | VERTEX             | regression    | create_property_graph_tests | tc2        | 
+ regression             | create_property_graph_tests | gc1                 | tc3                 | VERTEX             | regression    | create_property_graph_tests | tc3        | 
+ regression             | create_property_graph_tests | gt                  | e                   | EDGE               | regression    | create_property_graph_tests | e          | 
+ regression             | create_property_graph_tests | gt                  | v1                  | VERTEX             | regression    | create_property_graph_tests | v1         | 
+ regression             | create_property_graph_tests | gt                  | v2                  | VERTEX             | regression    | create_property_graph_tests | v2         | 
+(23 rows)
 
 SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position;
  property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | column_name | ordinal_position 
@@ -240,7 +338,18 @@ SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_
  regression             | create_property_graph_tests | g5                  | t11                 | a           |                1
  regression             | create_property_graph_tests | g5                  | t12                 | b           |                1
  regression             | create_property_graph_tests | g5                  | t13                 | c           |                1
-(19 rows)
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek1         |                1
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek2         |                2
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek1         |                1
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek2         |                2
+ regression             | create_property_graph_tests | gc1                 | tc1                 | a           |                1
+ regression             | create_property_graph_tests | gc1                 | tc2                 | a           |                1
+ regression             | create_property_graph_tests | gc1                 | tc3                 | a           |                1
+ regression             | create_property_graph_tests | gt                  | e                   | k1          |                1
+ regression             | create_property_graph_tests | gt                  | e                   | k2          |                2
+ regression             | create_property_graph_tests | gt                  | v1                  | a           |                1
+ regression             | create_property_graph_tests | gt                  | v2                  | m           |                1
+(30 rows)
 
 SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position;
  property_graph_catalog |    property_graph_schema    | property_graph_name | edge_table_alias | vertex_table_alias |  edge_end   | edge_table_column_name | vertex_table_column_name | ordinal_position 
@@ -257,7 +366,13 @@ SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_grap
  regression             | create_property_graph_tests | g4                  | e2               | t3                 | DESTINATION | t                      | y                        |                2
  regression             | create_property_graph_tests | g5                  | t13              | t11                | SOURCE      | e                      | a                        |                1
  regression             | create_property_graph_tests | g5                  | t13              | t12                | DESTINATION | e                      | b                        |                1
-(12 rows)
+ regression             | create_property_graph_tests | gc1                 | ec1              | tc1                | SOURCE      | ek1                    | a                        |                1
+ regression             | create_property_graph_tests | gc1                 | ec1              | tc2                | DESTINATION | ek2                    | a                        |                1
+ regression             | create_property_graph_tests | gc1                 | ec2              | tc1                | SOURCE      | ek1                    | a                        |                1
+ regression             | create_property_graph_tests | gc1                 | ec2              | tc2                | DESTINATION | ek2                    | a                        |                1
+ regression             | create_property_graph_tests | gt                  | e                | v1                 | SOURCE      | k1                     | a                        |                1
+ regression             | create_property_graph_tests | gt                  | e                | v2                 | DESTINATION | k2                     | m                        |                1
+(18 rows)
 
 SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | label_name 
@@ -280,11 +395,19 @@ SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph
  regression             | create_property_graph_tests | g5                  | t11                 | t11
  regression             | create_property_graph_tests | g5                  | t12                 | t12
  regression             | create_property_graph_tests | g5                  | t13                 | t13
-(18 rows)
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ec1
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ec2
+ regression             | create_property_graph_tests | gc1                 | tc1                 | tc1
+ regression             | create_property_graph_tests | gc1                 | tc2                 | tc2
+ regression             | create_property_graph_tests | gc1                 | tc3                 | tc3
+ regression             | create_property_graph_tests | gt                  | e                   | e
+ regression             | create_property_graph_tests | gt                  | v1                  | v1
+ regression             | create_property_graph_tests | gt                  | v2                  | v2
+(26 rows)
 
 SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name;
- property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | property_name | property_expression 
-------------------------+-----------------------------+---------------------+---------------------+---------------+---------------------
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | property_name |         property_expression          
+------------------------+-----------------------------+---------------------+---------------------+---------------+--------------------------------------
  regression             | create_property_graph_tests | g2                  | e1                  | a             | a
  regression             | create_property_graph_tests | g2                  | e1                  | i             | i
  regression             | create_property_graph_tests | g2                  | e1                  | t             | t
@@ -320,7 +443,26 @@ SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_g
  regression             | create_property_graph_tests | g5                  | t13                 | c             | c
  regression             | create_property_graph_tests | g5                  | t13                 | d             | d
  regression             | create_property_graph_tests | g5                  | t13                 | e             | e
-(35 rows)
+ regression             | create_property_graph_tests | gc1                 | ec1                 | eb            | eb
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek1           | ek1
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek2           | ek2
+ regression             | create_property_graph_tests | gc1                 | ec2                 | eb            | (eb COLLATE "default")
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek1           | ek1
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek2           | ek2
+ regression             | create_property_graph_tests | gc1                 | tc1                 | a             | a
+ regression             | create_property_graph_tests | gc1                 | tc1                 | b             | ((b)::character varying COLLATE "C")
+ regression             | create_property_graph_tests | gc1                 | tc2                 | a             | a
+ regression             | create_property_graph_tests | gc1                 | tc2                 | b             | ((b)::character varying COLLATE "C")
+ regression             | create_property_graph_tests | gc1                 | tc3                 | a             | a
+ regression             | create_property_graph_tests | gc1                 | tc3                 | b             | (b)::character varying
+ regression             | create_property_graph_tests | gt                  | e                   | c             | c
+ regression             | create_property_graph_tests | gt                  | e                   | k1            | k1
+ regression             | create_property_graph_tests | gt                  | e                   | k2            | k2
+ regression             | create_property_graph_tests | gt                  | v1                  | a             | a
+ regression             | create_property_graph_tests | gt                  | v1                  | b             | b
+ regression             | create_property_graph_tests | gt                  | v2                  | m             | m
+ regression             | create_property_graph_tests | gt                  | v2                  | n             | n
+(54 rows)
 
 SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | label_name | property_name 
@@ -367,7 +509,26 @@ SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_nam
  regression             | create_property_graph_tests | g5                  | t13        | c
  regression             | create_property_graph_tests | g5                  | t13        | d
  regression             | create_property_graph_tests | g5                  | t13        | e
-(42 rows)
+ regression             | create_property_graph_tests | gc1                 | ec1        | eb
+ regression             | create_property_graph_tests | gc1                 | ec1        | ek1
+ regression             | create_property_graph_tests | gc1                 | ec1        | ek2
+ regression             | create_property_graph_tests | gc1                 | ec2        | eb
+ regression             | create_property_graph_tests | gc1                 | ec2        | ek1
+ regression             | create_property_graph_tests | gc1                 | ec2        | ek2
+ regression             | create_property_graph_tests | gc1                 | tc1        | a
+ regression             | create_property_graph_tests | gc1                 | tc1        | b
+ regression             | create_property_graph_tests | gc1                 | tc2        | a
+ regression             | create_property_graph_tests | gc1                 | tc2        | b
+ regression             | create_property_graph_tests | gc1                 | tc3        | a
+ regression             | create_property_graph_tests | gc1                 | tc3        | b
+ regression             | create_property_graph_tests | gt                  | e          | c
+ regression             | create_property_graph_tests | gt                  | e          | k1
+ regression             | create_property_graph_tests | gt                  | e          | k2
+ regression             | create_property_graph_tests | gt                  | v1         | a
+ regression             | create_property_graph_tests | gt                  | v1         | b
+ regression             | create_property_graph_tests | gt                  | v2         | m
+ regression             | create_property_graph_tests | gt                  | v2         | n
+(61 rows)
 
 SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name;
  property_graph_catalog |    property_graph_schema    | property_graph_name | label_name 
@@ -390,39 +551,59 @@ SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_n
  regression             | create_property_graph_tests | g5                  | t11
  regression             | create_property_graph_tests | g5                  | t12
  regression             | create_property_graph_tests | g5                  | t13
-(18 rows)
+ regression             | create_property_graph_tests | gc1                 | ec1
+ regression             | create_property_graph_tests | gc1                 | ec2
+ regression             | create_property_graph_tests | gc1                 | tc1
+ regression             | create_property_graph_tests | gc1                 | tc2
+ regression             | create_property_graph_tests | gc1                 | tc3
+ regression             | create_property_graph_tests | gt                  | e
+ regression             | create_property_graph_tests | gt                  | v1
+ regression             | create_property_graph_tests | gt                  | v2
+(26 rows)
 
 SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name;
- property_graph_catalog |    property_graph_schema    | property_graph_name | property_name | data_type | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier 
-------------------------+-----------------------------+---------------------+---------------+-----------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+---------------------------+--------------------------+------------------------+---------------+--------------+------------+---------------------+----------------
- regression             | create_property_graph_tests | g2                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
- regression             | create_property_graph_tests | g2                  | b             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
- regression             | create_property_graph_tests | g2                  | i             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
- regression             | create_property_graph_tests | g2                  | j             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | j
- regression             | create_property_graph_tests | g2                  | k             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | k
- regression             | create_property_graph_tests | g2                  | t             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
- regression             | create_property_graph_tests | g2                  | x             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
- regression             | create_property_graph_tests | g2                  | y             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
- regression             | create_property_graph_tests | g2                  | z             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
- regression             | create_property_graph_tests | g3                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
- regression             | create_property_graph_tests | g3                  | b             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
- regression             | create_property_graph_tests | g3                  | x             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
- regression             | create_property_graph_tests | g3                  | y             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
- regression             | create_property_graph_tests | g3                  | z             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
- regression             | create_property_graph_tests | g4                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
- regression             | create_property_graph_tests | g4                  | i             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
- regression             | create_property_graph_tests | g4                  | i_j           | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i_j
- regression             | create_property_graph_tests | g4                  | kk            | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | kk
- regression             | create_property_graph_tests | g4                  | t             | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
- regression             | create_property_graph_tests | g4                  | x             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
- regression             | create_property_graph_tests | g4                  | yy            | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | yy
- regression             | create_property_graph_tests | g4                  | zz            | text      |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | zz
- regression             | create_property_graph_tests | g5                  | a             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
- regression             | create_property_graph_tests | g5                  | b             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | b
- regression             | create_property_graph_tests | g5                  | c             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | c
- regression             | create_property_graph_tests | g5                  | d             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | d
- regression             | create_property_graph_tests | g5                  | e             | integer   |                          |                        |                       |                      |                    |                   |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | e
-(27 rows)
+ property_graph_catalog |    property_graph_schema    | property_graph_name | property_name |     data_type     | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier 
+------------------------+-----------------------------+---------------------+---------------+-------------------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+---------------------------+--------------------------+------------------------+---------------+--------------+------------+---------------------+----------------
+ regression             | create_property_graph_tests | g2                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g2                  | b             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g2                  | i             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
+ regression             | create_property_graph_tests | g2                  | j             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | j
+ regression             | create_property_graph_tests | g2                  | k             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | k
+ regression             | create_property_graph_tests | g2                  | t             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
+ regression             | create_property_graph_tests | g2                  | x             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g2                  | y             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
+ regression             | create_property_graph_tests | g2                  | z             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
+ regression             | create_property_graph_tests | g3                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g3                  | b             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g3                  | x             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g3                  | y             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
+ regression             | create_property_graph_tests | g3                  | z             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
+ regression             | create_property_graph_tests | g4                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g4                  | i             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
+ regression             | create_property_graph_tests | g4                  | i_j           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i_j
+ regression             | create_property_graph_tests | g4                  | kk            | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | kk
+ regression             | create_property_graph_tests | g4                  | t             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
+ regression             | create_property_graph_tests | g4                  | x             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g4                  | yy            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | yy
+ regression             | create_property_graph_tests | g4                  | zz            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | zz
+ regression             | create_property_graph_tests | g5                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g5                  | b             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g5                  | c             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | c
+ regression             | create_property_graph_tests | g5                  | d             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | d
+ regression             | create_property_graph_tests | g5                  | e             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | e
+ regression             | create_property_graph_tests | gc1                 | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | gc1                 | b             | character varying |                          |                        |                       |                      |                    | regression        | pg_catalog       | C              |                   |                         |               |                    |               |                    | regression                | pg_catalog               | varchar                |               |              |            |                     | b
+ regression             | create_property_graph_tests | gc1                 | eb            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | eb
+ regression             | create_property_graph_tests | gc1                 | ek1           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | ek1
+ regression             | create_property_graph_tests | gc1                 | ek2           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | ek2
+ regression             | create_property_graph_tests | gt                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | gt                  | b             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | gt                  | c             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | c
+ regression             | create_property_graph_tests | gt                  | k1            | bigint            |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int8                   |               |              |            |                     | k1
+ regression             | create_property_graph_tests | gt                  | k2            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | k2
+ regression             | create_property_graph_tests | gt                  | m             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | m
+ regression             | create_property_graph_tests | gt                  | n             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | n
+(39 rows)
 
 SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name;
        grantor       |       grantee       | property_graph_catalog |    property_graph_schema    | property_graph_name | privilege_type | is_grantable 
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 3796297cb07..0a574b10f4b 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -517,7 +517,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a is vl1) COLUMNS (a.vname AS sel
  v13  | v23     |      30 |       1030
 (2 rows)
 
--- add an edge with same vertex as source and destination to test loops
+-- add an edge with same vertex as source and destination to test loops. Also
+-- use this for collation tests
 CREATE TABLE e3_3 (src_id int,
                     dest_id int,
                     ename varchar(10),
@@ -530,8 +531,35 @@ ALTER PROPERTY GRAPH g1 ADD EDGE TABLES (
         LABEL l1 PROPERTIES (ename AS elname)
 );
 INSERT INTO e3_3 VALUES (2003, 2003, 'e331', 10010);
+INSERT INTO e3_3 VALUES (2003, 2003, 'E331', 10010);
 -- cyclic pattern with edge patterns with same variable name
-SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name ASC;
+ self | loop_name 
+------+-----------
+ v33  | e331
+ v33  | E331
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name COLLATE "C" ASC;
+ self | loop_name 
+------+-----------
+ v33  | E331
+ v33  | e331
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b IS el2 WHERE b.ename > 'E331' COLLATE "C"]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.ename > 'E331' COLLATE "C" COLUMNS (a.vname AS self, b.ename AS loop_name));
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) WHERE loop_name > 'E331' COLLATE "C";
  self | loop_name 
 ------+-----------
  v33  | e331
@@ -541,40 +569,89 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS an
 ERROR:  An edge can not connect more than two vertexes even in a cyclic pattern.
 -- property graph with some of the elements, labels and properties same as the
 -- previous one. Test whether components from the specified property graph are
--- used.
+-- used. Also use this for collation tests
 create property graph g2
 vertex tables (
 	v1
-        label l1 properties ('g2.' || vname as elname),
+        label l1 properties ('g2.' || vname COLLATE "C" as elname),
 	v2 key (id1, id2)
-        label l1 properties ('g2.' || vname as elname),
+        label l1 properties ('g2.' || vname COLLATE "C" as elname),
 	v3
-        label l1 properties ('g2.' || vname as elname)
+        label l1 properties ('g2.' || vname COLLATE "C" as elname)
 )
 edge tables (
 	e1_2 key (id_1, id_2_1, id_2_2)
 		source key (id_1) references v1 (id)
 		destination key (id_2_1, id_2_2) references v2 (id1, id2)
-        label l1 properties ('g2.' || ename as elname),
+        label l1 properties ('g2.' || ename COLLATE "C" as elname),
 	e1_3
 		source key (id_1) references v1 (id)
 		destination key (id_3) references v3 (id)
-        label l1 properties ('g2.' || ename as elname),
+        label l1 properties ('g2.' || ename COLLATE "C" as elname),
     e2_3 key (id_2_1, id_2_2, id_3)
         source key (id_2_1, id_2_2) references v2 (id1, id2)
         destination key (id_3) references v3 (id)
-        label l1 properties ('g2.' || ename as elname)
+        label l1 properties ('g2.' || ename COLLATE "C" as elname),
+    e3_3 KEY (src_id, dest_id)
+        SOURCE KEY (src_id) REFERENCES v3 (id)
+        DESTINATION KEY (src_id) REFERENCES v3 (id)
+        LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" as elname)
 );
-select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn));
+select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn)) ORDER BY 1, 2, 3;
    sn   |   cn    |   dn   
 --------+---------+--------
- g2.v12 | g2.e122 | g2.v21
  g2.v11 | g2.e121 | g2.v22
- g2.v13 | g2.e123 | g2.v23
  g2.v11 | g2.e131 | g2.v33
  g2.v11 | g2.e132 | g2.v31
+ g2.v12 | g2.e122 | g2.v21
+ g2.v13 | g2.e123 | g2.v23
  g2.v22 | g2.e231 | g2.v32
-(6 rows)
+ g2.v33 | g2.E331 | g2.v33
+ g2.v33 | g2.e331 | g2.v33
+(8 rows)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname COLLATE pg_catalog."default" AS loop_name)) ORDER BY loop_name ASC;
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.e331
+ g2.v33 | g2.E331
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b WHERE b.elname > 'g2.E331']->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name));
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.elname > 'g2.E331' COLUMNS (a.elname AS self, b.elname AS loop_name));
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name)) WHERE loop_name > 'g2.E331';
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b WHERE b.elname > 'g2.e331' COLLATE pg_catalog."default"]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name));
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.E331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.elname > 'g2.e331' COLLATE pg_catalog."default" COLUMNS (a.elname AS self, b.elname AS loop_name));
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.E331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name)) WHERE loop_name > 'g2.e331' COLLATE pg_catalog."default";
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.E331
+(1 row)
 
 CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));
 SELECT pg_get_viewdef('customers_us'::regclass);
@@ -671,6 +748,14 @@ SELECT *
            1 | customer1 | US      | redacted1
 (1 row)
 
+-- graph table in a subquery
+SELECT * FROM customers co WHERE co.customer_id =
+    (SELECT customer_id FROM GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cg.customer_id)));
+ customer_id |   name    | address 
+-------------+-----------+---------
+           1 | customer1 | US
+(1 row)
+
 -- query within graph table
 SELECT sname, dname
     FROM GRAPH_TABLE (g1 MATCH (src)->(dest)
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 3b41fc7ba04..9ad17275f24 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -277,3 +277,4 @@ NOTICE:  checking pg_propgraph_label_property {plppropid} => pg_propgraph_proper
 NOTICE:  checking pg_propgraph_label_property {plpellabelid} => pg_propgraph_element_label {oid}
 NOTICE:  checking pg_propgraph_property {pgppgid} => pg_class {oid}
 NOTICE:  checking pg_propgraph_property {pgptypid} => pg_type {oid}
+NOTICE:  checking pg_propgraph_property {pgpcollation} => pg_collation {oid}
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 4f9b5c0349b..9a1fe4fc6de 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -147,6 +147,83 @@ GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
 GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
 RESET ROLE;
 
+-- collation
+CREATE TABLE tc1 (a int, b text);
+CREATE TABLE tc2 (a int, b text);
+CREATE TABLE tc3 (a int, b text COLLATE "C");
+
+CREATE TABLE ec1 (ek1 int, ek2 int, eb text);
+CREATE TABLE ec2 (ek1 int, ek2 int, eb text COLLATE "POSIX");
+
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a), tc3 KEY (a)); -- fail
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)); -- fail
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a) DEFAULT LABEL PROPERTIES (a), tc3 KEY (b))
+    EDGE TABLES (
+        ec2 KEY (ek1, eb)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (eb) REFERENCES tc3 (b)); -- fail
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a));
+ALTER PROPERTY GRAPH gc1 ADD VERTEX TABLES (tc3 KEY (a)); -- fail
+ALTER PROPERTY GRAPH gc1 ADD EDGE TABLES (
+            ec2 KEY (ek1, ek2)
+                SOURCE KEY (ek1) REFERENCES tc1 (a)
+                DESTINATION KEY (ek2) REFERENCES tc2 (a)); -- fail
+ALTER PROPERTY GRAPH gc1
+    ADD VERTEX TABLES (
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b COLLATE pg_catalog.DEFAULT AS b));
+ALTER PROPERTY GRAPH gc1 ADD EDGE TABLES (
+            ec2 KEY (ek1, ek2)
+                SOURCE KEY (ek1) REFERENCES tc1 (a)
+                DESTINATION KEY (ek2) REFERENCES tc2 (a)
+                DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb));
+DROP PROPERTY GRAPH gc1;
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (
+        tc1 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar COLLATE "C" AS b),
+        tc2 KEY (a) DEFAULT LABEL PROPERTIES (a, (b COLLATE "C")::varchar AS b),
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar AS b))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb));
+
+-- type incosistency check
+CREATE TABLE v1 (a int primary key, b text);
+CREATE TABLE e(k1 text, k2 text, c text);
+CREATE TABLE v2 (m text, n text);
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+        SOURCE KEY (k1) REFERENCES v1(a)
+        DESTINATION KEY (k2) REFERENCES v2(m)); -- fail
+ALTER TABLE e DROP COLUMN k1, ADD COLUMN k1 bigint primary key;
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+        SOURCE KEY (k1) REFERENCES v1(a)
+        DESTINATION KEY (k2) REFERENCES v2(m));
 
 -- information schema
 
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 905be9df01b..977bf2774e5 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -322,7 +322,8 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a is vl2) WHERE a.vname <>
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a is vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a is vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
 
--- add an edge with same vertex as source and destination to test loops
+-- add an edge with same vertex as source and destination to test loops. Also
+-- use this for collation tests
 CREATE TABLE e3_3 (src_id int,
                     dest_id int,
                     ename varchar(10),
@@ -336,37 +337,53 @@ ALTER PROPERTY GRAPH g1 ADD EDGE TABLES (
 );
 
 INSERT INTO e3_3 VALUES (2003, 2003, 'e331', 10010);
+INSERT INTO e3_3 VALUES (2003, 2003, 'E331', 10010);
 -- cyclic pattern with edge patterns with same variable name
-SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name ASC;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name COLLATE "C" ASC;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b IS el2 WHERE b.ename > 'E331' COLLATE "C"]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.ename > 'E331' COLLATE "C" COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) WHERE loop_name > 'E331' COLLATE "C";
 SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS aname, b.ename AS bname, c.vname AS cname, d.vname AS dname)); --error
 
 -- property graph with some of the elements, labels and properties same as the
 -- previous one. Test whether components from the specified property graph are
--- used.
+-- used. Also use this for collation tests
 create property graph g2
 vertex tables (
 	v1
-        label l1 properties ('g2.' || vname as elname),
+        label l1 properties ('g2.' || vname COLLATE "C" as elname),
 	v2 key (id1, id2)
-        label l1 properties ('g2.' || vname as elname),
+        label l1 properties ('g2.' || vname COLLATE "C" as elname),
 	v3
-        label l1 properties ('g2.' || vname as elname)
+        label l1 properties ('g2.' || vname COLLATE "C" as elname)
 )
 edge tables (
 	e1_2 key (id_1, id_2_1, id_2_2)
 		source key (id_1) references v1 (id)
 		destination key (id_2_1, id_2_2) references v2 (id1, id2)
-        label l1 properties ('g2.' || ename as elname),
+        label l1 properties ('g2.' || ename COLLATE "C" as elname),
 	e1_3
 		source key (id_1) references v1 (id)
 		destination key (id_3) references v3 (id)
-        label l1 properties ('g2.' || ename as elname),
+        label l1 properties ('g2.' || ename COLLATE "C" as elname),
     e2_3 key (id_2_1, id_2_2, id_3)
         source key (id_2_1, id_2_2) references v2 (id1, id2)
         destination key (id_3) references v3 (id)
-        label l1 properties ('g2.' || ename as elname)
+        label l1 properties ('g2.' || ename COLLATE "C" as elname),
+    e3_3 KEY (src_id, dest_id)
+        SOURCE KEY (src_id) REFERENCES v3 (id)
+        DESTINATION KEY (src_id) REFERENCES v3 (id)
+        LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" as elname)
 );
-select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn));
+select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn)) ORDER BY 1, 2, 3;
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname COLLATE pg_catalog."default" AS loop_name)) ORDER BY loop_name ASC;
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b WHERE b.elname > 'g2.E331']->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name));
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.elname > 'g2.E331' COLUMNS (a.elname AS self, b.elname AS loop_name));
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name)) WHERE loop_name > 'g2.E331';
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b WHERE b.elname > 'g2.e331' COLLATE pg_catalog."default"]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name));
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.elname > 'g2.e331' COLLATE pg_catalog."default" COLUMNS (a.elname AS self, b.elname AS loop_name));
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name)) WHERE loop_name > 'g2.e331' COLLATE pg_catalog."default";

@@ -428,6 +445,10 @@ SELECT *
                               COLUMNS (cg.name_redacted AS customer_name_redacted))
     WHERE co.customer_id = 1;
 
+-- graph table in a subquery
+SELECT * FROM customers co WHERE co.customer_id =
+    (SELECT customer_id FROM GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cg.customer_id)));
+
 -- query within graph table
 SELECT sname, dname
     FROM GRAPH_TABLE (g1 MATCH (src)->(dest)
-- 
2.34.1

From a88e54f18f76f571072a1c790c762385872be389 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 28 Oct 2024 16:43:40 +0530
Subject: [PATCH 8/8] RLS tests

The commit tests interaction of property graph with RLS.  The test
queries referencing property graph are crafted from existing queries by
using equivalent GRAPH_TABLE constructs. Thus we just make sure that the
queries using GRAPH_TABLE constructs behave in the same manner and
provide the same output as the corresponding existing queries.

The tests covering simple tables, inheritance and partitioned tables
establish that the RLS is applied even when the tables are accessed
through GRAPH_TABLE.  That establishes that the RLS policies will be
applied when interacting with other features like recursive policies,
prepared statement invalidation, security barrier views, CTE, INSERT ...
SELECT, COPY, joins, non-target relations etc. This assumption is based
on the current implementation which rewrites GRAPH_TABLE clause well
before the RLS policies are added. In case the implementation changes in
future, these additional test queries will provide the required
extensive coverage.

Note: The tests for Bug #15708 are already covered earlier, hence not
added again.

Author: Ashutosh Bapat <[email-masked]>
---
 src/test/regress/expected/rowsecurity.out | 2176 +++++++++++++++++++--
 src/test/regress/sql/rowsecurity.sql      |  309 ++-
 2 files changed, 2364 insertions(+), 121 deletions(-)

diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 6d127c19f47..0c85836408c 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -72,6 +72,13 @@ INSERT INTO document VALUES
     ( 8, 44, 1, 'regress_rls_carol', 'great manga'),
     ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),
     (10, 33, 2, 'regress_rls_dave', 'awesome technology book');
+CREATE PROPERTY GRAPH acc_cat
+    VERTEX TABLES (uaccount, category)
+    EDGE TABLES (
+        document
+        SOURCE KEY (dauthor) REFERENCES uaccount (pguser)
+        DESTINATION KEY (cid) REFERENCES category (cid));
+GRANT ALL ON acc_cat TO public;
 ALTER TABLE document ENABLE ROW LEVEL SECURITY;
 -- user's security level must be higher than or equal to document's
 CREATE POLICY p1 ON document AS PERMISSIVE
@@ -93,24 +100,26 @@ CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave
 CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave
     USING (cid <> 44);
 \dp
-                                                                   Access privileges
-       Schema       |   Name   | Type  |              Access privileges               | Column privileges |                  Policies                  
---------------------+----------+-------+----------------------------------------------+-------------------+--------------------------------------------
- regress_rls_schema | category | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | 
-                    |          |       | =arwdDxtm/regress_rls_alice                  |                   | 
- regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | p1:                                       +
-                    |          |       | =arwdDxtm/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +
-                    |          |       |                                              |                   |    FROM uaccount                          +
-                    |          |       |                                              |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+
-                    |          |       |                                              |                   | p2r (RESTRICTIVE):                        +
-                    |          |       |                                              |                   |   (u): ((cid <> 44) AND (cid < 50))       +
-                    |          |       |                                              |                   |   to: regress_rls_dave                    +
-                    |          |       |                                              |                   | p1r (RESTRICTIVE):                        +
-                    |          |       |                                              |                   |   (u): (cid <> 44)                        +
-                    |          |       |                                              |                   |   to: regress_rls_dave
- regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | 
-                    |          |       | =r/regress_rls_alice                         |                   | 
-(3 rows)
+                                                                       Access privileges
+       Schema       |   Name   |      Type      |              Access privileges               | Column privileges |                  Policies                  
+--------------------+----------+----------------+----------------------------------------------+-------------------+--------------------------------------------
+ regress_rls_schema | acc_cat  | property graph | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | 
+                    |          |                | =arwdDxtm/regress_rls_alice                  |                   | 
+ regress_rls_schema | category | table          | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | 
+                    |          |                | =arwdDxtm/regress_rls_alice                  |                   | 
+ regress_rls_schema | document | table          | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | p1:                                       +
+                    |          |                | =arwdDxtm/regress_rls_alice                  |                   |   (u): (dlevel <= ( SELECT uaccount.seclv +
+                    |          |                |                                              |                   |    FROM uaccount                          +
+                    |          |                |                                              |                   |   WHERE (uaccount.pguser = CURRENT_USER)))+
+                    |          |                |                                              |                   | p2r (RESTRICTIVE):                        +
+                    |          |                |                                              |                   |   (u): ((cid <> 44) AND (cid < 50))       +
+                    |          |                |                                              |                   |   to: regress_rls_dave                    +
+                    |          |                |                                              |                   | p1r (RESTRICTIVE):                        +
+                    |          |                |                                              |                   |   (u): (cid <> 44)                        +
+                    |          |                |                                              |                   |   to: regress_rls_dave
+ regress_rls_schema | uaccount | table          | regress_rls_alice=arwdDxtm/regress_rls_alice+|                   | 
+                    |          |                | =r/regress_rls_alice                         |                   | 
+(4 rows)
 
 \d document
         Table "regress_rls_schema.document"
@@ -180,6 +189,23 @@ NOTICE:  f_leak => awesome science fiction
   22 |   9 |      1 | regress_rls_dave  | awesome science fiction | science fiction
 (5 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great manga
+NOTICE:  f_leak => awesome science fiction
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   4 |      1 | my first manga          |  44 | manga
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   8 |      1 | great manga             |  44 | manga
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+(5 rows)
+
 -- try a sampled version
 SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
   WHERE f_leak(dtitle) ORDER BY did;
@@ -247,6 +273,33 @@ NOTICE:  f_leak => awesome technology book
   33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology
 (10 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => awesome technology book
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   2 |      2 | my second novel         |  11 | novel
+ regress_rls_bob   |   3 |      2 | my science fiction      |  22 | science fiction
+ regress_rls_bob   |   4 |      1 | my first manga          |  44 | manga
+ regress_rls_bob   |   5 |      2 | my second manga         |  44 | manga
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book   |  33 | technology
+ regress_rls_carol |   8 |      1 | great manga             |  44 | manga
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+ regress_rls_dave  |  10 |      2 | awesome technology book |  33 | technology
+(10 rows)
+
 -- try a sampled version
 SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
   WHERE f_leak(dtitle) ORDER BY did;
@@ -288,6 +341,28 @@ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dt
                Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
 (9 rows)
 
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+                                      QUERY PLAN                                      
+--------------------------------------------------------------------------------------
+ Sort
+   Sort Key: document.did
+   InitPlan 1
+     ->  Index Scan using uaccount_pkey on uaccount uaccount_1
+           Index Cond: (pguser = CURRENT_USER)
+   ->  Hash Join
+         Hash Cond: (document.dauthor = uaccount.pguser)
+         ->  Hash Join
+               Hash Cond: (category.cid = document.cid)
+               ->  Seq Scan on category
+               ->  Hash
+                     ->  Seq Scan on document
+                           Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+         ->  Hash
+               ->  Seq Scan on uaccount
+(15 rows)
+
 -- viewpoint from regress_rls_dave
 SET SESSION AUTHORIZATION regress_rls_dave;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
@@ -328,6 +403,27 @@ NOTICE:  f_leak => awesome technology book
   33 |  10 |      2 | regress_rls_dave  | awesome technology book | technology
 (7 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => awesome technology book
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   2 |      2 | my second novel         |  11 | novel
+ regress_rls_bob   |   3 |      2 | my science fiction      |  22 | science fiction
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book   |  33 | technology
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+ regress_rls_dave  |  10 |      2 | awesome technology book |  33 | technology
+(7 rows)
+
 EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
                                                  QUERY PLAN                                                  
 -------------------------------------------------------------------------------------------------------------
@@ -352,6 +448,28 @@ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dt
                Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
 (9 rows)
 
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+                                                                QUERY PLAN                                                                 
+-------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+   Sort Key: document.did
+   InitPlan 1
+     ->  Index Scan using uaccount_pkey on uaccount uaccount_1
+           Index Cond: (pguser = CURRENT_USER)
+   ->  Hash Join
+         Hash Cond: (category.cid = document.cid)
+         ->  Seq Scan on category
+         ->  Hash
+               ->  Hash Join
+                     Hash Cond: (uaccount.pguser = document.dauthor)
+                     ->  Seq Scan on uaccount
+                     ->  Hash
+                           ->  Seq Scan on document
+                                 Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+(15 rows)
+
 -- 44 would technically fail for both p2r and p1r, but we should get an error
 -- back from p1r for this because it sorts first
 INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
@@ -398,6 +516,23 @@ NOTICE:  f_leak => my second manga
   44 |   5 |      2 | regress_rls_bob | my second manga    | manga
 (5 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+     pguser      | did | dlevel |       dtitle       | cid |      cname      
+-----------------+-----+--------+--------------------+-----+-----------------
+ regress_rls_bob |   1 |      1 | my first novel     |  11 | novel
+ regress_rls_bob |   2 |      2 | my second novel    |  11 | novel
+ regress_rls_bob |   3 |      2 | my science fiction |  22 | science fiction
+ regress_rls_bob |   4 |      1 | my first manga     |  44 | manga
+ regress_rls_bob |   5 |      2 | my second manga    |  44 | manga
+(5 rows)
+
 -- viewpoint from rls_regres_carol again
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
@@ -422,6 +557,19 @@ NOTICE:  f_leak => great manga
   44 |   8 |      1 | regress_rls_carol | great manga           | manga
 (3 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+      pguser       | did | dlevel |        dtitle         | cid |      cname      
+-------------------+-----+--------+-----------------------+-----+-----------------
+ regress_rls_carol |   6 |      1 | great science fiction |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book |  33 | technology
+ regress_rls_carol |   8 |      1 | great manga           |  44 | manga
+(3 rows)
+
 EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
                        QUERY PLAN                        
 ---------------------------------------------------------
@@ -439,6 +587,23 @@ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dt
          Index Cond: (cid = document.cid)
 (5 rows)
 
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Sort
+   Sort Key: document.did
+   ->  Nested Loop
+         ->  Nested Loop
+               ->  Index Only Scan using uaccount_pkey on uaccount
+                     Index Cond: (pguser = CURRENT_USER)
+               ->  Seq Scan on document
+                     Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+         ->  Index Scan using category_pkey on category
+               Index Cond: (cid = document.cid)
+(10 rows)
+
 -- interaction of FK/PK constraints
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE POLICY p2 ON category
@@ -515,6 +680,35 @@ SELECT * FROM category;
   44 | manga
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => awesome technology book
+NOTICE:  f_leak => hoge
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   2 |      2 | my second novel         |  11 | novel
+ regress_rls_bob   |   3 |      2 | my science fiction      |  22 | science fiction
+ regress_rls_bob   |   4 |      1 | my first manga          |  44 | manga
+ regress_rls_bob   |   5 |      2 | my second manga         |  44 | manga
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book   |  33 | technology
+ regress_rls_carol |   8 |      1 | great manga             |  44 | manga
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+ regress_rls_dave  |  10 |      2 | awesome technology book |  33 | technology
+ regress_rls_carol |  11 |      1 | hoge                    |  33 | technology
+(11 rows)
+
 -- database superuser does bypass RLS policy when disabled
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;
@@ -543,6 +737,35 @@ SELECT * FROM category;
   44 | manga
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => awesome technology book
+NOTICE:  f_leak => hoge
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   2 |      2 | my second novel         |  11 | novel
+ regress_rls_bob   |   3 |      2 | my science fiction      |  22 | science fiction
+ regress_rls_bob   |   4 |      1 | my first manga          |  44 | manga
+ regress_rls_bob   |   5 |      2 | my second manga         |  44 | manga
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book   |  33 | technology
+ regress_rls_carol |   8 |      1 | great manga             |  44 | manga
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+ regress_rls_dave  |  10 |      2 | awesome technology book |  33 | technology
+ regress_rls_carol |  11 |      1 | hoge                    |  33 | technology
+(11 rows)
+
 -- database non-superuser with bypass privilege can bypass RLS policy when disabled
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
@@ -571,6 +794,35 @@ SELECT * FROM category;
   44 | manga
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => awesome technology book
+NOTICE:  f_leak => hoge
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   2 |      2 | my second novel         |  11 | novel
+ regress_rls_bob   |   3 |      2 | my science fiction      |  22 | science fiction
+ regress_rls_bob   |   4 |      1 | my first manga          |  44 | manga
+ regress_rls_bob   |   5 |      2 | my second manga         |  44 | manga
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book   |  33 | technology
+ regress_rls_carol |   8 |      1 | great manga             |  44 | manga
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+ regress_rls_dave  |  10 |      2 | awesome technology book |  33 | technology
+ regress_rls_carol |  11 |      1 | hoge                    |  33 | technology
+(11 rows)
+
 -- RLS policy does not apply to table owner when RLS enabled.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security TO ON;
@@ -599,6 +851,35 @@ SELECT * FROM category;
   44 | manga
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => awesome technology book
+NOTICE:  f_leak => hoge
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   2 |      2 | my second novel         |  11 | novel
+ regress_rls_bob   |   3 |      2 | my science fiction      |  22 | science fiction
+ regress_rls_bob   |   4 |      1 | my first manga          |  44 | manga
+ regress_rls_bob   |   5 |      2 | my second manga         |  44 | manga
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book   |  33 | technology
+ regress_rls_carol |   8 |      1 | great manga             |  44 | manga
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+ regress_rls_dave  |  10 |      2 | awesome technology book |  33 | technology
+ regress_rls_carol |  11 |      1 | hoge                    |  33 | technology
+(11 rows)
+
 -- RLS policy does not apply to table owner when RLS disabled.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security TO OFF;
@@ -627,6 +908,35 @@ SELECT * FROM category;
   44 | manga
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => awesome technology book
+NOTICE:  f_leak => hoge
+      pguser       | did | dlevel |         dtitle          | cid |      cname      
+-------------------+-----+--------+-------------------------+-----+-----------------
+ regress_rls_bob   |   1 |      1 | my first novel          |  11 | novel
+ regress_rls_bob   |   2 |      2 | my second novel         |  11 | novel
+ regress_rls_bob   |   3 |      2 | my science fiction      |  22 | science fiction
+ regress_rls_bob   |   4 |      1 | my first manga          |  44 | manga
+ regress_rls_bob   |   5 |      2 | my second manga         |  44 | manga
+ regress_rls_carol |   6 |      1 | great science fiction   |  22 | science fiction
+ regress_rls_carol |   7 |      2 | great technology book   |  33 | technology
+ regress_rls_carol |   8 |      1 | great manga             |  44 | manga
+ regress_rls_dave  |   9 |      1 | awesome science fiction |  22 | science fiction
+ regress_rls_dave  |  10 |      2 | awesome technology book |  33 | technology
+ regress_rls_carol |  11 |      1 | hoge                    |  33 | technology
+(11 rows)
+
 --
 -- Table inheritance and RLS policy
 --
@@ -643,6 +953,12 @@ CREATE TABLE t3 (id int not null primary key, c text, b text, a int);
 ALTER TABLE t3 INHERIT t1;
 GRANT ALL ON t3 TO public;
 COPY t3(id, a,b,c) FROM stdin;
+CREATE PROPERTY GRAPH itpg
+    VERTEX TABLES (
+        t1 KEY(id) DEFAULT LABEL PROPERTIES (id, a, b, tableoid::regclass as tableoid),
+        t2 KEY(id) DEFAULT LABEL PROPERTIES (id, a, b, tableoid::regclass as tableoid),
+        t3 KEY(id) DEFAULT LABEL PROPERTIES (id, a, b, tableoid::regclass as tableoid));
+GRANT ALL ON itpg TO public;
 CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number
 CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number
 ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
@@ -670,6 +986,28 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1;
          Filter: ((a % 2) = 0)
 (7 rows)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.id, t.a, t.b));
+ id  | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | dad
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.id, t.a, t.b));
+          QUERY PLAN           
+-------------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t2 t1_2
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t3 t1_3
+         Filter: ((a % 2) = 0)
+(7 rows)
+
 SELECT * FROM t1 WHERE f_leak(b);
 NOTICE:  f_leak => bbb
 NOTICE:  f_leak => dad
@@ -697,6 +1035,33 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
          Filter: (((a % 2) = 0) AND f_leak(b))
 (7 rows)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => def
+NOTICE:  f_leak => yyy
+ id  | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | dad
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+                  QUERY PLAN                   
+-----------------------------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: (((a % 2) = 0) AND f_leak(b))
+   ->  Seq Scan on t2 t1_2
+         Filter: (((a % 2) = 0) AND f_leak(b))
+   ->  Seq Scan on t3 t1_3
+         Filter: (((a % 2) = 0) AND f_leak(b))
+(7 rows)
+
 -- reference to system column
 SELECT tableoid::regclass, * FROM t1;
  tableoid | id  | a |  b  
@@ -815,6 +1180,26 @@ EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a,
    ->  Seq Scan on t3
 (4 rows)
 
+-- label disjunction
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t2 | t3) COLUMNS (t.a, t.b, t.tableoid));
+ a |  b  | tableoid 
+---+-----+----------
+ 1 | abc | t2
+ 3 | cde | t2
+ 1 | xxx | t3
+ 2 | yyy | t3
+ 3 | zzz | t3
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t2 | t3) COLUMNS (t.a, t.b, t.tableoid));
+          QUERY PLAN           
+-------------------------------
+ Append
+   ->  Seq Scan on t2
+         Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(4 rows)
+
 -- superuser is allowed to bypass RLS checks
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;
@@ -857,6 +1242,45 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
          Filter: f_leak(b)
 (7 rows)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => dad
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => def
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+ id  | a |  b  
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 103 | 3 | ccc
+ 104 | 4 | dad
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 203 | 3 | cde
+ 204 | 4 | def
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: f_leak(b)
+   ->  Seq Scan on t2 t1_2
+         Filter: f_leak(b)
+   ->  Seq Scan on t3 t1_3
+         Filter: f_leak(b)
+(7 rows)
+
 -- non-superuser with bypass privilege can bypass RLS policy when disabled
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
@@ -899,6 +1323,45 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
          Filter: f_leak(b)
 (7 rows)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => dad
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => def
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+ id  | a |  b  
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 103 | 3 | ccc
+ 104 | 4 | dad
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 203 | 3 | cde
+ 204 | 4 | def
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: f_leak(b)
+   ->  Seq Scan on t2 t1_2
+         Filter: f_leak(b)
+   ->  Seq Scan on t3 t1_3
+         Filter: f_leak(b)
+(7 rows)
+
 --
 -- Partitioned Tables
 --
@@ -929,6 +1392,9 @@ INSERT INTO part_document VALUES
     ( 8, 55, 2, 'regress_rls_carol', 'great satire'),
     ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),
     (10, 99, 2, 'regress_rls_dave', 'awesome technology book');
+CREATE PROPERTY GRAPH ptpg
+    VERTEX TABLES (part_document KEY (did), part_document_satire KEY (did));
+GRANT ALL ON ptpg TO public;
 ALTER TABLE part_document ENABLE ROW LEVEL SECURITY;
 -- Create policy on parent
 -- user's security level must be higher than or equal to document's
@@ -1001,19 +1467,49 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
          Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
 (10 rows)
 
--- viewpoint from regress_rls_carol
-SET SESSION AUTHORIZATION regress_rls_carol;
-SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
 NOTICE:  f_leak => my first novel
-NOTICE:  f_leak => my second novel
 NOTICE:  f_leak => great science fiction
 NOTICE:  f_leak => awesome science fiction
 NOTICE:  f_leak => my first satire
-NOTICE:  f_leak => great satire
-NOTICE:  f_leak => my science textbook
-NOTICE:  f_leak => my history book
-NOTICE:  f_leak => great technology book
-NOTICE:  f_leak => awesome technology book
+ did | cid | dlevel |      dauthor      |         dtitle          
+-----+-----+--------+-------------------+-------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Sort
+   Sort Key: part_document.did
+   InitPlan 1
+     ->  Index Scan using uaccount_pkey on uaccount
+           Index Cond: (pguser = CURRENT_USER)
+   ->  Append
+         ->  Seq Scan on part_document_fiction part_document_1
+               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+         ->  Seq Scan on part_document_satire part_document_2
+               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+         ->  Seq Scan on part_document_nonfiction part_document_3
+               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+(12 rows)
+
+-- viewpoint from regress_rls_carol
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => my first satire
+NOTICE:  f_leak => great satire
+NOTICE:  f_leak => my science textbook
+NOTICE:  f_leak => my history book
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => awesome technology book
  did | cid | dlevel |      dauthor      |         dtitle          
 -----+-----+--------+-------------------+-------------------------
    1 |  11 |      1 | regress_rls_bob   | my first novel
@@ -1043,6 +1539,48 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
          Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
 (10 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => my first satire
+NOTICE:  f_leak => great satire
+NOTICE:  f_leak => my science textbook
+NOTICE:  f_leak => my history book
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => awesome technology book
+ did | cid | dlevel |      dauthor      |         dtitle          
+-----+-----+--------+-------------------+-------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   2 |  11 |      2 | regress_rls_bob   | my second novel
+   3 |  99 |      2 | regress_rls_bob   | my science textbook
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   5 |  99 |      2 | regress_rls_bob   | my history book
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   7 |  99 |      2 | regress_rls_carol | great technology book
+   8 |  55 |      2 | regress_rls_carol | great satire
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
+  10 |  99 |      2 | regress_rls_dave  | awesome technology book
+(10 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Sort
+   Sort Key: part_document.did
+   InitPlan 1
+     ->  Index Scan using uaccount_pkey on uaccount
+           Index Cond: (pguser = CURRENT_USER)
+   ->  Append
+         ->  Seq Scan on part_document_fiction part_document_1
+               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+         ->  Seq Scan on part_document_satire part_document_2
+               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+         ->  Seq Scan on part_document_nonfiction part_document_3
+               Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+(12 rows)
+
 -- viewpoint from regress_rls_dave
 SET SESSION AUTHORIZATION regress_rls_dave;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
@@ -1068,6 +1606,29 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
            Index Cond: (pguser = CURRENT_USER)
 (5 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => awesome science fiction
+ did | cid | dlevel |      dauthor      |         dtitle          
+-----+-----+--------+-------------------+-------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   2 |  11 |      2 | regress_rls_bob   | my second novel
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Seq Scan on part_document_fiction part_document
+   Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+   InitPlan 1
+     ->  Index Scan using uaccount_pkey on uaccount
+           Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
 -- pp1 ERROR
 INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
 ERROR:  new row violates row-level security policy for table "part_document"
@@ -1106,6 +1667,17 @@ NOTICE:  f_leak => testing RLS with partitions
  100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
 (3 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+NOTICE:  f_leak => my first satire
+NOTICE:  f_leak => great satire
+NOTICE:  f_leak => testing RLS with partitions
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+(3 rows)
+
 -- Turn on RLS and create policy on child to show RLS is checked before constraints
 SET SESSION AUTHORIZATION regress_rls_alice;
 ALTER TABLE part_document_satire ENABLE ROW LEVEL SECURITY;
@@ -1121,6 +1693,11 @@ SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did;
 -----+-----+--------+---------+--------
 (0 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ did | cid | dlevel | dauthor | dtitle 
+-----+-----+--------+---------+--------
+(0 rows)
+
 -- The parent looks same as before
 -- viewpoint from regress_rls_dave
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
@@ -1146,6 +1723,29 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
            Index Cond: (pguser = CURRENT_USER)
 (5 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => awesome science fiction
+ did | cid | dlevel |      dauthor      |         dtitle          
+-----+-----+--------+-------------------+-------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   2 |  11 |      2 | regress_rls_bob   | my second novel
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Seq Scan on part_document_fiction part_document
+   Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+   InitPlan 1
+     ->  Index Scan using uaccount_pkey on uaccount
+           Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
 -- viewpoint from regress_rls_carol
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
@@ -1190,6 +1790,48 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
          Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
 (10 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => awesome science fiction
+NOTICE:  f_leak => my first satire
+NOTICE:  f_leak => great satire
+NOTICE:  f_leak => testing RLS with partitions
+NOTICE:  f_leak => my science textbook
+NOTICE:  f_leak => my history book
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => awesome technology book
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   2 |  11 |      2 | regress_rls_bob   | my second novel
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+   3 |  99 |      2 | regress_rls_bob   | my science textbook
+   5 |  99 |      2 | regress_rls_bob   | my history book
+   7 |  99 |      2 | regress_rls_carol | great technology book
+  10 |  99 |      2 | regress_rls_dave  | awesome technology book
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Append
+   InitPlan 1
+     ->  Index Scan using uaccount_pkey on uaccount
+           Index Cond: (pguser = CURRENT_USER)
+   ->  Seq Scan on part_document_fiction part_document_1
+         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+   ->  Seq Scan on part_document_satire part_document_2
+         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+   ->  Seq Scan on part_document_nonfiction part_document_3
+         Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle))
+(10 rows)
+
 -- only owner can change policies
 ALTER POLICY pp1 ON part_document USING (true);    --fail
 ERROR:  must be owner of table part_document
@@ -1214,6 +1856,21 @@ NOTICE:  f_leak => my history book
    5 |  99 |      2 | regress_rls_bob | my history book
 (5 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my first satire
+NOTICE:  f_leak => my science textbook
+NOTICE:  f_leak => my history book
+ did | cid | dlevel |     dauthor     |       dtitle        
+-----+-----+--------+-----------------+---------------------
+   1 |  11 |      1 | regress_rls_bob | my first novel
+   2 |  11 |      2 | regress_rls_bob | my second novel
+   4 |  55 |      1 | regress_rls_bob | my first satire
+   3 |  99 |      2 | regress_rls_bob | my science textbook
+   5 |  99 |      2 | regress_rls_bob | my history book
+(5 rows)
+
 -- viewpoint from rls_regres_carol again
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
@@ -1239,6 +1896,29 @@ EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
          Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
 (7 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great satire
+NOTICE:  f_leak => great technology book
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   8 |  55 |      2 | regress_rls_carol | great satire
+   7 |  99 |      2 | regress_rls_carol | great technology book
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Append
+   ->  Seq Scan on part_document_fiction part_document_1
+         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+   ->  Seq Scan on part_document_satire part_document_2
+         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+   ->  Seq Scan on part_document_nonfiction part_document_3
+         Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+(7 rows)
+
 -- database superuser does bypass RLS policy when enabled
 RESET SESSION AUTHORIZATION;
 SET row_security TO ON;
@@ -1258,6 +1938,22 @@ SELECT * FROM part_document ORDER BY did;
  100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
 (11 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   2 |  11 |      2 | regress_rls_bob   | my second novel
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+   3 |  99 |      2 | regress_rls_bob   | my science textbook
+   5 |  99 |      2 | regress_rls_bob   | my history book
+   7 |  99 |      2 | regress_rls_carol | great technology book
+  10 |  99 |      2 | regress_rls_dave  | awesome technology book
+(11 rows)
+
 SELECT * FROM part_document_satire ORDER by did;
  did | cid | dlevel |      dauthor      |           dtitle            
 -----+-----+--------+-------------------+-----------------------------
@@ -1266,6 +1962,14 @@ SELECT * FROM part_document_satire ORDER by did;
  100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
 (3 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+(3 rows)
+
 -- database non-superuser with bypass privilege can bypass RLS policy when disabled
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
@@ -1285,6 +1989,22 @@ SELECT * FROM part_document ORDER BY did;
  100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
 (11 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   2 |  11 |      2 | regress_rls_bob   | my second novel
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+   3 |  99 |      2 | regress_rls_bob   | my science textbook
+   5 |  99 |      2 | regress_rls_bob   | my history book
+   7 |  99 |      2 | regress_rls_carol | great technology book
+  10 |  99 |      2 | regress_rls_dave  | awesome technology book
+(11 rows)
+
 SELECT * FROM part_document_satire ORDER by did;
  did | cid | dlevel |      dauthor      |           dtitle            
 -----+-----+--------+-------------------+-----------------------------
@@ -1293,6 +2013,14 @@ SELECT * FROM part_document_satire ORDER by did;
  100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
 (3 rows)
 
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+(3 rows)
+
 -- RLS policy does not apply to table owner when RLS enabled.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security TO ON;
@@ -1312,21 +2040,49 @@ SELECT * FROM part_document ORDER by did;
  100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
 (11 rows)
 
-SELECT * FROM part_document_satire ORDER by did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
  did | cid | dlevel |      dauthor      |           dtitle            
 -----+-----+--------+-------------------+-----------------------------
+   1 |  11 |      1 | regress_rls_bob   | my first novel
+   2 |  11 |      2 | regress_rls_bob   | my second novel
+   6 |  11 |      1 | regress_rls_carol | great science fiction
+   9 |  11 |      1 | regress_rls_dave  | awesome science fiction
    4 |  55 |      1 | regress_rls_bob   | my first satire
    8 |  55 |      2 | regress_rls_carol | great satire
  100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
-(3 rows)
+   3 |  99 |      2 | regress_rls_bob   | my science textbook
+   5 |  99 |      2 | regress_rls_bob   | my history book
+   7 |  99 |      2 | regress_rls_carol | great technology book
+  10 |  99 |      2 | regress_rls_dave  | awesome technology book
+(11 rows)
 
--- When RLS disabled, other users get ERROR.
-SET SESSION AUTHORIZATION regress_rls_dave;
-SET row_security TO OFF;
+SELECT * FROM part_document_satire ORDER by did;
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+(3 rows)
+
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ did | cid | dlevel |      dauthor      |           dtitle            
+-----+-----+--------+-------------------+-----------------------------
+   4 |  55 |      1 | regress_rls_bob   | my first satire
+   8 |  55 |      2 | regress_rls_carol | great satire
+ 100 |  55 |      1 | regress_rls_dave  | testing RLS with partitions
+(3 rows)
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_rls_dave;
+SET row_security TO OFF;
 SELECT * FROM part_document ORDER by did;
 ERROR:  query would be affected by row-level security policy for table "part_document"
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ERROR:  query would be affected by row-level security policy for table "part_document"
 SELECT * FROM part_document_satire ORDER by did;
 ERROR:  query would be affected by row-level security policy for table "part_document_satire"
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+ERROR:  query would be affected by row-level security policy for table "part_document_satire"
 -- Check behavior with a policy that uses a SubPlan not an InitPlan.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security TO ON;
@@ -1362,10 +2118,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualifie
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE rec1 (x integer, y integer);
 CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));
+CREATE PROPERTY GRAPH rtpg VERTEX TABLES (rec1 KEY (x));
 ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1; -- fail, direct recursion
 ERROR:  infinite recursion detected in policy for relation "rec1"
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, direct recursion
+ERROR:  infinite recursion detected in policy for relation "rec1"
 --
 -- Mutual recursion
 --
@@ -1377,6 +2136,8 @@ ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1;    -- fail, mutual recursion
 ERROR:  infinite recursion detected in policy for relation "rec1"
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion
+ERROR:  infinite recursion detected in policy for relation "rec1"
 --
 -- Mutual recursion via views
 --
@@ -1389,6 +2150,8 @@ ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1;    -- fail, mutual recursion via views
 ERROR:  infinite recursion detected in policy for relation "rec1"
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion via views
+ERROR:  infinite recursion detected in policy for relation "rec1"
 --
 -- Mutual recursion via .s.b views
 --
@@ -1405,6 +2168,27 @@ CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1;    -- fail, mutual recursion via s.b. views
 ERROR:  infinite recursion detected in policy for relation "rec1"
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion via s.b. views
+ERROR:  infinite recursion detected in policy for relation "rec1"
+--
+-- Direct and mutual recursion via GRAPH_TABLE
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER POLICY r1 ON rec1 USING (x = (SELECT x FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)) WHERE x = y));
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1;    -- fail, direct recursion via GRAPH_TABLE
+ERROR:  infinite recursion detected in policy for relation "rec1"
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, direct recursion via GRAPH_TABLE
+ERROR:  infinite recursion detected in policy for relation "rec1"
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER PROPERTY GRAPH rtpg ADD VERTEX TABLES (rec2 KEY (a));
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM GRAPH_TABLE (rtpg MATCH (r: rec2) COLUMNS (r.a, r.b)) WHERE b = y));
+ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y))WHERE y = b));
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1;    -- fail, mutual recursion via GRAPH_TABLE
+ERROR:  infinite recursion detected in policy for relation "rec1"
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion via GRAPH_TABLE
+ERROR:  infinite recursion detected in policy for relation "rec1"
 --
 -- recursive RLS and VIEWs in policy
 --
@@ -1413,7 +2197,9 @@ CREATE TABLE s1 (a int, b text);
 INSERT INTO s1 (SELECT x, public.fipshash(x::text) FROM generate_series(-10,10) x);
 CREATE TABLE s2 (x int, y text);
 INSERT INTO s2 (SELECT x, public.fipshash(x::text) FROM generate_series(-6,6) x);
+CREATE PROPERTY GRAPH rvtpg VERTEX TABLES (s1 KEY (a));
 GRANT SELECT ON s1, s2 TO regress_rls_bob;
+GRANT SELECT ON rvtpg TO regress_rls_bob;
 CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));
 CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));
 CREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));
@@ -1423,6 +2209,8 @@ SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
 SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
 ERROR:  infinite recursion detected in policy for relation "s1"
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b)); -- fail (infinite recursion)
+ERROR:  infinite recursion detected in policy for relation "s1"
 INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)
 ERROR:  infinite recursion detected in policy for relation "s1"
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -1446,6 +2234,23 @@ EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
            Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))
 (5 rows)
 
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
+NOTICE:  f_leak => 03b26944890929ff751653acb2f2af79
+ a  |                b                 
+----+----------------------------------
+ -6 | 03b26944890929ff751653acb2f2af79
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Seq Scan on s1
+   Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))
+   SubPlan 1
+     ->  Seq Scan on s2
+           Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))
+(5 rows)
+
 SET SESSION AUTHORIZATION regress_rls_alice;
 ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy
 SET SESSION AUTHORIZATION regress_rls_bob;
@@ -1466,6 +2271,23 @@ EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
            Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))
 (5 rows)
 
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
+NOTICE:  f_leak => 03b26944890929ff751653acb2f2af79
+ a  |                b                 
+----+----------------------------------
+ -6 | 03b26944890929ff751653acb2f2af79
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Seq Scan on s1
+   Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b))
+   SubPlan 1
+     ->  Seq Scan on s2
+           Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))
+(5 rows)
+
 SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
  xx | x  |                y                 
 ----+----+----------------------------------
@@ -1491,6 +2313,8 @@ ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion via view)
 ERROR:  infinite recursion detected in policy for relation "s1"
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));	-- fail (infinite recursion via view)
+ERROR:  infinite recursion detected in policy for relation "s1"
 -- prepared statement with regress_rls_alice privilege
 PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
 EXECUTE p1(2);
@@ -1513,6 +2337,27 @@ EXPLAIN (COSTS OFF) EXECUTE p1(2);
          Filter: ((a <= 2) AND ((a % 2) = 0))
 (7 rows)
 
+PREPARE ppg1(int) AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t1) WHERE t.a <= $1 COLUMNS (t.id, t.a, t.b));
+EXECUTE ppg1(2);
+ id  | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE ppg1(2);
+                  QUERY PLAN                  
+----------------------------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t2 t1_2
+         Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t3 t1_3
+         Filter: ((a <= 2) AND ((a % 2) = 0))
+(7 rows)
+
 -- superuser is allowed to bypass RLS checks
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;
@@ -1579,6 +2424,29 @@ EXPLAIN (COSTS OFF) EXECUTE p1(2);
          Filter: (a <= 2)
 (7 rows)
 
+EXECUTE ppg1(2);
+ id  | a |  b  
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+(6 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE ppg1(2);
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: (a <= 2)
+   ->  Seq Scan on t2 t1_2
+         Filter: (a <= 2)
+   ->  Seq Scan on t3 t1_3
+         Filter: (a <= 2)
+(7 rows)
+
 PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
 EXECUTE p2(2);
  id  | a |  b  
@@ -1600,6 +2468,27 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2);
          Filter: (a = 2)
 (7 rows)
 
+PREPARE ppg2(int) AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t1) WHERE t.a = $1 COLUMNS (t.id, t.a, t.b));
+EXECUTE ppg2(2);
+ id  | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE ppg2(2);
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: (a = 2)
+   ->  Seq Scan on t2 t1_2
+         Filter: (a = 2)
+   ->  Seq Scan on t3 t1_3
+         Filter: (a = 2)
+(7 rows)
+
 -- also, case when privilege switch from superuser
 SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO ON;
@@ -1623,6 +2512,26 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2);
          Filter: ((a = 2) AND ((a % 2) = 0))
 (7 rows)
 
+EXECUTE ppg2(2);
+ id  | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE ppg2(2);
+                 QUERY PLAN                  
+---------------------------------------------
+ Append
+   ->  Seq Scan on t1 t1_1
+         Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t2 t1_2
+         Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t3 t1_3
+         Filter: ((a = 2) AND ((a % 2) = 0))
+(7 rows)
+
 --
 -- UPDATE / DELETE and Row-level security
 --
@@ -1903,6 +2812,8 @@ GRANT ALL ON b1 TO regress_rls_bob;
 SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;
 GRANT ALL ON bv1 TO regress_rls_carol;
+CREATE PROPERTY GRAPH sbvpg VERTEX TABLES (bv1 KEY (a));
+GRANT ALL ON sbvpg TO regress_rls_carol;
 SET SESSION AUTHORIZATION regress_rls_carol;
 EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
                  QUERY PLAN                  
@@ -1928,6 +2839,30 @@ NOTICE:  f_leak => 4a44dc15364204a80fe80e9039455cc1
  10 | 4a44dc15364204a80fe80e9039455cc1
 (5 rows)
 
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (sbvpg MATCH (b : bv1) WHERE f_leak(b.b) COLUMNS (b.a, b.b));
+                 QUERY PLAN                  
+---------------------------------------------
+ Subquery Scan on bv1
+   Filter: f_leak(bv1.b)
+   ->  Seq Scan on b1
+         Filter: ((a > 0) AND ((a % 2) = 0))
+(4 rows)
+
+SELECT * FROM GRAPH_TABLE (sbvpg MATCH (b : bv1) WHERE f_leak(b.b) COLUMNS (b.a, b.b));
+NOTICE:  f_leak => d4735e3a265e16eee03f59718b9b5d03
+NOTICE:  f_leak => 4b227777d4dd1fc61c6f884f48641d02
+NOTICE:  f_leak => e7f6c011776e8db7cd330b54174fd76f
+NOTICE:  f_leak => 2c624232cdd221771294dfbb310aca00
+NOTICE:  f_leak => 4a44dc15364204a80fe80e9039455cc1
+ a  |                b                 
+----+----------------------------------
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+(5 rows)
+
 INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
 ERROR:  new row violates row-level security policy for table "b1"
 INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
@@ -2363,7 +3298,8 @@ SELECT * FROM document;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE z1 (a int, b text);
 CREATE TABLE z2 (a int, b text);
-GRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,
+CREATE PROPERTY GRAPH gtpg VERTEX TABLES (z1 KEY (a), z2 KEY (a));
+GRANT SELECT ON z1,z2, gtpg TO regress_rls_group1, regress_rls_group2,
     regress_rls_bob, regress_rls_carol;
 INSERT INTO z1 VALUES
     (1, 'aba'),
@@ -2390,6 +3326,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
 PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) EXECUTE plancache_test;
                QUERY PLAN                
@@ -2398,6 +3350,14 @@ EXPLAIN (COSTS OFF) EXECUTE plancache_test;
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
+PREPARE plancache_pg_test AS SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
 PREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
                    QUERY PLAN                    
@@ -2411,6 +3371,19 @@ EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
          ->  Seq Scan on z2
 (7 rows)
 
+PREPARE plancache_pg_test2 AS WITH q AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS(z.a, z.b))) SELECT * FROM q, GRAPH_TABLE (gtpg MATCH (z : z2) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z1
+           Filter: (((a % 2) = 0) AND f_leak(b))
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z2
+(7 rows)
+
 PREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);
 EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
                      QUERY PLAN                      
@@ -2424,6 +3397,19 @@ EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
                Filter: (((a % 2) = 0) AND f_leak(b))
 (7 rows)
 
+PREPARE plancache_pg_test3 AS WITH q AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z2) COLUMNS(z.a, z.b))) SELECT * FROM q, GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z2
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z1
+               Filter: (((a % 2) = 0) AND f_leak(b))
+(7 rows)
+
 SET ROLE regress_rls_group1;
 SELECT * FROM z1 WHERE f_leak(b);
 NOTICE:  f_leak => bbb
@@ -2441,6 +3427,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
 EXPLAIN (COSTS OFF) EXECUTE plancache_test;
                QUERY PLAN                
 -----------------------------------------
@@ -2472,43 +3474,26 @@ EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
                Filter: (((a % 2) = 0) AND f_leak(b))
 (7 rows)
 
-SET SESSION AUTHORIZATION regress_rls_carol;
-SELECT * FROM z1 WHERE f_leak(b);
-NOTICE:  f_leak => aba
-NOTICE:  f_leak => ccc
- a |  b  
----+-----
- 1 | aba
- 3 | ccc
-(2 rows)
-
-EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
-               QUERY PLAN                
------------------------------------------
- Seq Scan on z1
-   Filter: (((a % 2) = 1) AND f_leak(b))
-(2 rows)
-
-EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
                QUERY PLAN                
 -----------------------------------------
  Seq Scan on z1
-   Filter: (((a % 2) = 1) AND f_leak(b))
+   Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
-EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
                    QUERY PLAN                    
 -------------------------------------------------
  Nested Loop
    CTE q
      ->  Seq Scan on z1
-           Filter: (((a % 2) = 1) AND f_leak(b))
+           Filter: (((a % 2) = 0) AND f_leak(b))
    ->  CTE Scan on q
    ->  Materialize
          ->  Seq Scan on z2
 (7 rows)
 
-EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
                      QUERY PLAN                      
 -----------------------------------------------------
  Nested Loop
@@ -2517,10 +3502,10 @@ EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
    ->  CTE Scan on q
    ->  Materialize
          ->  Seq Scan on z1
-               Filter: (((a % 2) = 1) AND f_leak(b))
+               Filter: (((a % 2) = 0) AND f_leak(b))
 (7 rows)
 
-SET ROLE regress_rls_group2;
+SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM z1 WHERE f_leak(b);
 NOTICE:  f_leak => aba
 NOTICE:  f_leak => ccc
@@ -2537,7 +3522,118 @@ EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
    Filter: (((a % 2) = 1) AND f_leak(b))
 (2 rows)
 
-EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => ccc
+ a |  b  
+---+-----
+ 1 | aba
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z1
+           Filter: (((a % 2) = 1) AND f_leak(b))
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z2
+(7 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z2
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z1
+               Filter: (((a % 2) = 1) AND f_leak(b))
+(7 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z1
+           Filter: (((a % 2) = 1) AND f_leak(b))
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z2
+(7 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z2
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z1
+               Filter: (((a % 2) = 1) AND f_leak(b))
+(7 rows)
+
+SET ROLE regress_rls_group2;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => ccc
+ a |  b  
+---+-----
+ 1 | aba
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => ccc
+ a |  b  
+---+-----
+ 1 | aba
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
                QUERY PLAN                
 -----------------------------------------
  Seq Scan on z1
@@ -2568,13 +3664,46 @@ EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
                Filter: (((a % 2) = 1) AND f_leak(b))
 (7 rows)
 
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z1
+           Filter: (((a % 2) = 1) AND f_leak(b))
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z2
+(7 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop
+   CTE q
+     ->  Seq Scan on z2
+   ->  CTE Scan on q
+   ->  Materialize
+         ->  Seq Scan on z1
+               Filter: (((a % 2) = 1) AND f_leak(b))
+(7 rows)
+
 --
 -- Views should follow policy for view owner.
 --
 -- View and Table owner are the same.
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
 GRANT SELECT ON rls_view TO regress_rls_bob;
+GRANT SELECT ON vtpg TO regress_rls_bob;
 -- Query as role that is not owner of view or table.  Should return all records.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view;
@@ -2597,6 +3726,26 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: f_leak(b)
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 1 | aba
+ 2 | bbb
+ 3 | ccc
+ 4 | dad
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+     QUERY PLAN      
+---------------------
+ Seq Scan on z1
+   Filter: f_leak(b)
+(2 rows)
+
 -- Query as view/table owner.  Should return all records.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM rls_view;
@@ -2619,11 +3768,34 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: f_leak(b)
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 1 | aba
+ 2 | bbb
+ 3 | ccc
+ 4 | dad
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+     QUERY PLAN      
+---------------------
+ Seq Scan on z1
+   Filter: f_leak(b)
+(2 rows)
+
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 -- View and Table owners are different.
 SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
 GRANT SELECT ON rls_view TO regress_rls_alice;
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
+GRANT SELECT ON vtpg TO regress_rls_alice;
 -- Query as role that is not owner of view but is owner of table.
 -- Should return records based on view owner policies.
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -2643,6 +3815,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
 -- Query as role that is not owner of table but is owner of view.
 -- Should return records based on view owner policies.
 SET SESSION AUTHORIZATION regress_rls_bob;
@@ -2662,6 +3850,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view; --fail - permission denied.
@@ -2671,6 +3875,7 @@ ERROR:  permission denied for view rls_view
 -- Query as role that is not the owner of the table or view with permissions.
 SET SESSION AUTHORIZATION regress_rls_bob;
 GRANT SELECT ON rls_view TO regress_rls_carol;
+GRANT SELECT ON vtpg TO regress_rls_carol;
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
 NOTICE:  f_leak => bbb
@@ -2688,6 +3893,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
 -- Policy requiring access to another table.
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE z1_blacklist (a int);
@@ -2699,12 +3920,20 @@ SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
 -- Query as role that is not owner of table but is owner of view with permissions.
 SET SESSION AUTHORIZATION regress_rls_alice;
 GRANT SELECT ON z1_blacklist TO regress_rls_bob;
@@ -2725,6 +3954,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
      ->  Seq Scan on z1_blacklist
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+ a |  b  
+---+-----
+ 2 | bbb
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Seq Scan on z1
+   Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 0) AND f_leak(b))
+   SubPlan 1
+     ->  Seq Scan on z1_blacklist
+(4 rows)
+
 -- Query as role that is not the owner of the table or view with permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
@@ -2743,10 +3988,27 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
      ->  Seq Scan on z1_blacklist
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+ a |  b  
+---+-----
+ 2 | bbb
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Seq Scan on z1
+   Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 0) AND f_leak(b))
+   SubPlan 1
+     ->  Seq Scan on z1_blacklist
+(4 rows)
+
 SET SESSION AUTHORIZATION regress_rls_alice;
 REVOKE SELECT ON z1_blacklist FROM regress_rls_bob;
 DROP POLICY p3 ON z1;
 SET SESSION AUTHORIZATION regress_rls_bob;
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 --
 -- Security invoker views should follow policy for current user.
@@ -2755,8 +4017,11 @@ DROP VIEW rls_view;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE VIEW rls_view WITH (security_invoker) AS
     SELECT * FROM z1 WHERE f_leak(b);
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
 GRANT SELECT ON rls_view TO regress_rls_bob;
 GRANT SELECT ON rls_view TO regress_rls_carol;
+GRANT SELECT ON vtpg TO regress_rls_bob;
+GRANT SELECT ON vtpg TO regress_rls_carol;
 -- Query as table owner.  Should return all records.
 SELECT * FROM rls_view;
 NOTICE:  f_leak => aba
@@ -2778,6 +4043,26 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: f_leak(b)
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 1 | aba
+ 2 | bbb
+ 3 | ccc
+ 4 | dad
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+     QUERY PLAN      
+---------------------
+ Seq Scan on z1
+   Filter: f_leak(b)
+(2 rows)
+
 -- Queries as other users.
 -- Should return records based on current user's policies.
 SET SESSION AUTHORIZATION regress_rls_bob;
@@ -2797,9 +4082,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
-SET SESSION AUTHORIZATION regress_rls_carol;
-SELECT * FROM rls_view;
-NOTICE:  f_leak => aba
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM rls_view;
+NOTICE:  f_leak => aba
 NOTICE:  f_leak => ccc
  a |  b  
 ---+-----
@@ -2814,14 +4115,34 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: (((a % 2) = 1) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => ccc
+ a |  b  
+---+-----
+ 1 | aba
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
 -- View and table owners are different.
 SET SESSION AUTHORIZATION regress_rls_alice;
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW rls_view WITH (security_invoker) AS
     SELECT * FROM z1 WHERE f_leak(b);
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
 GRANT SELECT ON rls_view TO regress_rls_alice;
 GRANT SELECT ON rls_view TO regress_rls_carol;
+GRANT SELECT ON vtpg TO regress_rls_alice;
+GRANT SELECT ON vtpg TO regress_rls_carol;
 -- Query as table owner.  Should return all records.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM rls_view;
@@ -2844,6 +4165,26 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: f_leak(b)
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 1 | aba
+ 2 | bbb
+ 3 | ccc
+ 4 | dad
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+     QUERY PLAN      
+---------------------
+ Seq Scan on z1
+   Filter: f_leak(b)
+(2 rows)
+
 -- Queries as other users.
 -- Should return records based on current user's policies.
 SET SESSION AUTHORIZATION regress_rls_bob;
@@ -2863,6 +4204,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: (((a % 2) = 0) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => dad
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
 NOTICE:  f_leak => aba
@@ -2880,6 +4237,22 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
    Filter: (((a % 2) = 1) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => aba
+NOTICE:  f_leak => ccc
+ a |  b  
+---+-----
+ 1 | aba
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+               QUERY PLAN                
+-----------------------------------------
+ Seq Scan on z1
+   Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
 -- Policy requiring access to another table.
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist));
@@ -2889,12 +4262,20 @@ SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
 -- Query as role that is not owner of table but is owner of view with permissions.
 SET SESSION AUTHORIZATION regress_rls_alice;
 GRANT SELECT ON z1_blacklist TO regress_rls_bob;
@@ -2915,12 +4296,32 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
      ->  Seq Scan on z1_blacklist
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => bbb
+ a |  b  
+---+-----
+ 2 | bbb
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Seq Scan on z1
+   Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 0) AND f_leak(b))
+   SubPlan 1
+     ->  Seq Scan on z1_blacklist
+(4 rows)
+
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
 ERROR:  permission denied for table z1_blacklist
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+ERROR:  permission denied for table z1_blacklist
 -- Query as role that is not the owner of the table or view with permissions.
 SET SESSION AUTHORIZATION regress_rls_alice;
 GRANT SELECT ON z1_blacklist TO regress_rls_carol;
@@ -2941,14 +4342,33 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
      ->  Seq Scan on z1_blacklist
 (4 rows)
 
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+NOTICE:  f_leak => aba
+ a |  b  
+---+-----
+ 1 | aba
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Seq Scan on z1
+   Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 1) AND f_leak(b))
+   SubPlan 1
+     ->  Seq Scan on z1_blacklist
+(4 rows)
+
 SET SESSION AUTHORIZATION regress_rls_bob;
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 --
 -- Command specific
 --
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE x1 (a int, b text, c text);
+CREATE PROPERTY GRAPH cstpg VERTEX TABLES (x1 KEY (a));
 GRANT ALL ON x1 TO PUBLIC;
+GRANT ALL ON cstpg TO PUBLIC;
 INSERT INTO x1 VALUES
     (1, 'abc', 'regress_rls_bob'),
     (2, 'bcd', 'regress_rls_bob'),
@@ -2982,6 +4402,23 @@ NOTICE:  f_leak => fgh
  8 | fgh | regress_rls_carol
 (6 rows)
 
+SELECT * FROM GRAPH_TABLE (cstpg MATCH (x : x1) WHERE f_leak(x.b) COLUMNS (x.a, x.b, x.c)) ORDER BY a ASC;
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => def
+NOTICE:  f_leak => efg
+NOTICE:  f_leak => fgh
+NOTICE:  f_leak => fgh
+ a |  b  |         c         
+---+-----+-------------------
+ 1 | abc | regress_rls_bob
+ 2 | bcd | regress_rls_bob
+ 4 | def | regress_rls_carol
+ 5 | efg | regress_rls_bob
+ 6 | fgh | regress_rls_bob
+ 8 | fgh | regress_rls_carol
+(6 rows)
+
 UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
 NOTICE:  f_leak => abc
 NOTICE:  f_leak => bcd
@@ -3017,6 +4454,23 @@ NOTICE:  f_leak => fgh_updt
  8 | fgh_updt | regress_rls_carol
 (6 rows)
 
+SELECT * FROM GRAPH_TABLE (cstpg MATCH (x : x1) WHERE f_leak(x.b) COLUMNS (x.a, x.b, x.c)) ORDER BY a ASC;
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => fgh
+NOTICE:  f_leak => bcd_updt
+NOTICE:  f_leak => def_updt
+NOTICE:  f_leak => fgh_updt
+NOTICE:  f_leak => fgh_updt
+ a |    b     |         c         
+---+----------+-------------------
+ 2 | bcd_updt | regress_rls_bob
+ 3 | cde      | regress_rls_carol
+ 4 | def_updt | regress_rls_carol
+ 6 | fgh_updt | regress_rls_bob
+ 7 | fgh      | regress_rls_carol
+ 8 | fgh_updt | regress_rls_carol
+(6 rows)
+
 UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
 NOTICE:  f_leak => cde
 NOTICE:  f_leak => fgh
@@ -3057,7 +4511,9 @@ NOTICE:  f_leak => fgh_updt_updt
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE y1 (a int, b text);
 CREATE TABLE y2 (a int, b text);
+CREATE PROPERTY GRAPH svbtpg VERTEX TABLES (y1 KEY (a), y2 KEY (a));
 GRANT ALL ON y1, y2 TO regress_rls_bob;
+GRANT ALL ON svbtpg TO regress_rls_bob;
 CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);
 CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);
 CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail
@@ -3072,6 +4528,7 @@ ALTER TABLE y2 ENABLE ROW LEVEL SECURITY;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE VIEW rls_sbv WITH (security_barrier) AS
     SELECT * FROM y1 WHERE f_leak(b);
+ALTER PROPERTY GRAPH svbtpg ADD VERTEX TABLES (rls_sbv KEY (a));
 EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
             QUERY PLAN             
 -----------------------------------
@@ -3079,6 +4536,14 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
    Filter: (f_leak(b) AND (a = 1))
 (2 rows)
 
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (svbtpg MATCH (r: rls_sbv) WHERE r.a = 1 COLUMNS (r.a, r.b));
+            QUERY PLAN             
+-----------------------------------
+ Seq Scan on y1
+   Filter: (f_leak(b) AND (a = 1))
+(2 rows)
+
+ALTER PROPERTY GRAPH svbtpg DROP VERTEX TABLES (rls_sbv);
 DROP VIEW rls_sbv;
 -- Create view as role that does not own table.  RLS should be applied.
 SET SESSION AUTHORIZATION regress_rls_bob;
@@ -3140,6 +4605,46 @@ EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
    Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak(y.b) COLUMNS (y.a, y.b));
+NOTICE:  f_leak => 5feceb66ffc86f38d952786c6d696c79
+NOTICE:  f_leak => d4735e3a265e16eee03f59718b9b5d03
+NOTICE:  f_leak => 4e07408562bedb8b60ce05c1decfe3ad
+NOTICE:  f_leak => 4b227777d4dd1fc61c6f884f48641d02
+NOTICE:  f_leak => e7f6c011776e8db7cd330b54174fd76f
+NOTICE:  f_leak => 2c624232cdd221771294dfbb310aca00
+NOTICE:  f_leak => 19581e27de7ced00ff1ce50b2047e7a5
+NOTICE:  f_leak => 4a44dc15364204a80fe80e9039455cc1
+NOTICE:  f_leak => 6b51d431df5d7f141cbececcf79edf3d
+NOTICE:  f_leak => 8527a891e224136950ff32ca212b45bc
+NOTICE:  f_leak => e629fa6598d732768f7c726b4b621285
+NOTICE:  f_leak => b17ef6d19c7a5b1ee83b907c595526dc
+NOTICE:  f_leak => 4ec9599fc203d176a301536c2e091a19
+NOTICE:  f_leak => f5ca38f748a1d6eaf726b8a42fb575c3
+ a  |                b                 
+----+----------------------------------
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  3 | 4e07408562bedb8b60ce05c1decfe3ad
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+  9 | 19581e27de7ced00ff1ce50b2047e7a5
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 15 | e629fa6598d732768f7c726b4b621285
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+(14 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak(y.b) COLUMNS (y.a, y.b));
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Seq Scan on y2
+   Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))
+(2 rows)
+
 --
 -- Qual push-down of leaky functions, when not referring to table
 --
@@ -3190,6 +4695,53 @@ EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
    Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak('abc') COLUMNS (y.a, y.b));
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => abc
+ a  |                b                 
+----+----------------------------------
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  3 | 4e07408562bedb8b60ce05c1decfe3ad
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+  9 | 19581e27de7ced00ff1ce50b2047e7a5
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 15 | e629fa6598d732768f7c726b4b621285
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+(14 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak('abc') COLUMNS (y.a, y.b));
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Seq Scan on y2
+   Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))
+(2 rows)
+
 CREATE TABLE test_qual_pushdown (
     abc text
 );
@@ -3248,18 +4800,22 @@ DROP TABLE test_qual_pushdown;
 -- Plancache invalidate on user change.
 --
 RESET SESSION AUTHORIZATION;
+DROP PROPERTY GRAPH itpg;
 DROP TABLE t1 CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to table t2
 drop cascades to table t3
 CREATE TABLE t1 (a integer);
 GRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;
+CREATE PROPERTY GRAPH itpg VERTEX TABLES (t1 KEY(a));
+GRANT SELECT ON itpg TO regress_rls_bob, regress_rls_carol;
 CREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);
 CREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);
 ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
 -- Prepare as regress_rls_bob
 SET ROLE regress_rls_bob;
 PREPARE role_inval AS SELECT * FROM t1;
+PREPARE role_inval_pg AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a));
 -- Check plan
 EXPLAIN (COSTS OFF) EXECUTE role_inval;
        QUERY PLAN        
@@ -3268,6 +4824,13 @@ EXPLAIN (COSTS OFF) EXECUTE role_inval;
    Filter: ((a % 2) = 0)
 (2 rows)
 
+EXPLAIN (COSTS OFF) EXECUTE role_inval_pg;
+       QUERY PLAN        
+-------------------------
+ Seq Scan on t1
+   Filter: ((a % 2) = 0)
+(2 rows)
+
 -- Change to regress_rls_carol
 SET ROLE regress_rls_carol;
 -- Check plan- should be different
@@ -3278,6 +4841,13 @@ EXPLAIN (COSTS OFF) EXECUTE role_inval;
    Filter: ((a % 4) = 0)
 (2 rows)
 
+EXPLAIN (COSTS OFF) EXECUTE role_inval_pg;
+       QUERY PLAN        
+-------------------------
+ Seq Scan on t1
+   Filter: ((a % 4) = 0)
+(2 rows)
+
 -- Change back to regress_rls_bob
 SET ROLE regress_rls_bob;
 -- Check plan- should be back to original
@@ -3288,15 +4858,25 @@ EXPLAIN (COSTS OFF) EXECUTE role_inval;
    Filter: ((a % 2) = 0)
 (2 rows)
 
+EXPLAIN (COSTS OFF) EXECUTE role_inval_pg;
+       QUERY PLAN        
+-------------------------
+ Seq Scan on t1
+   Filter: ((a % 2) = 0)
+(2 rows)
+
 --
 -- CTE and RLS
 --
 RESET SESSION AUTHORIZATION;
+DROP PROPERTY GRAPH itpg;
 DROP TABLE t1 CASCADE;
 CREATE TABLE t1 (a integer, b text);
+CREATE PROPERTY GRAPH itpg VERTEX TABLES (t1 KEY(a));
 CREATE POLICY p1 ON t1 USING (a % 2 = 0);
 ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
 GRANT ALL ON t1 TO regress_rls_bob;
+GRANT ALL ON itpg TO regress_rls_bob;
 INSERT INTO t1 (SELECT x, public.fipshash(x::text) FROM generate_series(0,20) x);
 SET SESSION AUTHORIZATION regress_rls_bob;
 WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
@@ -3324,21 +4904,163 @@ NOTICE:  f_leak => f5ca38f748a1d6eaf726b8a42fb575c3
  16 | b17ef6d19c7a5b1ee83b907c595526dc
  18 | 4ec9599fc203d176a301536c2e091a19
  20 | f5ca38f748a1d6eaf726b8a42fb575c3
-(11 rows)
-
-EXPLAIN (COSTS OFF)
-WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
-                   QUERY PLAN                    
--------------------------------------------------
- CTE Scan on cte1
-   CTE cte1
-     ->  Seq Scan on t1
-           Filter: (((a % 2) = 0) AND f_leak(b))
-(4 rows)
+(11 rows)
+
+EXPLAIN (COSTS OFF)
+WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+                   QUERY PLAN                    
+-------------------------------------------------
+ CTE Scan on cte1
+   CTE cte1
+     ->  Seq Scan on t1
+           Filter: (((a % 2) = 0) AND f_leak(b))
+(4 rows)
+
+WITH cte1 AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) WHERE f_leak(t.b) COLUMNS (t.a, t.b))) SELECT * FROM cte1;
+NOTICE:  f_leak => 5feceb66ffc86f38d952786c6d696c79
+NOTICE:  f_leak => d4735e3a265e16eee03f59718b9b5d03
+NOTICE:  f_leak => 4b227777d4dd1fc61c6f884f48641d02
+NOTICE:  f_leak => e7f6c011776e8db7cd330b54174fd76f
+NOTICE:  f_leak => 2c624232cdd221771294dfbb310aca00
+NOTICE:  f_leak => 4a44dc15364204a80fe80e9039455cc1
+NOTICE:  f_leak => 6b51d431df5d7f141cbececcf79edf3d
+NOTICE:  f_leak => 8527a891e224136950ff32ca212b45bc
+NOTICE:  f_leak => b17ef6d19c7a5b1ee83b907c595526dc
+NOTICE:  f_leak => 4ec9599fc203d176a301536c2e091a19
+NOTICE:  f_leak => f5ca38f748a1d6eaf726b8a42fb575c3
+ a  |                b                 
+----+----------------------------------
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+(11 rows)
+
+EXPLAIN (COSTS OFF)
+WITH cte1 AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) WHERE f_leak(t.b) COLUMNS (t.a, t.b))) SELECT * FROM cte1;
+                   QUERY PLAN                    
+-------------------------------------------------
+ CTE Scan on cte1
+   CTE cte1
+     ->  Seq Scan on t1
+           Filter: (((a % 2) = 0) AND f_leak(b))
+(4 rows)
+
+WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
+ERROR:  new row violates row-level security policy for table "t1"
+WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
+ a  |                b                 
+----+----------------------------------
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+(11 rows)
+
+WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
+ERROR:  new row violates row-level security policy for table "t1"
+WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
+ a  |    b    
+----+---------
+ 20 | Success
+(1 row)
+
+--
+-- Rename Policy
+--
+RESET SESSION AUTHORIZATION;
+ALTER POLICY p1 ON t1 RENAME TO p1; --fail
+ERROR:  policy "p1" for table "t1" already exists
+SELECT polname, relname
+    FROM pg_policy pol
+    JOIN pg_class pc ON (pc.oid = pol.polrelid)
+    WHERE relname = 't1';
+ polname | relname 
+---------+---------
+ p1      | t1
+(1 row)
+
+ALTER POLICY p1 ON t1 RENAME TO p2; --ok
+SELECT polname, relname
+    FROM pg_policy pol
+    JOIN pg_class pc ON (pc.oid = pol.polrelid)
+    WHERE relname = 't1';
+ polname | relname 
+---------+---------
+ p2      | t1
+(1 row)
+
+--
+-- Check INSERT SELECT
+--
+SET SESSION AUTHORIZATION regress_rls_bob;
+CREATE TABLE t2 (a integer, b text);
+INSERT INTO t2 (SELECT * FROM t1);
+EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);
+          QUERY PLAN           
+-------------------------------
+ Insert on t2
+   ->  Seq Scan on t1
+         Filter: ((a % 2) = 0)
+(3 rows)
+
+SELECT * FROM t2;
+ a  |                b                 
+----+----------------------------------
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+ 20 | Success
+(12 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t2;
+   QUERY PLAN   
+----------------
+ Seq Scan on t2
+(1 row)
+
+CREATE TABLE t3 AS SELECT * FROM t1;
+SELECT * FROM t3;
+ a  |                b                 
+----+----------------------------------
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+ 20 | Success
+(12 rows)
 
-WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
-ERROR:  new row violates row-level security policy for table "t1"
-WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
+SELECT * INTO t4 FROM t1;
+SELECT * FROM t4;
  a  |                b                 
 ----+----------------------------------
   0 | 5feceb66ffc86f38d952786c6d696c79
@@ -3352,48 +5074,13 @@ WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
  16 | b17ef6d19c7a5b1ee83b907c595526dc
  18 | 4ec9599fc203d176a301536c2e091a19
  20 | f5ca38f748a1d6eaf726b8a42fb575c3
-(11 rows)
-
-WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
-ERROR:  new row violates row-level security policy for table "t1"
-WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
- a  |    b    
-----+---------
  20 | Success
-(1 row)
-
---
--- Rename Policy
---
-RESET SESSION AUTHORIZATION;
-ALTER POLICY p1 ON t1 RENAME TO p1; --fail
-ERROR:  policy "p1" for table "t1" already exists
-SELECT polname, relname
-    FROM pg_policy pol
-    JOIN pg_class pc ON (pc.oid = pol.polrelid)
-    WHERE relname = 't1';
- polname | relname 
----------+---------
- p1      | t1
-(1 row)
-
-ALTER POLICY p1 ON t1 RENAME TO p2; --ok
-SELECT polname, relname
-    FROM pg_policy pol
-    JOIN pg_class pc ON (pc.oid = pol.polrelid)
-    WHERE relname = 't1';
- polname | relname 
----------+---------
- p2      | t1
-(1 row)
+(12 rows)
 
---
--- Check INSERT SELECT
---
-SET SESSION AUTHORIZATION regress_rls_bob;
+DROP TABLE t2, t3, t4;
 CREATE TABLE t2 (a integer, b text);
-INSERT INTO t2 (SELECT * FROM t1);
-EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);
+INSERT INTO t2 (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b)));
+EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b)));
           QUERY PLAN           
 -------------------------------
  Insert on t2
@@ -3424,7 +5111,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM t2;
  Seq Scan on t2
 (1 row)
 
-CREATE TABLE t3 AS SELECT * FROM t1;
+CREATE TABLE t3 AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
 SELECT * FROM t3;
  a  |                b                 
 ----+----------------------------------
@@ -3442,7 +5129,7 @@ SELECT * FROM t3;
  20 | Success
 (12 rows)
 
-SELECT * INTO t4 FROM t1;
+SELECT * INTO t4 FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
 SELECT * FROM t4;
  a  |                b                 
 ----+----------------------------------
@@ -3466,7 +5153,14 @@ SELECT * FROM t4;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE blog (id integer, author text, post text);
 CREATE TABLE comment (blog_id integer, message text);
+CREATE PROPERTY GRAPH jtpg
+    VERTEX TABLES (blog KEY (id))
+    EDGE TABLES
+    (comment KEY (blog_id)
+     SOURCE KEY (blog_id) REFERENCES blog(id)
+     DESTINATION KEY (blog_id) REFERENCES blog(id));
 GRANT ALL ON blog, comment TO regress_rls_bob;
+GRANT ALL ON jtpg TO regress_rls_bob;
 CREATE POLICY blog_1 ON blog USING (id % 2 = 0);
 ALTER TABLE blog ENABLE ROW LEVEL SECURITY;
 INSERT INTO blog VALUES
@@ -3499,6 +5193,13 @@ SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
   2 | bob    | who did it?
 (2 rows)
 
+SELECT id, author, message FROM GRAPH_TABLE (jtpg MATCH (b: blog)-[c: comment]->(b) COLUMNS (b.id AS id, b.author AS author, c.message AS message));
+ id | author |   message   
+----+--------+-------------
+  4 | alice  | insane!
+  2 | bob    | who did it?
+(2 rows)
+
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE POLICY comment_1 ON comment USING (blog_id < 4);
 ALTER TABLE comment ENABLE ROW LEVEL SECURITY;
@@ -3516,7 +5217,14 @@ SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
   2 | bob    | who did it?
 (1 row)
 
+SELECT id, author, message FROM GRAPH_TABLE (jtpg MATCH (b: blog)-[c: comment]->(b) COLUMNS (b.id AS id, b.author AS author, c.message AS message));
+ id | author |   message   
+----+--------+-------------
+  2 | bob    | who did it?
+(1 row)
+
 SET SESSION AUTHORIZATION regress_rls_alice;
+DROP PROPERTY GRAPH jtpg;
 DROP TABLE blog, comment;
 --
 -- Default Deny Policy
@@ -3524,6 +5232,7 @@ DROP TABLE blog, comment;
 RESET SESSION AUTHORIZATION;
 DROP POLICY p2 ON t1;
 ALTER TABLE t1 OWNER TO regress_rls_alice;
+GRANT SELECT ON itpg TO regress_rls_alice;
 -- Check that default deny does not apply to superuser.
 RESET SESSION AUTHORIZATION;
 SELECT * FROM t1;
@@ -3559,6 +5268,39 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1;
  Seq Scan on t1
 (1 row)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+ a  |                b                 
+----+----------------------------------
+  1 | 6b86b273ff34fce19d6b804eff5a3f57
+  3 | 4e07408562bedb8b60ce05c1decfe3ad
+  5 | ef2d127de37b942baad06145e54b0c61
+  7 | 7902699be42c8a8e46fbbb4501726517
+  9 | 19581e27de7ced00ff1ce50b2047e7a5
+ 11 | 4fc82b26aecb47d2868c4efbe3581732
+ 13 | 3fdba35f04dc8c462986c992bcf87554
+ 15 | e629fa6598d732768f7c726b4b621285
+ 17 | 4523540f1504cd17100c4835e85b7eef
+ 19 | 9400f1b21cb527d7fa3d3eabba93557a
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+ 20 | Success
+(22 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+   QUERY PLAN   
+----------------
+ Seq Scan on t1
+(1 row)
+
 -- Check that default deny does not apply to table owner.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM t1;
@@ -3594,6 +5336,39 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1;
  Seq Scan on t1
 (1 row)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+ a  |                b                 
+----+----------------------------------
+  1 | 6b86b273ff34fce19d6b804eff5a3f57
+  3 | 4e07408562bedb8b60ce05c1decfe3ad
+  5 | ef2d127de37b942baad06145e54b0c61
+  7 | 7902699be42c8a8e46fbbb4501726517
+  9 | 19581e27de7ced00ff1ce50b2047e7a5
+ 11 | 4fc82b26aecb47d2868c4efbe3581732
+ 13 | 3fdba35f04dc8c462986c992bcf87554
+ 15 | e629fa6598d732768f7c726b4b621285
+ 17 | 4523540f1504cd17100c4835e85b7eef
+ 19 | 9400f1b21cb527d7fa3d3eabba93557a
+  0 | 5feceb66ffc86f38d952786c6d696c79
+  2 | d4735e3a265e16eee03f59718b9b5d03
+  4 | 4b227777d4dd1fc61c6f884f48641d02
+  6 | e7f6c011776e8db7cd330b54174fd76f
+  8 | 2c624232cdd221771294dfbb310aca00
+ 10 | 4a44dc15364204a80fe80e9039455cc1
+ 12 | 6b51d431df5d7f141cbececcf79edf3d
+ 14 | 8527a891e224136950ff32ca212b45bc
+ 16 | b17ef6d19c7a5b1ee83b907c595526dc
+ 18 | 4ec9599fc203d176a301536c2e091a19
+ 20 | f5ca38f748a1d6eaf726b8a42fb575c3
+ 20 | Success
+(22 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+   QUERY PLAN   
+----------------
+ Seq Scan on t1
+(1 row)
+
 -- Check that default deny applies to non-owner/non-superuser when RLS on.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO ON;
@@ -3609,6 +5384,18 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1;
    One-Time Filter: false
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+ a | b 
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM t1;
  a | b 
@@ -3622,6 +5409,18 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1;
    One-Time Filter: false
 (2 rows)
 
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+ a | b 
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
 --
 -- COPY TO/FROM
 --
@@ -3629,9 +5428,11 @@ RESET SESSION AUTHORIZATION;
 DROP TABLE copy_t CASCADE;
 ERROR:  table "copy_t" does not exist
 CREATE TABLE copy_t (a integer, b text);
+CREATE PROPERTY GRAPH ctpg VERTEX TABLES (copy_t KEY (a));
 CREATE POLICY p1 ON copy_t USING (a % 2 = 0);
 ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;
 GRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;
+GRANT ALL ON ctpg TO regress_rls_bob, regress_rls_exempt_user;
 INSERT INTO copy_t (SELECT x, public.fipshash(x::text) FROM generate_series(0,10) x);
 -- Check COPY TO as Superuser/owner.
 RESET SESSION AUTHORIZATION;
@@ -3648,6 +5449,18 @@ COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 8,2c624232cdd221771294dfbb310aca00
 9,19581e27de7ced00ff1ce50b2047e7a5
 10,4a44dc15364204a80fe80e9039455cc1
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,5feceb66ffc86f38d952786c6d696c79
+1,6b86b273ff34fce19d6b804eff5a3f57
+2,d4735e3a265e16eee03f59718b9b5d03
+3,4e07408562bedb8b60ce05c1decfe3ad
+4,4b227777d4dd1fc61c6f884f48641d02
+5,ef2d127de37b942baad06145e54b0c61
+6,e7f6c011776e8db7cd330b54174fd76f
+7,7902699be42c8a8e46fbbb4501726517
+8,2c624232cdd221771294dfbb310aca00
+9,19581e27de7ced00ff1ce50b2047e7a5
+10,4a44dc15364204a80fe80e9039455cc1
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 0,5feceb66ffc86f38d952786c6d696c79
@@ -3661,11 +5474,25 @@ COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 8,2c624232cdd221771294dfbb310aca00
 9,19581e27de7ced00ff1ce50b2047e7a5
 10,4a44dc15364204a80fe80e9039455cc1
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,5feceb66ffc86f38d952786c6d696c79
+1,6b86b273ff34fce19d6b804eff5a3f57
+2,d4735e3a265e16eee03f59718b9b5d03
+3,4e07408562bedb8b60ce05c1decfe3ad
+4,4b227777d4dd1fc61c6f884f48641d02
+5,ef2d127de37b942baad06145e54b0c61
+6,e7f6c011776e8db7cd330b54174fd76f
+7,7902699be42c8a8e46fbbb4501726517
+8,2c624232cdd221771294dfbb310aca00
+9,19581e27de7ced00ff1ce50b2047e7a5
+10,4a44dc15364204a80fe80e9039455cc1
 -- Check COPY TO as user with permissions.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO OFF;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
 ERROR:  query would be affected by row-level security policy for table "copy_t"
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+ERROR:  query would be affected by row-level security policy for table "copy_t"
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
 0,5feceb66ffc86f38d952786c6d696c79
@@ -3674,6 +5501,13 @@ COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
 6,e7f6c011776e8db7cd330b54174fd76f
 8,2c624232cdd221771294dfbb310aca00
 10,4a44dc15364204a80fe80e9039455cc1
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,5feceb66ffc86f38d952786c6d696c79
+2,d4735e3a265e16eee03f59718b9b5d03
+4,4b227777d4dd1fc61c6f884f48641d02
+6,e7f6c011776e8db7cd330b54174fd76f
+8,2c624232cdd221771294dfbb310aca00
+10,4a44dc15364204a80fe80e9039455cc1
 -- Check COPY TO as user with permissions and BYPASSRLS
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
@@ -3689,6 +5523,18 @@ COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
 8,2c624232cdd221771294dfbb310aca00
 9,19581e27de7ced00ff1ce50b2047e7a5
 10,4a44dc15364204a80fe80e9039455cc1
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,5feceb66ffc86f38d952786c6d696c79
+1,6b86b273ff34fce19d6b804eff5a3f57
+2,d4735e3a265e16eee03f59718b9b5d03
+3,4e07408562bedb8b60ce05c1decfe3ad
+4,4b227777d4dd1fc61c6f884f48641d02
+5,ef2d127de37b942baad06145e54b0c61
+6,e7f6c011776e8db7cd330b54174fd76f
+7,7902699be42c8a8e46fbbb4501726517
+8,2c624232cdd221771294dfbb310aca00
+9,19581e27de7ced00ff1ce50b2047e7a5
+10,4a44dc15364204a80fe80e9039455cc1
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
 0,5feceb66ffc86f38d952786c6d696c79
@@ -3702,14 +5548,30 @@ COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
 8,2c624232cdd221771294dfbb310aca00
 9,19581e27de7ced00ff1ce50b2047e7a5
 10,4a44dc15364204a80fe80e9039455cc1
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,5feceb66ffc86f38d952786c6d696c79
+1,6b86b273ff34fce19d6b804eff5a3f57
+2,d4735e3a265e16eee03f59718b9b5d03
+3,4e07408562bedb8b60ce05c1decfe3ad
+4,4b227777d4dd1fc61c6f884f48641d02
+5,ef2d127de37b942baad06145e54b0c61
+6,e7f6c011776e8db7cd330b54174fd76f
+7,7902699be42c8a8e46fbbb4501726517
+8,2c624232cdd221771294dfbb310aca00
+9,19581e27de7ced00ff1ce50b2047e7a5
+10,4a44dc15364204a80fe80e9039455cc1
 -- Check COPY TO as user without permissions. SET row_security TO OFF;
 SET SESSION AUTHORIZATION regress_rls_carol;
 SET row_security TO OFF;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
 ERROR:  query would be affected by row-level security policy for table "copy_t"
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+ERROR:  query would be affected by row-level security policy for table "copy_t"
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
 ERROR:  permission denied for table copy_t
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+ERROR:  permission denied for property graph ctpg
 -- Check COPY relation TO; keep it just one row to avoid reordering issues
 RESET SESSION AUTHORIZATION;
 SET row_security TO ON;
@@ -3813,6 +5675,7 @@ SET row_security TO ON;
 COPY copy_t FROM STDIN; --fail - permission denied.
 ERROR:  permission denied for table copy_t
 RESET SESSION AUTHORIZATION;
+DROP PROPERTY GRAPH ctpg;
 DROP TABLE copy_t;
 DROP TABLE copy_rel_to CASCADE;
 NOTICE:  drop cascades to table copy_rel_to_child
@@ -4005,9 +5868,11 @@ SELECT attname, most_common_vals FROM pg_stats
 --
 BEGIN;
 CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
+CREATE PROPERTY GRAPH cltpg VERTEX TABLES (coll_t KEY (c));
 CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
 ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
 GRANT SELECT ON coll_t TO regress_rls_alice;
+GRANT SELECT ON cltpg TO regress_rls_alice;
 SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;
    inputcollid    
 ------------------
@@ -4021,6 +5886,12 @@ SELECT * FROM coll_t;
  bar
 (1 row)
 
+SELECT * FROM GRAPH_TABLE (cltpg MATCH (c: coll_t) COLUMNS (c.c));
+  c  
+-----
+ bar
+(1 row)
+
 ROLLBACK;
 --
 -- Shared Object Dependencies
@@ -4093,7 +5964,8 @@ CREATE TABLE r1 (a int);
 CREATE TABLE r2 (a int);
 INSERT INTO r1 VALUES (10), (20);
 INSERT INTO r2 VALUES (10), (20);
-GRANT ALL ON r1, r2 TO regress_rls_bob;
+CREATE PROPERTY GRAPH trtpg VERTEX TABLES (r2 KEY (a));
+GRANT ALL ON r1, r2, trtpg TO regress_rls_bob;
 CREATE POLICY p1 ON r1 USING (true);
 ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
 CREATE POLICY p1 ON r2 FOR SELECT USING (true);
@@ -4165,7 +6037,43 @@ SELECT * FROM r2;
  20
 (2 rows)
 
+INSERT INTO r1 SELECT a FROM GRAPH_TABLE (trtpg MATCH (r: r2) COLUMNS (r.a)) r2 RETURNING *; -- OK
+ a  
+----
+ 10
+ 20
+(2 rows)
+
+UPDATE r1 SET a = r2.a + 2 FROM GRAPH_TABLE (trtpg MATCH (r: r2) COLUMNS (r.a)) r2 WHERE r1.a = r2.a RETURNING *; -- OK
+ a  | a  
+----+----
+ 12 | 10
+ 22 | 20
+(2 rows)
+
+DELETE FROM r1 USING GRAPH_TABLE (trtpg MATCH (r: r2) COLUMNS (r.a)) r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK
+ a  | a  
+----+----
+ 12 | 10
+ 22 | 20
+(2 rows)
+
+SELECT * FROM r1;
+ a  
+----
+ 11
+ 21
+(2 rows)
+
+SELECT * FROM r2;
+ a  
+----
+ 10
+ 20
+(2 rows)
+
 SET SESSION AUTHORIZATION regress_rls_alice;
+DROP PROPERTY GRAPH trtpg;
 DROP TABLE r1;
 DROP TABLE r2;
 --
@@ -4174,6 +6082,7 @@ DROP TABLE r2;
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security = on;
 CREATE TABLE r1 (a int);
+CREATE PROPERTY GRAPH frlstpg VERTEX TABLES (r1 KEY (a));
 INSERT INTO r1 VALUES (10), (20);
 CREATE POLICY p1 ON r1 USING (false);
 ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
@@ -4184,6 +6093,11 @@ TABLE r1;
 ---
 (0 rows)
 
+SELECT * FROM GRAPH_TABLE (frlstpg MATCH (r: r1) COLUMNS (r.a));
+ a 
+---
+(0 rows)
+
 -- RLS error
 INSERT INTO r1 VALUES (1);
 ERROR:  new row violates row-level security policy for table "r1"
@@ -4206,12 +6120,16 @@ SET row_security = off;
 TABLE r1;
 ERROR:  query would be affected by row-level security policy for table "r1"
 HINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
+SELECT * FROM GRAPH_TABLE (frlstpg MATCH (r: r1) COLUMNS (r.a));
+ERROR:  query would be affected by row-level security policy for table "r1"
+HINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
 UPDATE r1 SET a = 1;
 ERROR:  query would be affected by row-level security policy for table "r1"
 HINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
 DELETE FROM r1;
 ERROR:  query would be affected by row-level security policy for table "r1"
 HINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
+DROP PROPERTY GRAPH frlstpg;
 DROP TABLE r1;
 --
 -- FORCE ROW LEVEL SECURITY does not break RI
@@ -4481,8 +6399,10 @@ DROP TABLE ref_tbl;
 CREATE TABLE rls_tbl (a int);
 INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
 ANALYZE rls_tbl;
+CREATE PROPERTY GRAPH lotpg VERTEX TABLES (rls_tbl KEY (a));
 ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
 GRANT SELECT ON rls_tbl TO regress_rls_alice;
+GRANT SELECT ON lotpg TO regress_rls_alice;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE FUNCTION op_leak(int, int) RETURNS bool
     AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
@@ -4494,9 +6414,20 @@ SELECT * FROM rls_tbl WHERE a <<< 1000;
 ---
 (0 rows)
 
+SELECT * FROM GRAPH_TABLE (lotpg MATCH (r: rls_tbl) WHERE r.a <<< 1000 COLUMNS (r.a));
+ a 
+---
+(0 rows)
+
+SELECT * FROM GRAPH_TABLE (lotpg MATCH (r: rls_tbl WHERE r.a <<< 1000) COLUMNS (r.a));
+ a 
+---
+(0 rows)
+
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
+DROP PROPERTY GRAPH lotpg;
 DROP TABLE rls_tbl;
 -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -4664,29 +6595,38 @@ DROP TABLE rls_t;
 --
 RESET SESSION AUTHORIZATION;
 DROP SCHEMA regress_rls_schema CASCADE;
-NOTICE:  drop cascades to 30 other objects
+NOTICE:  drop cascades to 39 other objects
 DETAIL:  drop cascades to function f_leak(text)
 drop cascades to table uaccount
 drop cascades to table category
 drop cascades to table document
+drop cascades to property graph acc_cat
 drop cascades to table part_document
+drop cascades to property graph ptpg
 drop cascades to table dependent
 drop cascades to table rec1
+drop cascades to property graph rtpg
 drop cascades to table rec2
 drop cascades to view rec1v
 drop cascades to view rec2v
 drop cascades to table s1
 drop cascades to table s2
+drop cascades to property graph rvtpg
 drop cascades to view v2
 drop cascades to table b1
 drop cascades to view bv1
+drop cascades to property graph sbvpg
 drop cascades to table z1
 drop cascades to table z2
+drop cascades to property graph gtpg
 drop cascades to table z1_blacklist
 drop cascades to table x1
+drop cascades to property graph cstpg
 drop cascades to table y1
 drop cascades to table y2
+drop cascades to property graph svbtpg
 drop cascades to table t1
+drop cascades to property graph itpg
 drop cascades to table t2
 drop cascades to table t3
 drop cascades to table t4
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index eab7d99003e..e7bf8cdaa2d 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -86,6 +86,14 @@ INSERT INTO document VALUES
     ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),
     (10, 33, 2, 'regress_rls_dave', 'awesome technology book');
 
+CREATE PROPERTY GRAPH acc_cat
+    VERTEX TABLES (uaccount, category)
+    EDGE TABLES (
+        document
+        SOURCE KEY (dauthor) REFERENCES uaccount (pguser)
+        DESTINATION KEY (cid) REFERENCES category (cid));
+GRANT ALL ON acc_cat TO public;
+
 ALTER TABLE document ENABLE ROW LEVEL SECURITY;
 
 -- user's security level must be higher than or equal to document's
@@ -116,6 +124,9 @@ SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO ON;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
 SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- try a sampled version
 SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
@@ -125,6 +136,9 @@ SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
 SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- try a sampled version
 SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
@@ -132,14 +146,23 @@ SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
 
 EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
 EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- viewpoint from regress_rls_dave
 SET SESSION AUTHORIZATION regress_rls_dave;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
 SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
 EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- 44 would technically fail for both p2r and p1r, but we should get an error
 -- back from p1r for this because it sorts first
@@ -158,14 +181,23 @@ ALTER POLICY p1 ON document USING (dauthor = current_user);
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
 SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- viewpoint from rls_regres_carol again
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
 SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
 EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- interaction of FK/PK constraints
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -200,30 +232,45 @@ RESET SESSION AUTHORIZATION;
 SET row_security TO ON;
 SELECT * FROM document;
 SELECT * FROM category;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- database superuser does bypass RLS policy when disabled
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;
 SELECT * FROM document;
 SELECT * FROM category;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- database non-superuser with bypass privilege can bypass RLS policy when disabled
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
 SELECT * FROM document;
 SELECT * FROM category;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- RLS policy does not apply to table owner when RLS enabled.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security TO ON;
 SELECT * FROM document;
 SELECT * FROM category;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 -- RLS policy does not apply to table owner when RLS disabled.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security TO OFF;
 SELECT * FROM document;
 SELECT * FROM category;
+SELECT * FROM GRAPH_TABLE (acc_cat
+                            MATCH (u : uaccount)-[d : document WHERE f_leak(d.dtitle)]->(c : category)
+                            COLUMNS (u.pguser, d.did, d.dlevel, d.dtitle, c.cid, c.cname)) ORDER BY did;
 
 --
 -- Table inheritance and RLS policy
@@ -263,6 +310,13 @@ COPY t3(id, a,b,c) FROM stdin;
 303	3	zzz	Z
 \.
 
+CREATE PROPERTY GRAPH itpg
+    VERTEX TABLES (
+        t1 KEY(id) DEFAULT LABEL PROPERTIES (id, a, b, tableoid::regclass as tableoid),
+        t2 KEY(id) DEFAULT LABEL PROPERTIES (id, a, b, tableoid::regclass as tableoid),
+        t3 KEY(id) DEFAULT LABEL PROPERTIES (id, a, b, tableoid::regclass as tableoid));
+GRANT ALL ON itpg TO public;
+
 CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number
 CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number
 
@@ -273,9 +327,13 @@ SET SESSION AUTHORIZATION regress_rls_bob;
 
 SELECT * FROM t1;
 EXPLAIN (COSTS OFF) SELECT * FROM t1;
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.id, t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.id, t.a, t.b));
 
 SELECT * FROM t1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
 
 -- reference to system column
 SELECT tableoid::regclass, * FROM t1;
@@ -295,18 +353,25 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
 -- union all query
 SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3;
 EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3;
+-- label disjunction
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t2 | t3) COLUMNS (t.a, t.b, t.tableoid));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t2 | t3) COLUMNS (t.a, t.b, t.tableoid));
 
 -- superuser is allowed to bypass RLS checks
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;
 SELECT * FROM t1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
 
 -- non-superuser with bypass privilege can bypass RLS policy when disabled
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
 SELECT * FROM t1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1 WHERE f_leak(t.b)) COLUMNS (t.id, t.a, t.b));
 
 --
 -- Partitioned Tables
@@ -344,6 +409,11 @@ INSERT INTO part_document VALUES
     ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),
     (10, 99, 2, 'regress_rls_dave', 'awesome technology book');
 
+CREATE PROPERTY GRAPH ptpg
+    VERTEX TABLES (part_document KEY (did), part_document_satire KEY (did));
+
+GRANT ALL ON ptpg TO public;
+
 ALTER TABLE part_document ENABLE ROW LEVEL SECURITY;
 
 -- Create policy on parent
@@ -363,16 +433,22 @@ SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO ON;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
 
 -- viewpoint from regress_rls_carol
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle)) ORDER BY did;
 
 -- viewpoint from regress_rls_dave
 SET SESSION AUTHORIZATION regress_rls_dave;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- pp1 ERROR
 INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
@@ -388,6 +464,7 @@ INSERT INTO part_document_satire VALUES (100, 55, 1, 'regress_rls_dave', 'testin
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
 -- But we can if we look directly
 SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- Turn on RLS and create policy on child to show RLS is checked before constraints
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -399,15 +476,20 @@ SET SESSION AUTHORIZATION regress_rls_dave;
 INSERT INTO part_document_satire VALUES (101, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail
 -- And now we cannot see directly into the partition either, due to RLS
 SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 -- The parent looks same as before
 -- viewpoint from regress_rls_dave
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- viewpoint from regress_rls_carol
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- only owner can change policies
 ALTER POLICY pp1 ON part_document USING (true);    --fail
@@ -419,36 +501,46 @@ ALTER POLICY pp1 ON part_document USING (dauthor = current_user);
 -- viewpoint from regress_rls_bob again
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- viewpoint from rls_regres_carol again
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
-
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document WHERE f_leak(d.dtitle)) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- database superuser does bypass RLS policy when enabled
 RESET SESSION AUTHORIZATION;
 SET row_security TO ON;
 SELECT * FROM part_document ORDER BY did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 SELECT * FROM part_document_satire ORDER by did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- database non-superuser with bypass privilege can bypass RLS policy when disabled
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
 SELECT * FROM part_document ORDER BY did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 SELECT * FROM part_document_satire ORDER by did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- RLS policy does not apply to table owner when RLS enabled.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security TO ON;
 SELECT * FROM part_document ORDER by did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 SELECT * FROM part_document_satire ORDER by did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- When RLS disabled, other users get ERROR.
 SET SESSION AUTHORIZATION regress_rls_dave;
 SET row_security TO OFF;
 SELECT * FROM part_document ORDER by did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 SELECT * FROM part_document_satire ORDER by did;
+SELECT * FROM GRAPH_TABLE (ptpg MATCH (d : part_document_satire) COLUMNS (d.did, d.cid, d.dlevel, d.dauthor, d.dtitle));
 
 -- Check behavior with a policy that uses a SubPlan not an InitPlan.
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -484,9 +576,11 @@ EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualifie
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE rec1 (x integer, y integer);
 CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));
+CREATE PROPERTY GRAPH rtpg VERTEX TABLES (rec1 KEY (x));
 ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1; -- fail, direct recursion
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, direct recursion
 
 --
 -- Mutual recursion
@@ -499,6 +593,7 @@ ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;
 
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1;    -- fail, mutual recursion
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion
 
 --
 -- Mutual recursion via views
@@ -512,6 +607,7 @@ ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
 
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1;    -- fail, mutual recursion via views
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion via views
 
 --
 -- Mutual recursion via .s.b views
@@ -528,6 +624,24 @@ CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
 
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rec1;    -- fail, mutual recursion via s.b. views
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion via s.b. views
+
+--
+-- Direct and mutual recursion via GRAPH_TABLE
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER POLICY r1 ON rec1 USING (x = (SELECT x FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)) WHERE x = y));
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1;    -- fail, direct recursion via GRAPH_TABLE
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, direct recursion via GRAPH_TABLE
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER PROPERTY GRAPH rtpg ADD VERTEX TABLES (rec2 KEY (a));
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM GRAPH_TABLE (rtpg MATCH (r: rec2) COLUMNS (r.a, r.b)) WHERE b = y));
+ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y))WHERE y = b));
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1;    -- fail, mutual recursion via GRAPH_TABLE
+SELECT * FROM GRAPH_TABLE (rtpg MATCH (r: rec1) COLUMNS (r.x, r.y)); -- fail, mutual recursion via GRAPH_TABLE
 
 --
 -- recursive RLS and VIEWs in policy
@@ -539,7 +653,10 @@ INSERT INTO s1 (SELECT x, public.fipshash(x::text) FROM generate_series(-10,10)
 CREATE TABLE s2 (x int, y text);
 INSERT INTO s2 (SELECT x, public.fipshash(x::text) FROM generate_series(-6,6) x);
 
+CREATE PROPERTY GRAPH rvtpg VERTEX TABLES (s1 KEY (a));
+
 GRANT SELECT ON s1, s2 TO regress_rls_bob;
+GRANT SELECT ON rvtpg TO regress_rls_bob;
 
 CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));
 CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));
@@ -551,6 +668,7 @@ ALTER TABLE s2 ENABLE ROW LEVEL SECURITY;
 SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
 SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b)); -- fail (infinite recursion)
 
 INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)
 
@@ -561,12 +679,16 @@ ALTER POLICY p2 ON s2 USING (x % 2 = 0);
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM s1 WHERE f_leak(b);	-- OK
 EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
 
 SET SESSION AUTHORIZATION regress_rls_alice;
 ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM s1 WHERE f_leak(b);	-- OK
 EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));
 
 SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
 EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
@@ -575,11 +697,15 @@ SET SESSION AUTHORIZATION regress_rls_alice;
 ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion via view)
+SELECT * FROM GRAPH_TABLE (rvtpg MATCH (s : s1) WHERE f_leak(s.b) COLUMNS (s.a, s.b));	-- fail (infinite recursion via view)
 
 -- prepared statement with regress_rls_alice privilege
 PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
 EXECUTE p1(2);
 EXPLAIN (COSTS OFF) EXECUTE p1(2);
+PREPARE ppg1(int) AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t1) WHERE t.a <= $1 COLUMNS (t.id, t.a, t.b));
+EXECUTE ppg1(2);
+EXPLAIN (COSTS OFF) EXECUTE ppg1(2);
 
 -- superuser is allowed to bypass RLS checks
 RESET SESSION AUTHORIZATION;
@@ -590,16 +716,23 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
 -- plan cache should be invalidated
 EXECUTE p1(2);
 EXPLAIN (COSTS OFF) EXECUTE p1(2);
+EXECUTE ppg1(2);
+EXPLAIN (COSTS OFF) EXECUTE ppg1(2);
 
 PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
 EXECUTE p2(2);
 EXPLAIN (COSTS OFF) EXECUTE p2(2);
+PREPARE ppg2(int) AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t : t1) WHERE t.a = $1 COLUMNS (t.id, t.a, t.b));
+EXECUTE ppg2(2);
+EXPLAIN (COSTS OFF) EXECUTE ppg2(2);
 
 -- also, case when privilege switch from superuser
 SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO ON;
 EXECUTE p2(2);
 EXPLAIN (COSTS OFF) EXECUTE p2(2);
+EXECUTE ppg2(2);
+EXPLAIN (COSTS OFF) EXECUTE ppg2(2);
 
 --
 -- UPDATE / DELETE and Row-level security
@@ -678,11 +811,15 @@ GRANT ALL ON b1 TO regress_rls_bob;
 SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;
 GRANT ALL ON bv1 TO regress_rls_carol;
+CREATE PROPERTY GRAPH sbvpg VERTEX TABLES (bv1 KEY (a));
+GRANT ALL ON sbvpg TO regress_rls_carol;
 
 SET SESSION AUTHORIZATION regress_rls_carol;
 
 EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
 SELECT * FROM bv1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (sbvpg MATCH (b : bv1) WHERE f_leak(b.b) COLUMNS (b.a, b.b));
+SELECT * FROM GRAPH_TABLE (sbvpg MATCH (b : bv1) WHERE f_leak(b.b) COLUMNS (b.a, b.b));
 
 INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
 INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
@@ -1027,8 +1164,9 @@ SELECT * FROM document;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE z1 (a int, b text);
 CREATE TABLE z2 (a int, b text);
+CREATE PROPERTY GRAPH gtpg VERTEX TABLES (z1 KEY (a), z2 KEY (a));
 
-GRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,
+GRANT SELECT ON z1,z2, gtpg TO regress_rls_group1, regress_rls_group2,
     regress_rls_bob, regress_rls_carol;
 
 INSERT INTO z1 VALUES
@@ -1045,39 +1183,62 @@ ALTER TABLE z1 ENABLE ROW LEVEL SECURITY;
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM z1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
 
 PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+PREPARE plancache_pg_test AS SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
 
 PREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+PREPARE plancache_pg_test2 AS WITH q AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS(z.a, z.b))) SELECT * FROM q, GRAPH_TABLE (gtpg MATCH (z : z2) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
 
 PREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);
 EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+PREPARE plancache_pg_test3 AS WITH q AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z2) COLUMNS(z.a, z.b))) SELECT * FROM q, GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
 
 SET ROLE regress_rls_group1;
 SELECT * FROM z1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
 
 EXPLAIN (COSTS OFF) EXECUTE plancache_test;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
 
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM z1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
 
 EXPLAIN (COSTS OFF) EXECUTE plancache_test;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
 
 SET ROLE regress_rls_group2;
 SELECT * FROM z1 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (gtpg MATCH (z : z1) WHERE f_leak(z.b) COLUMNS (z.a, z.b));
 
 EXPLAIN (COSTS OFF) EXECUTE plancache_test;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
 EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test2;
+EXPLAIN (COSTS OFF) EXECUTE plancache_pg_test3;
 
 --
 -- Views should follow policy for view owner.
@@ -1085,35 +1246,48 @@ EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
 -- View and Table owner are the same.
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
 GRANT SELECT ON rls_view TO regress_rls_bob;
+GRANT SELECT ON vtpg TO regress_rls_bob;
 
 -- Query as role that is not owner of view or table.  Should return all records.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Query as view/table owner.  Should return all records.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 
 -- View and Table owners are different.
 SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
 GRANT SELECT ON rls_view TO regress_rls_alice;
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
+GRANT SELECT ON vtpg TO regress_rls_alice;
 
 -- Query as role that is not owner of view but is owner of table.
 -- Should return records based on view owner policies.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Query as role that is not owner of table but is owner of view.
 -- Should return records based on view owner policies.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
@@ -1123,10 +1297,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
 -- Query as role that is not the owner of the table or view with permissions.
 SET SESSION AUTHORIZATION regress_rls_bob;
 GRANT SELECT ON rls_view TO regress_rls_carol;
+GRANT SELECT ON vtpg TO regress_rls_carol;
 
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Policy requiring access to another table.
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -1138,11 +1315,15 @@ CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklis
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view; --fail - permission denied.
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
 
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view; --fail - permission denied.
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
 
 -- Query as role that is not owner of table but is owner of view with permissions.
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -1151,17 +1332,22 @@ GRANT SELECT ON z1_blacklist TO regress_rls_bob;
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Query as role that is not the owner of the table or view with permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 SET SESSION AUTHORIZATION regress_rls_alice;
 REVOKE SELECT ON z1_blacklist FROM regress_rls_bob;
 DROP POLICY p3 ON z1;
 
 SET SESSION AUTHORIZATION regress_rls_bob;
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 
 --
@@ -1171,47 +1357,66 @@ DROP VIEW rls_view;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE VIEW rls_view WITH (security_invoker) AS
     SELECT * FROM z1 WHERE f_leak(b);
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
 GRANT SELECT ON rls_view TO regress_rls_bob;
 GRANT SELECT ON rls_view TO regress_rls_carol;
+GRANT SELECT ON vtpg TO regress_rls_bob;
+GRANT SELECT ON vtpg TO regress_rls_carol;
 
 -- Query as table owner.  Should return all records.
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Queries as other users.
 -- Should return records based on current user's policies.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- View and table owners are different.
 SET SESSION AUTHORIZATION regress_rls_alice;
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 
 SET SESSION AUTHORIZATION regress_rls_bob;
 CREATE VIEW rls_view WITH (security_invoker) AS
     SELECT * FROM z1 WHERE f_leak(b);
+CREATE PROPERTY GRAPH vtpg VERTEX TABLES (rls_view KEY (a));
 GRANT SELECT ON rls_view TO regress_rls_alice;
 GRANT SELECT ON rls_view TO regress_rls_carol;
+GRANT SELECT ON vtpg TO regress_rls_alice;
+GRANT SELECT ON vtpg TO regress_rls_carol;
 
 -- Query as table owner.  Should return all records.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Queries as other users.
 -- Should return records based on current user's policies.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Policy requiring access to another table.
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -1221,11 +1426,15 @@ CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklis
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view; --fail - permission denied.
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
 
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view; --fail - permission denied.
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
 
 -- Query as role that is not owner of table but is owner of view with permissions.
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -1234,11 +1443,15 @@ GRANT SELECT ON z1_blacklist TO regress_rls_bob;
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 -- Query as role that is not the owner of the table or view without permissions.
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view; --fail - permission denied.
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b)); --fail - permission denied.
 
 -- Query as role that is not the owner of the table or view with permissions.
 SET SESSION AUTHORIZATION regress_rls_alice;
@@ -1247,8 +1460,11 @@ GRANT SELECT ON z1_blacklist TO regress_rls_carol;
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM rls_view;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (vtpg MATCH (r: rls_view) COLUMNS (r.a, r.b));
 
 SET SESSION AUTHORIZATION regress_rls_bob;
+DROP PROPERTY GRAPH vtpg;
 DROP VIEW rls_view;
 
 --
@@ -1257,7 +1473,9 @@ DROP VIEW rls_view;
 SET SESSION AUTHORIZATION regress_rls_alice;
 
 CREATE TABLE x1 (a int, b text, c text);
+CREATE PROPERTY GRAPH cstpg VERTEX TABLES (x1 KEY (a));
 GRANT ALL ON x1 TO PUBLIC;
+GRANT ALL ON cstpg TO PUBLIC;
 
 INSERT INTO x1 VALUES
     (1, 'abc', 'regress_rls_bob'),
@@ -1279,10 +1497,12 @@ ALTER TABLE x1 ENABLE ROW LEVEL SECURITY;
 
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+SELECT * FROM GRAPH_TABLE (cstpg MATCH (x : x1) WHERE f_leak(x.b) COLUMNS (x.a, x.b, x.c)) ORDER BY a ASC;
 UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
 
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+SELECT * FROM GRAPH_TABLE (cstpg MATCH (x : x1) WHERE f_leak(x.b) COLUMNS (x.a, x.b, x.c)) ORDER BY a ASC;
 UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
 DELETE FROM x1 WHERE f_leak(b) RETURNING *;
 
@@ -1292,8 +1512,10 @@ DELETE FROM x1 WHERE f_leak(b) RETURNING *;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE y1 (a int, b text);
 CREATE TABLE y2 (a int, b text);
+CREATE PROPERTY GRAPH svbtpg VERTEX TABLES (y1 KEY (a), y2 KEY (a));
 
 GRANT ALL ON y1, y2 TO regress_rls_bob;
+GRANT ALL ON svbtpg TO regress_rls_bob;
 
 CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);
 CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);
@@ -1310,7 +1532,10 @@ ALTER TABLE y2 ENABLE ROW LEVEL SECURITY;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE VIEW rls_sbv WITH (security_barrier) AS
     SELECT * FROM y1 WHERE f_leak(b);
+ALTER PROPERTY GRAPH svbtpg ADD VERTEX TABLES (rls_sbv KEY (a));
 EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (svbtpg MATCH (r: rls_sbv) WHERE r.a = 1 COLUMNS (r.a, r.b));
+ALTER PROPERTY GRAPH svbtpg DROP VERTEX TABLES (rls_sbv);
 DROP VIEW rls_sbv;
 
 -- Create view as role that does not own table.  RLS should be applied.
@@ -1331,12 +1556,16 @@ CREATE POLICY p3 ON y2 USING (a % 4 = 0);
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM y2 WHERE f_leak(b);
 EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
+SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak(y.b) COLUMNS (y.a, y.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak(y.b) COLUMNS (y.a, y.b));
 
 --
 -- Qual push-down of leaky functions, when not referring to table
 --
 SELECT * FROM y2 WHERE f_leak('abc');
 EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
+SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak('abc') COLUMNS (y.a, y.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (svbtpg MATCH (y: y2) WHERE f_leak('abc') COLUMNS (y.a, y.b));
 
 CREATE TABLE test_qual_pushdown (
     abc text
@@ -1357,12 +1586,17 @@ DROP TABLE test_qual_pushdown;
 --
 RESET SESSION AUTHORIZATION;
 
+DROP PROPERTY GRAPH itpg;
 DROP TABLE t1 CASCADE;
 
 CREATE TABLE t1 (a integer);
 
 GRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;
 
+CREATE PROPERTY GRAPH itpg VERTEX TABLES (t1 KEY(a));
+
+GRANT SELECT ON itpg TO regress_rls_bob, regress_rls_carol;
+
 CREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);
 CREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);
 
@@ -1371,30 +1605,37 @@ ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
 -- Prepare as regress_rls_bob
 SET ROLE regress_rls_bob;
 PREPARE role_inval AS SELECT * FROM t1;
+PREPARE role_inval_pg AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a));
 -- Check plan
 EXPLAIN (COSTS OFF) EXECUTE role_inval;
+EXPLAIN (COSTS OFF) EXECUTE role_inval_pg;
 
 -- Change to regress_rls_carol
 SET ROLE regress_rls_carol;
 -- Check plan- should be different
 EXPLAIN (COSTS OFF) EXECUTE role_inval;
+EXPLAIN (COSTS OFF) EXECUTE role_inval_pg;
 
 -- Change back to regress_rls_bob
 SET ROLE regress_rls_bob;
 -- Check plan- should be back to original
 EXPLAIN (COSTS OFF) EXECUTE role_inval;
+EXPLAIN (COSTS OFF) EXECUTE role_inval_pg;
 
 --
 -- CTE and RLS
 --
 RESET SESSION AUTHORIZATION;
+DROP PROPERTY GRAPH itpg;
 DROP TABLE t1 CASCADE;
 CREATE TABLE t1 (a integer, b text);
+CREATE PROPERTY GRAPH itpg VERTEX TABLES (t1 KEY(a));
 CREATE POLICY p1 ON t1 USING (a % 2 = 0);
 
 ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
 
 GRANT ALL ON t1 TO regress_rls_bob;
+GRANT ALL ON itpg TO regress_rls_bob;
 
 INSERT INTO t1 (SELECT x, public.fipshash(x::text) FROM generate_series(0,20) x);
 
@@ -1403,6 +1644,9 @@ SET SESSION AUTHORIZATION regress_rls_bob;
 WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
 EXPLAIN (COSTS OFF)
 WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+WITH cte1 AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) WHERE f_leak(t.b) COLUMNS (t.a, t.b))) SELECT * FROM cte1;
+EXPLAIN (COSTS OFF)
+WITH cte1 AS MATERIALIZED (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) WHERE f_leak(t.b) COLUMNS (t.a, t.b))) SELECT * FROM cte1;
 
 WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
 WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
@@ -1441,6 +1685,16 @@ CREATE TABLE t3 AS SELECT * FROM t1;
 SELECT * FROM t3;
 SELECT * INTO t4 FROM t1;
 SELECT * FROM t4;
+DROP TABLE t2, t3, t4;
+CREATE TABLE t2 (a integer, b text);
+INSERT INTO t2 (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b)));
+EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b)));
+SELECT * FROM t2;
+EXPLAIN (COSTS OFF) SELECT * FROM t2;
+CREATE TABLE t3 AS SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+SELECT * FROM t3;
+SELECT * INTO t4 FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+SELECT * FROM t4;
 
 --
 -- RLS with JOIN
@@ -1448,8 +1702,15 @@ SELECT * FROM t4;
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE TABLE blog (id integer, author text, post text);
 CREATE TABLE comment (blog_id integer, message text);
+CREATE PROPERTY GRAPH jtpg
+    VERTEX TABLES (blog KEY (id))
+    EDGE TABLES
+    (comment KEY (blog_id)
+     SOURCE KEY (blog_id) REFERENCES blog(id)
+     DESTINATION KEY (blog_id) REFERENCES blog(id));
 
 GRANT ALL ON blog, comment TO regress_rls_bob;
+GRANT ALL ON jtpg TO regress_rls_bob;
 
 CREATE POLICY blog_1 ON blog USING (id % 2 = 0);
 
@@ -1475,6 +1736,7 @@ SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
 -- Check Non-RLS JOIN with RLS.
 SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+SELECT id, author, message FROM GRAPH_TABLE (jtpg MATCH (b: blog)-[c: comment]->(b) COLUMNS (b.id AS id, b.author AS author, c.message AS message));
 
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE POLICY comment_1 ON comment USING (blog_id < 4);
@@ -1485,8 +1747,10 @@ SET SESSION AUTHORIZATION regress_rls_bob;
 -- Check RLS JOIN RLS
 SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
 SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+SELECT id, author, message FROM GRAPH_TABLE (jtpg MATCH (b: blog)-[c: comment]->(b) COLUMNS (b.id AS id, b.author AS author, c.message AS message));
 
 SET SESSION AUTHORIZATION regress_rls_alice;
+DROP PROPERTY GRAPH jtpg;
 DROP TABLE blog, comment;
 
 --
@@ -1495,25 +1759,34 @@ DROP TABLE blog, comment;
 RESET SESSION AUTHORIZATION;
 DROP POLICY p2 ON t1;
 ALTER TABLE t1 OWNER TO regress_rls_alice;
+GRANT SELECT ON itpg TO regress_rls_alice;
 
 -- Check that default deny does not apply to superuser.
 RESET SESSION AUTHORIZATION;
 SELECT * FROM t1;
 EXPLAIN (COSTS OFF) SELECT * FROM t1;
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
 
 -- Check that default deny does not apply to table owner.
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM t1;
 EXPLAIN (COSTS OFF) SELECT * FROM t1;
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
 
 -- Check that default deny applies to non-owner/non-superuser when RLS on.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO ON;
 SELECT * FROM t1;
 EXPLAIN (COSTS OFF) SELECT * FROM t1;
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
 SET SESSION AUTHORIZATION regress_rls_bob;
 SELECT * FROM t1;
 EXPLAIN (COSTS OFF) SELECT * FROM t1;
+SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
+EXPLAIN (COSTS OFF) SELECT * FROM GRAPH_TABLE (itpg MATCH (t: t1) COLUMNS (t.a, t.b));
 
 --
 -- COPY TO/FROM
@@ -1522,11 +1795,13 @@ EXPLAIN (COSTS OFF) SELECT * FROM t1;
 RESET SESSION AUTHORIZATION;
 DROP TABLE copy_t CASCADE;
 CREATE TABLE copy_t (a integer, b text);
+CREATE PROPERTY GRAPH ctpg VERTEX TABLES (copy_t KEY (a));
 CREATE POLICY p1 ON copy_t USING (a % 2 = 0);
 
 ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;
 
 GRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;
+GRANT ALL ON ctpg TO regress_rls_bob, regress_rls_exempt_user;
 
 INSERT INTO copy_t (SELECT x, public.fipshash(x::text) FROM generate_series(0,10) x);
 
@@ -1534,29 +1809,37 @@ INSERT INTO copy_t (SELECT x, public.fipshash(x::text) FROM generate_series(0,10
 RESET SESSION AUTHORIZATION;
 SET row_security TO OFF;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 
 -- Check COPY TO as user with permissions.
 SET SESSION AUTHORIZATION regress_rls_bob;
 SET row_security TO OFF;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 
 -- Check COPY TO as user with permissions and BYPASSRLS
 SET SESSION AUTHORIZATION regress_rls_exempt_user;
 SET row_security TO OFF;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 
 -- Check COPY TO as user without permissions. SET row_security TO OFF;
 SET SESSION AUTHORIZATION regress_rls_carol;
 SET row_security TO OFF;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 SET row_security TO ON;
 COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
+COPY (SELECT * FROM GRAPH_TABLE (ctpg MATCH (c: copy_t) COLUMNS (c.a, c.b)) ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
 
 -- Check COPY relation TO; keep it just one row to avoid reordering issues
 RESET SESSION AUTHORIZATION;
@@ -1674,6 +1957,7 @@ SET row_security TO ON;
 COPY copy_t FROM STDIN; --fail - permission denied.
 
 RESET SESSION AUTHORIZATION;
+DROP PROPERTY GRAPH ctpg;
 DROP TABLE copy_t;
 DROP TABLE copy_rel_to CASCADE;
 
@@ -1769,12 +2053,15 @@ SELECT attname, most_common_vals FROM pg_stats
 --
 BEGIN;
 CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
+CREATE PROPERTY GRAPH cltpg VERTEX TABLES (coll_t KEY (c));
 CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
 ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
 GRANT SELECT ON coll_t TO regress_rls_alice;
+GRANT SELECT ON cltpg TO regress_rls_alice;
 SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;
 SET SESSION AUTHORIZATION regress_rls_alice;
 SELECT * FROM coll_t;
+SELECT * FROM GRAPH_TABLE (cltpg MATCH (c: coll_t) COLUMNS (c.c));
 ROLLBACK;
 
 --
@@ -1837,8 +2124,9 @@ CREATE TABLE r1 (a int);
 CREATE TABLE r2 (a int);
 INSERT INTO r1 VALUES (10), (20);
 INSERT INTO r2 VALUES (10), (20);
+CREATE PROPERTY GRAPH trtpg VERTEX TABLES (r2 KEY (a));
 
-GRANT ALL ON r1, r2 TO regress_rls_bob;
+GRANT ALL ON r1, r2, trtpg TO regress_rls_bob;
 
 CREATE POLICY p1 ON r1 USING (true);
 ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
@@ -1864,8 +2152,14 @@ UPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK
 DELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK
 SELECT * FROM r1;
 SELECT * FROM r2;
+INSERT INTO r1 SELECT a FROM GRAPH_TABLE (trtpg MATCH (r: r2) COLUMNS (r.a)) r2 RETURNING *; -- OK
+UPDATE r1 SET a = r2.a + 2 FROM GRAPH_TABLE (trtpg MATCH (r: r2) COLUMNS (r.a)) r2 WHERE r1.a = r2.a RETURNING *; -- OK
+DELETE FROM r1 USING GRAPH_TABLE (trtpg MATCH (r: r2) COLUMNS (r.a)) r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK
+SELECT * FROM r1;
+SELECT * FROM r2;
 
 SET SESSION AUTHORIZATION regress_rls_alice;
+DROP PROPERTY GRAPH trtpg;
 DROP TABLE r1;
 DROP TABLE r2;
 
@@ -1875,6 +2169,7 @@ DROP TABLE r2;
 SET SESSION AUTHORIZATION regress_rls_alice;
 SET row_security = on;
 CREATE TABLE r1 (a int);
+CREATE PROPERTY GRAPH frlstpg VERTEX TABLES (r1 KEY (a));
 INSERT INTO r1 VALUES (10), (20);
 
 CREATE POLICY p1 ON r1 USING (false);
@@ -1883,6 +2178,7 @@ ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
 
 -- No error, but no rows
 TABLE r1;
+SELECT * FROM GRAPH_TABLE (frlstpg MATCH (r: r1) COLUMNS (r.a));
 
 -- RLS error
 INSERT INTO r1 VALUES (1);
@@ -1898,9 +2194,11 @@ TABLE r1;
 SET row_security = off;
 -- these all fail, would be affected by RLS
 TABLE r1;
+SELECT * FROM GRAPH_TABLE (frlstpg MATCH (r: r1) COLUMNS (r.a));
 UPDATE r1 SET a = 1;
 DELETE FROM r1;
 
+DROP PROPERTY GRAPH frlstpg;
 DROP TABLE r1;
 
 --
@@ -2166,9 +2464,11 @@ DROP TABLE ref_tbl;
 CREATE TABLE rls_tbl (a int);
 INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
 ANALYZE rls_tbl;
+CREATE PROPERTY GRAPH lotpg VERTEX TABLES (rls_tbl KEY (a));
 
 ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
 GRANT SELECT ON rls_tbl TO regress_rls_alice;
+GRANT SELECT ON lotpg TO regress_rls_alice;
 
 SET SESSION AUTHORIZATION regress_rls_alice;
 CREATE FUNCTION op_leak(int, int) RETURNS bool
@@ -2177,9 +2477,12 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
 SELECT * FROM rls_tbl WHERE a <<< 1000;
+SELECT * FROM GRAPH_TABLE (lotpg MATCH (r: rls_tbl) WHERE r.a <<< 1000 COLUMNS (r.a));
+SELECT * FROM GRAPH_TABLE (lotpg MATCH (r: rls_tbl WHERE r.a <<< 1000) COLUMNS (r.a));
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
+DROP PROPERTY GRAPH lotpg;
 DROP TABLE rls_tbl;
 
 -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
-- 
2.34.1

---

From: Ashutosh Bapat <[email-masked]>
Date: Thu, 5 Dec 2024 16:38:35 +0530

> relation. And somehow redirect them to use \dG instead.

\d+ propgraph prints the definition of property graph. I find \dG
similar to \di which doesn't print the structure of an index. Instead
\d+ <index name> prints it.

In the attached patch series I have added patch 0008 to remove
unnecessary header
> >  Column | Type

It still prints an extra line but I haven't found a way to eliminate
it. Hence 0008 is WIP. I have listed TODOs in the commit message of
that patch.

> Will fix that in the next set.

Done. The fix is part of 0001 now.

> > I will continue to review this feature from the user's perspective.  Thank you for working on it, I am very excited to get this in.

here's patchset rebased on 792b2c7e6d926e61e8ff3b33d3e22d7d74e7a437
with conflicts in rowsecurity.sql/out fixed.

> 0007 - has RLS tests. It is the same as 0006 from the previous patch set.

This is same as the previous patchset.

> we should add a dependency on the operator. I will look into this

0006 in the attached patch series completes this work. Now we find the
equality operators to be used for vertex-edge quals and save it in
pg_propgraph_element catalog and also add a dependency between the
edge element and the equality operators.

0008 - has WIP fix for \d and \d+

--
Best Wishes,
Ashutosh Bapat

---

From: Ashutosh Bapat <[email-masked]>
Date: Thu, 5 Dec 2024 17:12:11 +0530

Sorry, I forgot to attach patches. PFA the patches.

-- 
Best Wishes,
Ashutosh Bapat
From fb082c59a2a84a21300efa9303f999762d9d9f55 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 21 Aug 2024 19:41:48 +0530
Subject: [PATCH 4/8] Fixes following issues

Additional changes

Author: Ashutosh Bapat
Reported By: Ajay Pal
---

 

 

 
+	/*

 /*

 
 
 	return (Node *) result;
 }

 	return (Node *) result;
 }

 

 
 		if (gep->quantifier)
 
-		/*

 		/*

 		prev_pf = pf;

 

 			/*

 

 	/*
 
 	ReleaseSysCache(eletup);

 

 

 
+-- GRAPH_TABLE in UDFs
 
 -- Errors

+-- GRAPH_TABLE in UDFs

From 4a5f7db0a9f96a8ddafd72ed25c7aaec77f005a6 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 8 Jul 2024 11:53:25 +0530
Subject: [PATCH 2/8] support WHERE clause in graph pattern

---

 

 

 

 
 -- Errors

From fbc6227e04bf6d1f7740b54b9df6a354a462a998 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 1 Aug 2024 10:52:35 +0530
Subject: [PATCH 3/8] Support cyclic path pattern.

Author: Ashutosh Bapat
---

+/*

 

 
+	/*

-	foreach(lc, gep_elems)
 
 		/*

 

-			/*
 
 			/*

-		if (gep->whereClause)
+		if (pf->whereClause)
 		{
 			Node	   *tr;

 

 
 
 	if (!eletup)

 
 	/*

 
 	ReleaseSysCache(eletup);
 
-	return gpe;
+	return pe;
 }
 
 static const char *
 
 		label_oids = NIL;

 		ScanKeyInit(&key[0],
 
-				if (gpe)
+				if (pe)
 				{

 		systable_endscan(scan);
 	}

 static const char *

 
 
 				ReleaseSysCache(tup);
 			}

 				if (!n)
 				{
 
 		if (!n)
 
 		return n;
 	}

 
 
 -- Errors

From a1f611f1b92cb2fd6ce10c6634b4a8fbb08c6851 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 17 Oct 2024 14:59:26 +0530
Subject: [PATCH 5/8] Access permissions on property graph

Note:

 
  <refsect1>

 

+--
+-- Property graphs
+--
 
 -- clean up

 drop sequence x_seq;
 
+drop view atpgv1;
+drop view atpgv2;

From b37588e7716d27561eb7ccf8a86f4df33ef8bcf2 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Thu, 27 Jun 2024 14:10:44 +0200
Subject: [PATCH 1/8] WIP: SQL Property Graph Queries (SQL/PGQ)

index bf3cee08a93..0b7a4dc5f9d 100644
 
+     <row>
@@ -2131,7 +2156,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
@@ -6289,6 +6315,449 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
  </sect1>

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index dea04d64db6..f89c6da0a74 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1985,7 +1985,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <para>
        Allows <command>SELECT</command> from
        any column, or specific column(s), of a table, view, materialized
-       view, or other table-like object.
+       view, property graph, or other table-like object.
        Also allows use of <command>COPY TO</command>.
        This privilege is also needed to reference existing column values in
        <command>UPDATE</command>, <command>DELETE</command>,
@@ -5323,6 +5323,229 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>

 
  <para>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8b81106fa23..98e39c25b00 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26082,6 +26082,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        </para></entry>
       </row>
 
+      <row>

 
+    <para>
 
  </sect1>

 
         <listitem>
         <para>

         <listitem>
         <para>

+     <varlistentry>
 
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_PROPGRAPH:
 
 			/*
index 2afc550540c..41de86e6bfb 100644

 

 		case OBJECT_COLUMN:

index 6ccae4cb4a8..a0f0ce5da63 100644

+		case OBJECT_PROPGRAPH:

index 50705a1e158..373fa1eec2f 100644
 
+		case RTE_GRAPH_TABLE:
index 2ebd938f6bd..4ef7b127dcd 100644
@@ -1355,6 +1355,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
@@ -2425,6 +2429,10 @@ replace_vars_in_jointree(Node *jtnode,
index 3864a675d2a..b2adf38dd44 100644
 
 		/*
 
-		/*
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
 
-			/*

 
-			/*
 
-			/* emit results */
 
-			/*

-				/*

-			/*
 
-		return (Node *) op;
+		/*

 
 /*

 

 

 	QUOTE QUOTES

 

 
 
 qual_Op:	Op
 
+
index 04b4596a65a..e15ce41a9be 100644

 			/*
 
+/*
index 2874731c352..10100202a9d 100644

 
 			/*
 
 			/*
 
 			/*
 
+ParseNamespaceItem *
 
 			/*
 
 			/*
index 72404e72fff..9748c7b105b 100644
 
 /*

+{right_arrow}	{
+					SET_YYLLOC();
+					return RIGHT_ARROW;
+				}
+
 {self}			{
 					SET_YYLLOC();
 					return yytext[0];

 OBJS = \
 	rewriteDefine.o \
+	rewriteGraphTable.o \
 	rewriteHandler.o \
 	rewriteManip.o \
 	rewriteRemove.o \
 
index ab2e2cd6476..a2fd6483033 100644
@@ -2041,6 +2042,16 @@ fireRIRrules(Query *parsetree, List *activeRIRs)

+		/*

 
 
+/*
 
+
+/*
 
 			/*

 	return subname;
 }
+
+char *
 
index 59e7941a0c3..aff6c87d8b4 100644
@@ -3645,6 +3645,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te)
index ec0cdf4ed74..2012455a4ef 100644
@@ -1699,10 +1699,10 @@ expand_table_name_patterns(Archive *fout,
@@ -2869,6 +2869,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
 
@@ -6987,7 +6990,8 @@ getTables(Archive *fout, int *numTables)
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15973,8 +15977,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
 		reltypename = "VIEW";
 
@@ -16000,6 +16002,47 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
@@ -16075,8 +16118,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
@@ -16810,6 +16851,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
@@ -18749,6 +18792,16 @@ getDependencies(Archive *fout)
 
+	/*

index 2657abdc72d..a26d81a0fde 100644
@@ -1051,6 +1051,7 @@ permissionsList(const char *pattern, bool showSystem)
@@ -1061,6 +1062,7 @@ permissionsList(const char *pattern, bool showSystem)
 
@@ -1152,6 +1154,7 @@ permissionsList(const char *pattern, bool showSystem)
 
@@ -2031,6 +2034,10 @@ describeOneTableDetails(const char *schemaname,
@@ -3130,6 +3137,32 @@ describeOneTableDetails(const char *schemaname,
 		}
 	}
 
@@ -3983,6 +4016,7 @@ describeRoleGrants(const char *pattern, bool showSystem)
@@ -3994,6 +4028,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	PQExpBufferData buf;
 	PGresult   *res;
@@ -4002,8 +4037,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	bool		translate_columns[] = {false, false, true, false, false, false, false, false, false};

 	initPQExpBuffer(&buf);
 
@@ -4020,6 +4055,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
@@ -4033,6 +4069,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
@@ -4120,6 +4157,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
index 3f4afc2d141..e2ae82987ac 100644
+	HELP0("  \\dG[S+] [PATTERN]      list property graphs\n");
index bbd08770c3d..b63ac625371 100644

 /* All relations */

 
 /* CREATE PUBLICATION */

+/* GRAPH_TABLE */

 /*

+{right_arrow}	{
+					ECHO;
+				}
+
 	/*

 
 
index 9575524007f..cd5379e8fd9 100644

+/*
 
+/*
 
+	/*

+/*

 #endif							/* ANALYZE_H */

 
 
index 82708013ee6..e4831fdcc19 100644
 
 /*

+{right_arrow}	{
+					return RIGHT_ARROW;
+				}
+
 {informix_special} {
 
 						/*

 /*
 
 # ----------

+--
+-- Property Graph
+--
index ce33e55bf1d..89b7bcbbd07 100644
@@ -4190,3 +4190,24 @@ yyscan_t

From 1434f99f909d5e9adba5f45b0109027a98f930fa Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 5 Dec 2024 14:31:36 +0530
Subject: [PATCH 8/8] WIP: Do not print empty columns table for a property
 graph

A property graph does not have any columns by itself. Thus it doesn't
need the table describing columns in \d[+] output.

The patch needs following work
1. Move the code to collect column information in a separate function,
   which can be skipped for property graphs. This avoids one indentation
   level and makes describeOneTableDetails() easy to follow.
2. Even though the borders are suppressed there is an extra blank line
   in \d output. Avoid that. It requires some adjustment to
   printTableOpt or some minion of printTable to avoid printing
   everything related to column description table.

   OR

If we decide that we want to output a different description (like
sequences) we need to decide what that description should be and then
implement the same.

Ashutosh Bapat
---
 src/bin/psql/describe.c                       | 307 +++++++++---------
 .../expected/create_property_graph.out        |   6 +-
 2 files changed, 164 insertions(+), 149 deletions(-)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index a26d81a0fde..66a3a3395e4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1857,128 +1857,6 @@ describeOneTableDetails(const char *schemaname,
 		goto error_return;		/* not an error, just return early */
 	}
 
-	/* Identify whether we should print collation, nullable, default vals */
-	if (tableinfo.relkind == RELKIND_RELATION ||
-		tableinfo.relkind == RELKIND_VIEW ||
-		tableinfo.relkind == RELKIND_MATVIEW ||
-		tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
-		tableinfo.relkind == RELKIND_COMPOSITE_TYPE ||
-		tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
-		show_column_details = true;
-
-	/*
-	 * Get per-column info
-	 *
-	 * Since the set of query columns we need varies depending on relkind and
-	 * server version, we compute all the column numbers on-the-fly.  Column
-	 * number variables for columns not fetched are left as -1; this avoids
-	 * duplicative test logic below.
-	 */
-	cols = 0;
-	printfPQExpBuffer(&buf, "SELECT a.attname");
-	attname_col = cols++;
-	appendPQExpBufferStr(&buf, ",\n  pg_catalog.format_type(a.atttypid, a.atttypmod)");
-	atttype_col = cols++;
-
-	if (show_column_details)
-	{
-		/* use "pretty" mode for expression to avoid excessive parentheses */
-		appendPQExpBufferStr(&buf,
-							 ",\n  (SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid, true)"
-							 "\n   FROM pg_catalog.pg_attrdef d"
-							 "\n   WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)"
-							 ",\n  a.attnotnull");
-		attrdef_col = cols++;
-		attnotnull_col = cols++;
-		appendPQExpBufferStr(&buf, ",\n  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n"
-							 "   WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation");
-		attcoll_col = cols++;
-		if (pset.sversion >= 100000)
-			appendPQExpBufferStr(&buf, ",\n  a.attidentity");
-		else
-			appendPQExpBufferStr(&buf, ",\n  ''::pg_catalog.char AS attidentity");
-		attidentity_col = cols++;
-		if (pset.sversion >= 120000)
-			appendPQExpBufferStr(&buf, ",\n  a.attgenerated");
-		else
-			appendPQExpBufferStr(&buf, ",\n  ''::pg_catalog.char AS attgenerated");
-		attgenerated_col = cols++;
-	}
-	if (tableinfo.relkind == RELKIND_INDEX ||
-		tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
-	{
-		if (pset.sversion >= 110000)
-		{
-			appendPQExpBuffer(&buf, ",\n  CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i WHERE i.indexrelid = '%s') THEN '%s' ELSE '%s' END AS is_key",
-							  oid,
-							  gettext_noop("yes"),
-							  gettext_noop("no"));
-			isindexkey_col = cols++;
-		}
-		appendPQExpBufferStr(&buf, ",\n  pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
-		indexdef_col = cols++;
-	}
-	/* FDW options for foreign table column */
-	if (tableinfo.relkind == RELKIND_FOREIGN_TABLE)
-	{
-		appendPQExpBufferStr(&buf, ",\n  CASE WHEN attfdwoptions IS NULL THEN '' ELSE "
-							 "  '(' || pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value)  FROM "
-							 "  pg_catalog.pg_options_to_table(attfdwoptions)), ', ') || ')' END AS attfdwoptions");
-		fdwopts_col = cols++;
-	}
-	if (verbose)
-	{
-		appendPQExpBufferStr(&buf, ",\n  a.attstorage");
-		attstorage_col = cols++;
-
-		/* compression info, if relevant to relkind */
-		if (pset.sversion >= 140000 &&
-			!pset.hide_compression &&
-			(tableinfo.relkind == RELKIND_RELATION ||
-			 tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
-			 tableinfo.relkind == RELKIND_MATVIEW))
-		{
-			appendPQExpBufferStr(&buf, ",\n  a.attcompression AS attcompression");
-			attcompression_col = cols++;
-		}
-
-		/* stats target, if relevant to relkind */
-		if (tableinfo.relkind == RELKIND_RELATION ||
-			tableinfo.relkind == RELKIND_INDEX ||
-			tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
-			tableinfo.relkind == RELKIND_MATVIEW ||
-			tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
-			tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
-		{
-			appendPQExpBufferStr(&buf, ",\n  CASE WHEN a.attstattarget=-1 THEN NULL ELSE a.attstattarget END AS attstattarget");
-			attstattarget_col = cols++;
-		}
-
-		/*
-		 * In 9.0+, we have column comments for: relations, views, composite
-		 * types, and foreign tables (cf. CommentObject() in comment.c).
-		 */
-		if (tableinfo.relkind == RELKIND_RELATION ||
-			tableinfo.relkind == RELKIND_VIEW ||
-			tableinfo.relkind == RELKIND_MATVIEW ||
-			tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
-			tableinfo.relkind == RELKIND_COMPOSITE_TYPE ||
-			tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
-		{
-			appendPQExpBufferStr(&buf, ",\n  pg_catalog.col_description(a.attrelid, a.attnum)");
-			attdescr_col = cols++;
-		}
-	}
-
-	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_attribute a");
-	appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid);
-	appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;");
-
-	res = PSQLexec(buf.data);
-	if (!res)
-		goto error_return;
-	numrows = PQntuples(res);
-
 	/* Make title */
 	switch (tableinfo.relkind)
 	{
@@ -2045,32 +1923,171 @@ describeOneTableDetails(const char *schemaname,
 			break;
 	}
 
-	/* Fill headers[] with the names of the columns we will output */
+	/* Identify whether we should print collation, nullable, default vals */
+	if (tableinfo.relkind == RELKIND_RELATION ||
+		tableinfo.relkind == RELKIND_VIEW ||
+		tableinfo.relkind == RELKIND_MATVIEW ||
+		tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
+		tableinfo.relkind == RELKIND_COMPOSITE_TYPE ||
+		tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+		show_column_details = true;
+
+	/*
+	 * Get per-column info
+	 *
+	 * Since the set of query columns we need varies depending on relkind and
+	 * server version, we compute all the column numbers on-the-fly.  Column
+	 * number variables for columns not fetched are left as -1; this avoids
+	 * duplicative test logic below. Property graph relations do not have
+	 * columns.
+	 */
 	cols = 0;
-	headers[cols++] = gettext_noop("Column");
-	headers[cols++] = gettext_noop("Type");
-	if (show_column_details)
+	numrows = 0;
+	if (tableinfo.relkind != RELKIND_PROPGRAPH)
 	{
-		headers[cols++] = gettext_noop("Collation");
-		headers[cols++] = gettext_noop("Nullable");
-		headers[cols++] = gettext_noop("Default");
+		printfPQExpBuffer(&buf, "SELECT a.attname");
+		attname_col = cols++;
+		appendPQExpBufferStr(&buf, ",\n  pg_catalog.format_type(a.atttypid, a.atttypmod)");
+		atttype_col = cols++;
+
+		if (show_column_details)
+		{
+			/* use "pretty" mode for expression to avoid excessive parentheses */
+			appendPQExpBufferStr(&buf,
+								 ",\n  (SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid, true)"
+								 "\n   FROM pg_catalog.pg_attrdef d"
+								 "\n   WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)"
+								 ",\n  a.attnotnull");
+			attrdef_col = cols++;
+			attnotnull_col = cols++;
+			appendPQExpBufferStr(&buf, ",\n  (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n"
+								 "   WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation");
+			attcoll_col = cols++;
+			if (pset.sversion >= 100000)
+				appendPQExpBufferStr(&buf, ",\n  a.attidentity");
+			else
+				appendPQExpBufferStr(&buf, ",\n  ''::pg_catalog.char AS attidentity");
+			attidentity_col = cols++;
+			if (pset.sversion >= 120000)
+				appendPQExpBufferStr(&buf, ",\n  a.attgenerated");
+			else
+				appendPQExpBufferStr(&buf, ",\n  ''::pg_catalog.char AS attgenerated");
+			attgenerated_col = cols++;
+		}
+		if (tableinfo.relkind == RELKIND_INDEX ||
+			tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
+		{
+			if (pset.sversion >= 110000)
+			{
+				appendPQExpBuffer(&buf, ",\n  CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i WHERE i.indexrelid = '%s') THEN '%s' ELSE '%s' END AS is_key",
+								  oid,
+								  gettext_noop("yes"),
+								  gettext_noop("no"));
+				isindexkey_col = cols++;
+			}
+			appendPQExpBufferStr(&buf, ",\n  pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
+			indexdef_col = cols++;
+		}
+		/* FDW options for foreign table column */
+		if (tableinfo.relkind == RELKIND_FOREIGN_TABLE)
+		{
+			appendPQExpBufferStr(&buf, ",\n  CASE WHEN attfdwoptions IS NULL THEN '' ELSE "
+								 "  '(' || pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(option_name) || ' ' || pg_catalog.quote_literal(option_value)  FROM "
+								 "  pg_catalog.pg_options_to_table(attfdwoptions)), ', ') || ')' END AS attfdwoptions");
+			fdwopts_col = cols++;
+		}
+		if (verbose)
+		{
+			appendPQExpBufferStr(&buf, ",\n  a.attstorage");
+			attstorage_col = cols++;
+
+			/* compression info, if relevant to relkind */
+			if (pset.sversion >= 140000 &&
+				!pset.hide_compression &&
+				(tableinfo.relkind == RELKIND_RELATION ||
+				 tableinfo.relkind == RELKIND_PARTITIONED_TABLE ||
+				 tableinfo.relkind == RELKIND_MATVIEW))
+			{
+				appendPQExpBufferStr(&buf, ",\n  a.attcompression AS attcompression");
+				attcompression_col = cols++;
+			}
+
+			/* stats target, if relevant to relkind */
+			if (tableinfo.relkind == RELKIND_RELATION ||
+				tableinfo.relkind == RELKIND_INDEX ||
+				tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
+				tableinfo.relkind == RELKIND_MATVIEW ||
+				tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
+				tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				appendPQExpBufferStr(&buf, ",\n  CASE WHEN a.attstattarget=-1 THEN NULL ELSE a.attstattarget END AS attstattarget");
+				attstattarget_col = cols++;
+			}
+
+			/*
+			 * In 9.0+, we have column comments for: relations, views,
+			 * composite types, and foreign tables (cf. CommentObject() in
+			 * comment.c).
+			 */
+			if (tableinfo.relkind == RELKIND_RELATION ||
+				tableinfo.relkind == RELKIND_VIEW ||
+				tableinfo.relkind == RELKIND_MATVIEW ||
+				tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
+				tableinfo.relkind == RELKIND_COMPOSITE_TYPE ||
+				tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				appendPQExpBufferStr(&buf, ",\n  pg_catalog.col_description(a.attrelid, a.attnum)");
+				attdescr_col = cols++;
+			}
+		}
+
+		appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_attribute a");
+		appendPQExpBuffer(&buf, "\nWHERE a.attrelid = '%s' AND a.attnum > 0 AND NOT a.attisdropped", oid);
+		appendPQExpBufferStr(&buf, "\nORDER BY a.attnum;");
+
+		res = PSQLexec(buf.data);
+		if (!res)
+			goto error_return;
+		numrows = PQntuples(res);
+
+		/* Fill headers[] with the names of the columns we will output */
+		cols = 0;
+		if (tableinfo.relkind != RELKIND_PROPGRAPH)
+		{
+			/* A property graph does not project any columns by itself. */
+			headers[cols++] = gettext_noop("Column");
+			headers[cols++] = gettext_noop("Type");
+		}
+		if (show_column_details)
+		{
+			headers[cols++] = gettext_noop("Collation");
+			headers[cols++] = gettext_noop("Nullable");
+			headers[cols++] = gettext_noop("Default");
+		}
+		if (isindexkey_col >= 0)
+			headers[cols++] = gettext_noop("Key?");
+		if (indexdef_col >= 0)
+			headers[cols++] = gettext_noop("Definition");
+		if (fdwopts_col >= 0)
+			headers[cols++] = gettext_noop("FDW options");
+		if (attstorage_col >= 0)
+			headers[cols++] = gettext_noop("Storage");
+		if (attcompression_col >= 0)
+			headers[cols++] = gettext_noop("Compression");
+		if (attstattarget_col >= 0)
+			headers[cols++] = gettext_noop("Stats target");
+		if (attdescr_col >= 0)
+			headers[cols++] = gettext_noop("Description");
+
+		Assert(cols <= lengthof(headers));
 	}
-	if (isindexkey_col >= 0)
-		headers[cols++] = gettext_noop("Key?");
-	if (indexdef_col >= 0)
-		headers[cols++] = gettext_noop("Definition");
-	if (fdwopts_col >= 0)
-		headers[cols++] = gettext_noop("FDW options");
-	if (attstorage_col >= 0)
-		headers[cols++] = gettext_noop("Storage");
-	if (attcompression_col >= 0)
-		headers[cols++] = gettext_noop("Compression");
-	if (attstattarget_col >= 0)
-		headers[cols++] = gettext_noop("Stats target");
-	if (attdescr_col >= 0)
-		headers[cols++] = gettext_noop("Description");
-
-	Assert(cols <= lengthof(headers));
+
+	/*
+	 * Like a property graph, if the relation doesn't have any columns,
+	 * suppress borders.
+	 */
+	if (cols <= 0 && numrows <= 0)
+		myopt.border = 0;
 
 	printTableInit(&cont, &myopt, title.data, cols, numrows);
 	printTableInitialized = true;
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index c3f072944cd..6db9e15a662 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -654,13 +654,11 @@ ERROR:  "pg_type" is not a property graph
 -- TODO
 \d g1
 Property graph "create_property_graph_tests.g1"
- Column | Type 
---------+------
+
 
 \d+ g1
 Property graph "create_property_graph_tests.g1"
- Column | Type | Storage 
---------+------+---------
+
 Property graph definition:
  CREATE PROPERTY GRAPH create_property_graph_tests.g1
 
-- 
2.34.1

From 46f9ccb5f931dcc296e30f09467046f5ced60ca4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 28 Oct 2024 16:43:40 +0530
Subject: [PATCH 7/8] RLS tests

index fd5654df35e..2e0bdaf70ca 100644
 
 \d document

 

 

 

 

 
 
+-- label disjunction

 

 

 

 

 

 

 
+EXECUTE ppg1(2);
+ id  | a |  b  
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+(6 rows)
+

+EXECUTE ppg2(2);
+ id  | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+

 

 

 

 

 

 

 

 

 

 

 

 
 
---
--- Check INSERT SELECT
---

 

 

 

@@ -4501,9 +6421,20 @@ EXPLAIN (COSTS OFF) SELECT * FROM rls_tbl WHERE a <<< 1000 or a <<< 900;
    One-Time Filter: false
 (2 rows)
 
@@ -4671,29 +6602,38 @@ DROP TABLE rls_t;
index cf09f62eaba..9922988f259 100644

 
 -- try a sampled version
 
 -- try a sampled version

 

 

 

 

 
 SELECT * FROM t1;

 

 

 
 
 -- pp1 ERROR

 

 

 

 

 

 

 

 

 

 

 INSERT INTO z1 VALUES

 

 

 

 

 

 

 

 

 

 

 

 

 

 INSERT INTO x1 VALUES

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
 
 -- No error, but no rows
 TABLE r1;
 
 -- RLS error

 
 
@@ -2178,9 +2478,12 @@ CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
 EXPLAIN (COSTS OFF) SELECT * FROM rls_tbl WHERE a <<< 1000 or a <<< 900;

From f7e3cfa4232bd856f21ab8b6391c880424a763c2 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 28 Oct 2024 16:36:27 +0530
Subject: [PATCH 6/8] Property collation and edge-vertex link support

1. collation of a property
--------------------------
The values with differing collations can not be collated. This means
that we will not able to compare, sort, or order values of properties
with the same name if they have different collations in different
elements or different labels.  This restricts the property's usage in
graph table query. Hence the collation of all the properties with the
same name needs to be the same. Enforce this.

called during transformSelectStmt() which is called before calling

Note: This is not explicitly specified in Section 9.15, "Consistency
check of a tabular property graph descriptor", syntax rule 4.c.iii.2 of
SQL/PGQ standard.

2. collation of source and destination keys of an edge
------------------------------------------------------
If collations of edge key and the corresponding referenced key differ,
an edge may end up being adjacent to undesired vertex. Prohibit such a
case when key columns are explicitly specified.

When edge and vertex keys are derived from the foreign key constraint,
we do not check collations again since the constraint itself ensures
one-to-many edge-to-vertex mapping.

3. Edge-vertex link quals
-----------------------------------------
When creating an edge element make sure that there exists an equality
operators that can be used to match vertex and edge keys. Fail the DDL if
such an operator does not exist. Use the same equality operators to build
vertex-edge quals in build_edge_vertex_link_quals(). Also add a
dependency of the edge on the equality operator so that it can not be
dropped without dropping the edge.

Note for reviewers:
The code in ATAddForeignKeyConstraint() looks up three different
equality operators and it's difficult to separate the code looking up
just PK=FK operators. So could not move some common code to a function
which can be used at both places.

4. Tests
--------

Author: Ashutosh Bapat
---
 doc/src/sgml/catalogs.sgml                    |  49 ++++
 src/backend/catalog/information_schema.sql    |   8 +-
 src/backend/commands/propgraphcmds.c          | 222 +++++++++++++--
 src/backend/nodes/nodeFuncs.c                 |   5 +-
 src/backend/parser/parse_collate.c            |   8 +-
 src/backend/parser/parse_graphtable.c         |  16 +-
 src/backend/rewrite/rewriteGraphTable.c       | 122 ++++++--
 src/include/catalog/pg_propgraph_element.h    |  11 +
 src/include/catalog/pg_propgraph_property.h   |   6 +
 src/include/nodes/primnodes.h                 |   2 +
 .../expected/create_property_graph.out        | 261 +++++++++++++++---
 src/test/regress/expected/graph_table.out     | 111 +++++++-
 src/test/regress/expected/oidjoins.out        |   1 +
 .../regress/sql/create_property_graph.sql     |  77 ++++++
 src/test/regress/sql/graph_table.sql          |  41 ++-
 15 files changed, 816 insertions(+), 124 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0b7a4dc5f9d..8c4c07f5a29 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6454,6 +6454,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrceqop</structfield> <type>oid[]</type>
+       (references <link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, an array of equality operators for
+       <structfield>pgesrcref</structfield> =
+       <structfield>pgesrckey</structfield> comparison. (Null for a vertex.)
+       </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>pgedestkey</structfield> <type>int2[]</type>
@@ -6482,6 +6494,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        and the destination vertex.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedesteqop</structfield> <type>oid[]</type>
+       (references <link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, an array of equality operators for
+       <structfield>pgedestref</structfield> =
+       <structfield>pgedestkey</structfield> comparison. (Null for a vertex.)
+       </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
@@ -6753,6 +6777,31 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        multiple times in different elements and labels.)
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgptypmod</structfield> <type>int4</type>
+      </para>
+      <para>
+      <literal>typmod</literal> to be applied to the data type of this property.
+      (This is required to be fixed for a given property in a property graph,
+      even if the property is defined multiple times in different elements and
+      labels.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgpcollation</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-collation"><structname>pg_collation</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The defined collation of this property, or zero if the property is not of
+       a collatable data type.  (This is required to be fixed for a given
+       property in a property graph, even if the property is defined multiple
+       times in different elements and labels.)
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index d6b08f99137..cc94339aa26 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -3271,9 +3271,9 @@ CREATE VIEW pg_property_data_types AS
            CAST(null AS sql_identifier) AS character_set_catalog,
            CAST(null AS sql_identifier) AS character_set_schema,
            CAST(null AS sql_identifier) AS character_set_name,
-           CAST(null AS sql_identifier) AS collation_catalog, -- FIXME
-           CAST(null AS sql_identifier) AS collation_schema, -- FIXME
-           CAST(null AS sql_identifier) AS collation_name, -- FIXME
+           CAST(current_database() AS sql_identifier) AS collation_catalog,
+           CAST(nc.nspname AS sql_identifier) AS collation_schema,
+           CAST(c.collname AS sql_identifier) AS collation_name,
            CAST(null AS cardinal_number) AS numeric_precision,
            CAST(null AS cardinal_number) AS numeric_precision_radix,
            CAST(null AS cardinal_number) AS numeric_scale,
@@ -3297,6 +3297,8 @@ CREATE VIEW pg_property_data_types AS
          JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON pgp.pgptypid = t.oid
          LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON (bt.typnamespace = nbt.oid))
            ON (t.typtype = 'd' AND t.typbasetype = bt.oid)
+         LEFT JOIN (pg_collation c JOIN pg_namespace nc ON (c.collnamespace = nc.oid))
+           ON pgp.pgpcollation = c.oid AND (nc.nspname, c.collname) <> ('pg_catalog', 'default')
 
     WHERE pg.relkind = 'g'
index 8430bfda7b7..70af8fb545f 100644
@@ -14,20 +14,27 @@
 
 #include "access/genam.h"
 #include "access/htup_details.h"
+#include "access/nbtree.h"
 #include "access/table.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_collation_d.h"
+#include "catalog/pg_operator_d.h"
 #include "catalog/pg_propgraph_element.h"
 #include "catalog/pg_propgraph_element_label.h"
 #include "catalog/pg_propgraph_label.h"
 #include "catalog/pg_propgraph_label_property.h"
 #include "catalog/pg_propgraph_property.h"
+#include "commands/defrem.h"
 #include "commands/propgraphcmds.h"
 #include "commands/tablecmds.h"
 #include "nodes/nodeFuncs.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "utils/array.h"
@@ -52,12 +59,14 @@ struct element_info
 	Oid			srcrelid;
 	ArrayType  *srckey;
 	ArrayType  *srcref;
+	ArrayType  *srceqop;
 
 	char	   *destvertex;
 	Oid			destvertexid;
 	Oid			destrelid;
 	ArrayType  *destkey;
 	ArrayType  *destref;
+	ArrayType  *desteqop;
 
 	List	   *labels;
 };
@@ -68,8 +77,8 @@ static ArrayType *propgraph_element_get_key(ParseState *pstate, const List *keyc
 static void propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
 										Relation edge_rel, Relation ref_rel,
 										const char *aliasname, int location, const char *type,
-										ArrayType **outkey, ArrayType **outref);
-static ArrayType *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
+										ArrayType **outkey, ArrayType **outref, ArrayType **outeqop);
+static AttrNumber *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
 static ArrayType *array_from_attnums(int numattrs, const AttrNumber *attnums);
 static Oid	insert_element_record(ObjectAddress pgaddress, struct element_info *einfo);
 static Oid	insert_label_record(Oid graphid, Oid peoid, const char *label);
@@ -217,10 +226,10 @@ CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt)
 
 		propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
 									einfo->aliasname, edge->location, "SOURCE",
-									&einfo->srckey, &einfo->srcref);
+									&einfo->srckey, &einfo->srcref, &einfo->srceqop);
 		propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
 									einfo->aliasname, edge->location, "DESTINATION",
-									&einfo->destkey, &einfo->destref);
+									&einfo->destkey, &einfo->destref, &einfo->desteqop);
 
 		einfo->labels = edge->labels;
 
@@ -336,7 +345,8 @@ propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation e
 	}
 	else
 	{
-		a = array_from_column_list(pstate, key_clause, location, element_rel);
+		a = array_from_attnums(list_length(key_clause),
+							   array_from_column_list(pstate, key_clause, location, element_rel));
 	}
 
 	return a;
@@ -360,8 +370,15 @@ static void
 propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
 							Relation edge_rel, Relation ref_rel,
 							const char *aliasname, int location, const char *type,
-							ArrayType **outkey, ArrayType **outref)
+							ArrayType **outkey, ArrayType **outref, ArrayType **outeqop)
 {
+	int			nkeys;
+	AttrNumber *keyattnums;
+	AttrNumber *refattnums;
+	Oid		   *keyeqops;
+	Datum	   *datums;
+	int			i;
+
 	Assert((keycols && refcols) || (!keycols && !refcols));
 
 	if (keycols)
@@ -372,8 +389,89 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 					errmsg("mismatching number of columns in %s vertex definition of edge \"%s\"", type, aliasname),
 					parser_errposition(pstate, location));
 
-		*outkey = array_from_column_list(pstate, keycols, location, edge_rel);
-		*outref = array_from_column_list(pstate, refcols, location, ref_rel);
+		nkeys = list_length(keycols);
+		keyattnums = array_from_column_list(pstate, keycols, location, edge_rel);
+		refattnums = array_from_column_list(pstate, refcols, location, ref_rel);
+		keyeqops = palloc_array(Oid, nkeys);
+
+		for (i = 0; i < nkeys; i++)
+		{
+			Oid			keytype;
+			int32		keytypmod;
+			Oid			keycoll;
+			Oid			reftype;
+			int32		reftypmod;
+			Oid			refcoll;
+			Oid			opc;
+			Oid			opf;
+			StrategyNumber strategy;
+
+			/*
+			 * Lookup equality operator to be used for edge and vertex key.
+			 * Vertex key is equivalent to primary key and edge key is similar
+			 * to foreign key since edge key references vertex key. Hence
+			 * vertex key is used as left operand and edge key is used as
+			 * right operand. The method used to find the equality operators
+			 * is similar to the method used to find equality operators for
+			 * FK/PK comparison in ATAddForeignKeyConstraint() except that
+			 * opclass of the the vertex key type is used as a starting point.
+			 * Since we need only equality operators we use both BT and HASH
+			 * strategies.
+			 *
+			 * If the required operators do not exist, we can not construct
+			 * quals linking an edge to its adjacent vertexes.
+			 */
+			get_atttypetypmodcoll(RelationGetRelid(edge_rel), keyattnums[i], &keytype, &keytypmod, &keycoll);
+			get_atttypetypmodcoll(RelationGetRelid(ref_rel), refattnums[i], &reftype, &reftypmod, &refcoll);
+			keyeqops[i] = InvalidOid;
+			strategy = BTEqualStrategyNumber;
+			opc = GetDefaultOpClass(reftype, BTREE_AM_OID);
+			if (!OidIsValid(opc))
+			{
+				opc = GetDefaultOpClass(reftype, HASH_AM_OID);
+				strategy = HTEqualStrategyNumber;
+			}
+			if (OidIsValid(opc))
+			{
+				opf = get_opclass_family(opc);
+				if (OidIsValid(opf))
+				{
+					keyeqops[i] = get_opfamily_member(opf, reftype, keytype, strategy);
+					if (!OidIsValid(keyeqops[i]))
+					{
+						/* Last resort, implicit cast. */
+						if (can_coerce_type(1, &keytype, &reftype, COERCION_IMPLICIT))
+							keyeqops[i] = get_opfamily_member(opf, reftype, reftype, strategy);
+					}
+				}
+			}
+
+			if (!OidIsValid(keyeqops[i]))
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("no equality operator exists for %s key comparison of edge \"%s\"",
+							   type, aliasname),
+						parser_errposition(pstate, location));
+
+			/*
+			 * If collations of key attribute and referenced attribute are
+			 * different, an edge may end up being adjacent to undesired
+			 * vertexes.  Prohibit such a case.
+			 *
+			 * PK/FK allows different collations as long as they are
+			 * deterministic for backward compatibility. But we can be a bit
+			 * stricter here and follow SQL standard.
+			 */
+			if (keycoll != refcoll &&
+				keycoll != DEFAULT_COLLATION_OID && refcoll != DEFAULT_COLLATION_OID &&
+				OidIsValid(keycoll) && OidIsValid(refcoll))
+				ereport(ERROR,
+						errcode(ERRCODE_SYNTAX_ERROR),
+						errmsg("collation mismatch in %s key of edge \"%s\": %s vs. %s",
+							   type, aliasname,
+							   get_collation_name(keycoll), get_collation_name(refcoll)),
+						parser_errposition(pstate, location));
+		}
 	}
 	else
 	{
@@ -404,25 +502,34 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 
 		Assert(fk);
 
-		*outkey = array_from_attnums(fk->nkeys, fk->conkey);
-		*outref = array_from_attnums(fk->nkeys, fk->confkey);
+		nkeys = fk->nkeys;
+		keyattnums = fk->conkey;
+		refattnums = fk->confkey;
+		keyeqops = fk->conpfeqop;
 	}
+
+	*outkey = array_from_attnums(nkeys, keyattnums);
+	*outref = array_from_attnums(nkeys, refattnums);
+	datums = (Datum *) palloc(sizeof(Datum) * nkeys);
+	for (i = 0; i < nkeys; i++)
+		datums[i] = ObjectIdGetDatum(keyeqops[i]);
+	*outeqop = construct_array_builtin(datums, nkeys, OIDOID);
 }
 
 /*

 	i = 0;
 	foreach(lc, colnames)
@@ -438,14 +545,14 @@ array_from_column_list(ParseState *pstate, const List *colnames, int location, R
 
@@ -453,7 +560,7 @@ array_from_column_list(ParseState *pstate, const List *colnames, int location, R
 		}
 	}

 static ArrayType *
@@ -486,6 +593,23 @@ array_of_attnums_to_objectaddrs(Oid relid, ArrayType *arr, ObjectAddresses *addr
 	}
 }
 
+static void
+array_of_opers_to_objectaddrs(ArrayType *arr, ObjectAddresses *addrs)
+{
+	Datum	   *opersd;
+	int			numopers;
+
+	deconstruct_array_builtin(arr, OIDOID, &opersd, NULL, &numopers);
+
+	for (int i = 0; i < numopers; i++)
+	{
+		ObjectAddress referenced;
+
+		ObjectAddressSet(referenced, OperatorRelationId, DatumGetObjectId(opersd[i]));
+		add_exact_object_address(&referenced, addrs);
+	}
+}
+
 /*
  * Insert a record for an element into the pg_propgraph_element catalog.  Also
  * inserts labels and properties into their respective catalogs.
@@ -526,6 +650,10 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 		values[Anum_pg_propgraph_element_pgesrcref - 1] = PointerGetDatum(einfo->srcref);
 	else
 		nulls[Anum_pg_propgraph_element_pgesrcref - 1] = true;
+	if (einfo->srceqop)
+		values[Anum_pg_propgraph_element_pgesrceqop - 1] = PointerGetDatum(einfo->srceqop);
+	else
+		nulls[Anum_pg_propgraph_element_pgesrceqop - 1] = true;
 	if (einfo->destkey)
 		values[Anum_pg_propgraph_element_pgedestkey - 1] = PointerGetDatum(einfo->destkey);
 	else
@@ -534,6 +662,10 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 		values[Anum_pg_propgraph_element_pgedestref - 1] = PointerGetDatum(einfo->destref);
 	else
 		nulls[Anum_pg_propgraph_element_pgedestref - 1] = true;
+	if (einfo->desteqop)
+		values[Anum_pg_propgraph_element_pgedesteqop - 1] = PointerGetDatum(einfo->desteqop);
+	else
+		nulls[Anum_pg_propgraph_element_pgedesteqop - 1] = true;
 
 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 	CatalogTupleInsert(rel, tup);
@@ -551,13 +683,17 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 	add_exact_object_address(&referenced, addrs);
 	array_of_attnums_to_objectaddrs(einfo->relid, einfo->key, addrs);
 
-	/* Add dependencies on vertices */
+	/*
+	 * Add dependencies on vertices and equality operators used for key
+	 * comparison.
+	 */
 	if (einfo->srcvertexid)
 	{
 		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid);
 		add_exact_object_address(&referenced, addrs);
 		array_of_attnums_to_objectaddrs(einfo->relid, einfo->srckey, addrs);
 		array_of_attnums_to_objectaddrs(einfo->srcrelid, einfo->srcref, addrs);
+		array_of_opers_to_objectaddrs(einfo->srceqop, addrs);
 	}
 	if (einfo->destvertexid)
 	{
@@ -565,10 +701,9 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 		add_exact_object_address(&referenced, addrs);
 		array_of_attnums_to_objectaddrs(einfo->relid, einfo->destkey, addrs);
 		array_of_attnums_to_objectaddrs(einfo->destrelid, einfo->destref, addrs);
+		array_of_opers_to_objectaddrs(einfo->desteqop, addrs);
 	}
 
-	/* TODO: dependencies on equality operators, like for foreign keys */
-
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 
 	table_close(rel, NoLock);
@@ -768,6 +903,7 @@ insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGra
 	table_close(rel, NoLock);

 	foreach(lc, tp)
 	{
@@ -786,9 +922,15 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 {
 	Oid			propoid;
 	Oid			exprtypid;
+	int32		exprtypmod;
+	Oid			exprcollation;
 	Oid			proptypid;
+	int32		proptypmod;
+	Oid			propcollation;

 	/*
 	 * Insert into pg_propgraph_property if not already existing.
@@ -805,6 +947,8 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 		ObjectAddress referenced;
 
 		proptypid = exprtypid;

@@ -814,6 +958,8 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 
@@ -830,7 +976,13 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 
 	/*
@@ -847,6 +999,32 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 				errdetail("In a property graph, a property of the same name has to have the same data type in each label."));
 	}
 
@@ -1238,10 +1416,10 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
 
 		propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
 									einfo->aliasname, edge->location, "SOURCE",
-									&einfo->srckey, &einfo->srcref);
+									&einfo->srckey, &einfo->srcref, &einfo->srceqop);
 		propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
 									einfo->aliasname, edge->location, "DESTINATION",
-									&einfo->destkey, &einfo->destref);
+									&einfo->destkey, &einfo->destref, &einfo->desteqop);
 
 		einfo->labels = edge->labels;
 
index 373fa1eec2f..fa8da073d20 100644
index 10100202a9d..0ebabda0e81 100644
 
 			/*

 
 
 			return (Node *) gpr;
 		}
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 9c14fa3b1b7..80e28db752d 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/table.h"
+#include "catalog/pg_operator.h"
 #include "catalog/pg_propgraph_element.h"
 #include "catalog/pg_propgraph_element_label.h"
 #include "catalog/pg_propgraph_label.h"
@@ -23,7 +24,10 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/analyze.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
 #include "parser/parse_node.h"
+#include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "parser/parse_relation.h"
@@ -83,7 +87,7 @@ struct path_element
 };
 
+static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum, AttrNumber catalog_eqop_attnum);
@@ -373,6 +377,23 @@ generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries,
@@ -552,6 +573,12 @@ generate_query_for_empty_path_pattern(RangeTblEntry *rte)
@@ -747,11 +774,15 @@ create_gpe_for_element(struct path_factor *pf, Oid elemoid)
+													 Anum_pg_propgraph_element_pgesrcref,
+													 Anum_pg_propgraph_element_pgesrceqop);
+													  Anum_pg_propgraph_element_pgedestref,
+													  Anum_pg_propgraph_element_pgedesteqop);
 	}
 
 	ReleaseSysCache(eletup);
@@ -1122,18 +1153,26 @@ replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
 }
 
 /*
- * Build join qualification expressions between edge and vertex tables.
+ * Build join qualification expressions between edge and vertex tables using
+ * equality operators identified at the time of creating the edge.
  */
 static List *
-build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum)
+build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum, AttrNumber catalog_eqop_attnum)
 {
 	List	   *quals = NIL;
 	Form_pg_propgraph_element pgeform;
 	Datum		datum;
 	Datum	   *d1,
-			   *d2;
+			   *d2,
+			   *d3;
 	int			n1,
-				n2;
+				n2,
+				n3;
+	ParseState *pstate = make_parsestate(NULL);
+	HeapTuple	reftup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(refid));
+	Oid			refrelid = ((Form_pg_propgraph_element) GETSTRUCT(reftup))->pgerelid;
+
+	ReleaseSysCache(reftup);

@@ -1143,33 +1182,74 @@ build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNum
 	datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_ref_attnum);
 	deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d2, NULL, &n2);
 
+	datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_eqop_attnum);
+	deconstruct_array_builtin(DatumGetArrayTypeP(datum), OIDOID, &d3, NULL, &n3);
+
 	if (n1 != n2)
 		elog(ERROR, "array size key (%d) vs ref (%d) mismatch for element ID %u", catalog_key_attnum, catalog_ref_attnum, pgeform->oid);
+	if (n1 != n3)
+		elog(ERROR, "array size key (%d) vs operator (%d) mismatch for element ID %u", catalog_key_attnum, catalog_eqop_attnum, pgeform->oid);
 
 	for (int i = 0; i < n1; i++)
 	{
 		AttrNumber	keyattn = DatumGetInt16(d1[i]);
 		AttrNumber	refattn = DatumGetInt16(d2[i]);
+		Oid			eqop = DatumGetObjectId(d3[i]);
+		Var		   *keyvar;
+		Var		   *refvar;
 		Oid			atttypid;
-		TypeCacheEntry *typentry;
-		OpExpr	   *op;
+		int32		atttypmod;
+		Oid			attcoll;
+		HeapTuple	tup;
+		Form_pg_operator opform;
+		List	   *args;
+		Oid			actual_arg_types[2];
+		Oid			declared_arg_types[2];
+		OpExpr	   *linkqual;
+
+		get_atttypetypmodcoll(pgeform->pgerelid, keyattn, &atttypid, &atttypmod, &attcoll);
+		keyvar = makeVar(edgerti, keyattn, atttypid, atttypmod, attcoll, 0);
+		get_atttypetypmodcoll(refrelid, refattn, &atttypid, &atttypmod, &attcoll);
+		refvar = makeVar(refrti, refattn, atttypid, atttypmod, attcoll, 0);
+
+		tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(eqop));
+		if (!HeapTupleIsValid(tup))
+			elog(ERROR, "cache lookup failed for operator %u", eqop);
+		opform = (Form_pg_operator) GETSTRUCT(tup);
+		/* An equality operator is a binary operator returning boolean result. */
+		Assert(opform->oprkind == 'b'
+			   && RegProcedureIsValid(opform->oprcode)
+			   && opform->oprresult == BOOLOID
+			   && !get_func_retset(opform->oprcode));
 
 		/*
-		 * TODO: Assumes types the same on both sides; no collations yet. Some
-		 * of this could probably be shared with foreign key triggers.
+		 * Prepare operands and cast them to the types required by the
+		 * equality operator. Similar to PK/FK qauls, referenced vertex key is
+		 * used as left operand and referencing edge key is used as right
+		 * operand.
 		 */
-		atttypid = get_atttype(pgeform->pgerelid, keyattn);
-		typentry = lookup_type_cache(atttypid, TYPECACHE_EQ_OPR);
-
-		op = makeNode(OpExpr);
-		op->location = -1;
-		op->opno = typentry->eq_opr;
-		op->opresulttype = BOOLOID;
-		op->args = list_make2(makeVar(edgerti, keyattn, atttypid, -1, 0, 0),
-							  makeVar(refrti, refattn, atttypid, -1, 0, 0));
-		quals = lappend(quals, op);
+		args = list_make2(refvar, keyvar);
+		actual_arg_types[0] = exprType((Node *) refvar);
+		actual_arg_types[1] = exprType((Node *) keyvar);
+		declared_arg_types[0] = opform->oprleft;
+		declared_arg_types[1] = opform->oprright;
+		make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
+
+		linkqual = makeNode(OpExpr);
+		linkqual->opno = opform->oid;
+		linkqual->opfuncid = opform->oprcode;
+		linkqual->opresulttype = opform->oprresult;
+		linkqual->opretset = false;
+		/* opcollid and inputcollid will be set by parse_collate.c */
+		linkqual->args = args;
+		linkqual->location = -1;
+
+		ReleaseSysCache(tup);
+		quals = lappend(quals, linkqual);
 	}

diff --git a/src/include/catalog/pg_propgraph_element.h b/src/include/catalog/pg_propgraph_element.h
index 2bc2066b6c1..fea06c0963e 100644
--- a/src/include/catalog/pg_propgraph_element.h
+++ b/src/include/catalog/pg_propgraph_element.h
@@ -62,6 +62,11 @@ CATALOG(pg_propgraph_element,8299,PropgraphElementRelationId)
 	 */
 	int16		pgesrcref[1];
 
+	/*
+	 * for edges: Oids of the equality operators for comparing source keys
+	 */
+	Oid			pgesrceqop[1];
+
 	/*
 	 * for edges: destination vertex key (column numbers in pgerelid relation)
 	 */
@@ -72,6 +77,12 @@ CATALOG(pg_propgraph_element,8299,PropgraphElementRelationId)
 	 * in relation reached via pgedestvertexid)
 	 */
 	int16		pgedestref[1];
+
+	/*
+	 * for edges: Oids of the equality operators for comparing destination
+	 * keys
+	 */
+	Oid			pgedesteqop[1];
 #endif
 } FormData_pg_propgraph_element;

 
 /* ----------------
 
index 43316fbc029..c3f072944cd 100644
+ERROR:  no equality operator exists for SOURCE key comparison of edge "e"

 

 

 

 
 
+-- collation
 
 -- information schema

---

From: Junwang Zhao <[email-masked]>
Date: Sat, 14 Dec 2024 21:53:05 +0800

Hi Ashutosh,

> > 0008 - has WIP fix for \d and \d+

I'm looking at the catalog definition, I have some questions which
might be silly.

Each pg element can have multiple labels(whose properties belong
to the same pg element), can we have multiple elements share the
same label?

If we have a 1..* relation between element and label, I think maybe
we don't need _pg_propgraph_label_.

>From the name _pg_propgraph_label_property_, I tend to think it is
referencing _pg_propgraph_label_, but actually it is referencing
_pg_propgraph_element_label_.

-- 
Regards
Junwang Zhao

---

From: Ashutosh Bapat <[email-masked]>
Date: Mon, 16 Dec 2024 15:44:04 +0530

Hi Junwang,

> I'm looking at the catalog definition, I have some questions which

Thanks for your interest in SQL/PGQ.

> to the same pg element), can we have multiple elements share the

Yes. A label can be shared between edges and vertices as well.

> we don't need _pg_propgraph_label_.

pg_propgraph_label is used to map a label name in a given property
graph to its OID. pg_propgraph_element_label - can be used to find all
the elements with a given label and all the labels of a given element.
The relationship is many to many.

> _pg_propgraph_element_label_.

Right.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Junwang Zhao <[email-masked]>
Date: Mon, 16 Dec 2024 20:00:30 +0800

> The relationship is many to many.

Ok, then it makes sense to me. Thanks for the explanation.

-- 
Regards
Junwang Zhao

---

From: Junwang Zhao <[email-masked]>
Date: Sat, 21 Dec 2024 20:51:04 +0800

Hi Ashutosh,

> > 0008 - has WIP fix for \d and \d+

Here are some review opinions for 0001, I haven't seen the other
patches, forgive me if some of the opinions have already been
addressed.

1. In propgraph_edge_get_ref_keys, when finding a matching foreign key,
the fk pointer will always point to last ForeignKeyCacheInfo of
the edge relation, which is wrong. We should add another pointer
that remembers the matched ForeignKeyCacheInfo to ref_rel. See
attached 0001.

2. Some of the TODOs seem easy to address, attached 0002 does this.

3. Since property name and label name are unique in property graph
scope, some of the wording are not accurate. See attached 0003.

-- 
Regards
Junwang Zhao
From 57ececd2d76e83709afc01c5112f6747f750ccf6 Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email-masked]>
Date: Sat, 21 Dec 2024 12:10:03 +0000
Subject: [PATCH v1 1/3] fix wrong ref keys

In propgraph_edge_get_ref_keys, when finding a matching foreign key,
the fk pointer will always point to last ForeignKeyCacheInfo of
the edge relation, which is wrong. We should add another pointer
that remembers the matched ForeignKeyCacheInfo to ref_rel.

Signed-off-by: Junwang Zhao <[email-masked]>
---
 src/backend/commands/propgraphcmds.c                | 10 +++++++---
 src/test/regress/expected/create_property_graph.out |  4 ++--
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 8430bfda7b..b9afb3a413 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -381,6 +381,7 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 		ListCell   *lc;
 		int			count = 0;
 		ForeignKeyCacheInfo *fk = NULL;
+		ForeignKeyCacheInfo *fk2 = NULL;
 
 		fkeys = RelationGetFKeyList(edge_rel);
 		foreach(lc, fkeys)
@@ -388,7 +389,10 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 			fk = lfirst_node(ForeignKeyCacheInfo, lc);
 
 			if (fk->confrelid == RelationGetRelid(ref_rel))
+			{
 				count++;
+				fk2 = fk;
+			}
 		}
 
 		if (count == 0)
@@ -402,10 +406,10 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 					errmsg("more than one suitable foreign key exists for %s key of edge \"%s\"", type, aliasname),
 					parser_errposition(pstate, location));
 
-		Assert(fk);
+		Assert(fk2);
 
-		*outkey = array_from_attnums(fk->nkeys, fk->conkey);
-		*outref = array_from_attnums(fk->nkeys, fk->confkey);
+		*outkey = array_from_attnums(fk2->nkeys, fk2->conkey);
+		*outref = array_from_attnums(fk2->nkeys, fk2->confkey);
 	}
 }
 
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 43316fbc02..70c4afda52 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -96,7 +96,7 @@ SELECT pg_get_propgraphdef('g5'::regclass);
          t12 KEY (b) PROPERTIES (b)                                                                               +
      )                                                                                                            +
      EDGE TABLES (                                                                                                +
-         t13 KEY (c) SOURCE KEY (e) REFERENCES t11 (a) DESTINATION KEY (e) REFERENCES t12 (b) PROPERTIES (c, d, e)+
+         t13 KEY (c) SOURCE KEY (d) REFERENCES t11 (a) DESTINATION KEY (e) REFERENCES t12 (b) PROPERTIES (c, d, e)+
      )
 (1 row)
 
@@ -255,7 +255,7 @@ SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_grap
  regression             | create_property_graph_tests | g4                  | e2               | t1                 | SOURCE      | a                      | a                        |                1
  regression             | create_property_graph_tests | g4                  | e2               | t3                 | DESTINATION | x                      | x                        |                1
  regression             | create_property_graph_tests | g4                  | e2               | t3                 | DESTINATION | t                      | y                        |                2
- regression             | create_property_graph_tests | g5                  | t13              | t11                | SOURCE      | e                      | a                        |                1
+ regression             | create_property_graph_tests | g5                  | t13              | t11                | SOURCE      | d                      | a                        |                1
  regression             | create_property_graph_tests | g5                  | t13              | t12                | DESTINATION | e                      | b                        |                1
 (12 rows)
 
-- 
2.39.5

From 5f226e04412eda4b7f12e64a24618772505c4c04 Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email-masked]>
Date: Sat, 21 Dec 2024 12:13:40 +0000
Subject: [PATCH v1 2/3] reduce several trivial TODOs

Signed-off-by: Junwang Zhao <[email-masked]>
---
 src/backend/catalog/objectaddress.c | 42 ++++++++++++++++++++++++-----
 src/backend/utils/cache/lsyscache.c | 19 +++++++++++++
 src/include/utils/lsyscache.h       |  1 +
 3 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d1bce431c7..6f967add87 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -6149,16 +6149,46 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case PropgraphElementRelationId:
-			appendStringInfo(&buffer, "%u TODO", object->objectId);
-			break;
+			{
+				char	   *elemname;
+
+				elemname = get_propgraph_element_alias_name(object->objectId);
+				if (elemname)
+				{
+					appendStringInfoString(&buffer, quote_identifier(elemname));
+					if (objname)
+						*objname = list_make1(elemname);
+				}
+				break;
+			}
 
 		case PropgraphLabelRelationId:
-			appendStringInfo(&buffer, "%u TODO", object->objectId);
-			break;
+			{
+				char	   *labelname;
+
+				labelname = get_propgraph_label_name(object->objectId);
+				if (labelname)
+				{
+					appendStringInfoString(&buffer, quote_identifier(labelname));
+					if (objname)
+						*objname = list_make1(labelname);
+				}
+				break;
+			}
 
 		case PropgraphPropertyRelationId:
-			appendStringInfo(&buffer, "%u TODO", object->objectId);
-			break;
+			{
+				char	   *propname;
+
+				propname = get_propgraph_property_name(object->objectId);
+				if (propname)
+				{
+					appendStringInfoString(&buffer, quote_identifier(propname));
+					if (objname)
+						*objname = list_make1(propname);
+				}
+				break;
+			}
 
index 3bf331b21a..6c081ca63f 100644
@@ -32,6 +32,7 @@
@@ -3717,6 +3718,24 @@ get_subscription_name(Oid subid, bool missing_ok)
 	return subname;
 }
 
+char *
+get_propgraph_element_alias_name(Oid elemoid)
+{
+	HeapTuple	tuple;
+	char	   *elemname;
+
+	tuple = SearchSysCache1(PROPGRAPHELOID, elemoid);
+	if (!tuple)
+	{
+		elog(ERROR, "cache lookup failed for element %u", elemoid);
+		return NULL;
+	}
+	elemname = pstrdup(NameStr(((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgealias));
+	ReleaseSysCache(tuple);
+
+	return elemname;
+}
+
 char *
 get_propgraph_label_name(Oid labeloid)
 {
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 82d0bbb267..7708793364 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -206,6 +206,7 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
 
+extern char *get_propgraph_element_alias_name(Oid elemoid);
 extern char *get_propgraph_label_name(Oid labeloid);
 extern char *get_propgraph_property_name(Oid propoid);
 
-- 
2.39.5

From 6a0aadc5687414673ec54e2a1a40ee913051622a Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email-masked]>
Date: Sat, 21 Dec 2024 12:41:31 +0000
Subject: [PATCH v1 3/3] some typos

Since property name and label name are unique in property graph
scope, some of the wording are not accurate.

Signed-off-by: Junwang Zhao <[email-masked]>
---
 doc/src/sgml/information_schema.sgml           | 18 +++++++++---------
 doc/src/sgml/ref/alter_property_graph.sgml     |  2 +-
 doc/src/sgml/ref/create_property_graph.sgml    |  6 +++---
 doc/src/sgml/ref/drop_property_graph.sgml      |  2 +-
 src/backend/commands/propgraphcmds.c           |  4 ++--
 .../regress/expected/create_property_graph.out |  8 ++++----
 6 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index c92b65b03a..dc0257902b 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -4227,7 +4227,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -4346,7 +4346,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -4430,7 +4430,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -4505,7 +4505,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -4589,7 +4589,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -4700,7 +4700,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -4774,7 +4774,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -4839,7 +4839,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
 
@@ -5255,7 +5255,7 @@ ORDER BY c.ordinal_position;
        <structfield>property_graph_name</structfield> <type>sql_identifier</type>
       </para>
       <para>
-       Name of the property_graph
+       Name of the property graph
       </para></entry>
      </row>
     </tbody>
diff --git a/doc/src/sgml/ref/alter_property_graph.sgml b/doc/src/sgml/ref/alter_property_graph.sgml
index 604c518011..4ae53deb09 100644
--- a/doc/src/sgml/ref/alter_property_graph.sgml
+++ b/doc/src/sgml/ref/alter_property_graph.sgml
@@ -99,7 +99,7 @@ ALTER PROPERTY GRAPH [ IF EXISTS ] <replaceable class="parameter">name</replacea
      <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... DROP LABEL</literal></term>
      <listitem>
       <para>
-       This form removes a new label from an existing vertex or edge table.
+       This form removes a label from an existing vertex or edge table.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/ref/create_property_graph.sgml b/doc/src/sgml/ref/create_property_graph.sgml
index f88d1194cb..e953725847 100644
--- a/doc/src/sgml/ref/create_property_graph.sgml
+++ b/doc/src/sgml/ref/create_property_graph.sgml
@@ -151,7 +151,7 @@ CREATE [ TEMP | TEMPORARY ] PROPERTY GRAPH <replaceable class="parameter">name</
     <listitem>
      <para>
       The vertex tables that the edge table is linked to.  These refer to the
-      aliases of a the vertex table.
+      aliases of the vertex tables.
      </para>
     </listitem>
    </varlistentry>
@@ -251,8 +251,8 @@ CREATE PROPERTY GRAPH g1
 
     <listitem>
      <para>
-      For each property graph element, all properties with the same name must
-      have the same expression for each label.  For example, this would be
+      For each property graph, all properties with the same name must
+      have the same expression.  For example, this would be
       allowed:
 <programlisting>
 CREATE PROPERTY GRAPH g1
diff --git a/doc/src/sgml/ref/drop_property_graph.sgml b/doc/src/sgml/ref/drop_property_graph.sgml
index 31cb77a2af..e16de5507b 100644
--- a/doc/src/sgml/ref/drop_property_graph.sgml
+++ b/doc/src/sgml/ref/drop_property_graph.sgml
@@ -96,7 +96,7 @@ DROP PROPERTY GRAPH g1;
    <command>DROP PROPERTY GRAPH</command> conforms to ISO/IEC 9075-16
    (SQL/PGQ), except that the standard only allows one property graph to be
    dropped per command, and apart from the <literal>IF EXISTS</literal>
-   option, which is a <productname>PostgreSQL</productname> extension..
+   option, which is a <productname>PostgreSQL</productname> extension.
   </para>
  </refsect1>
 
diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index b9afb3a413..93d3bc1bf1 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -848,7 +848,7 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 				errcode(ERRCODE_SYNTAX_ERROR),
 				errmsg("property \"%s\" data type mismatch: %s vs. %s",
 					   propname, format_type_be(proptypid), format_type_be(exprtypid)),
-				errdetail("In a property graph, a property of the same name has to have the same data type in each label."));
+				errdetail("In a property graph, a property of the same name has to have the same data type."));
 	}
 
 	/*
@@ -995,7 +995,7 @@ check_element_properties(Oid peoid)
 								errcode(ERRCODE_SYNTAX_ERROR),
 								errmsg("element \"%s\" property \"%s\" expression mismatch: %s vs. %s",
 									   NameStr(elform->pgealias), get_propgraph_property_name(propoid), dpa, dpb),
-								errdetail("In a property graph element, a property of the same name has to have the same expression in each label."));
+								errdetail("In a property graph, a property of the same name has to have the same expression."));
 
 						ReleaseSysCache(tuple3);
 					}
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 70c4afda52..475f2b7943 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -139,24 +139,24 @@ CREATE PROPERTY GRAPH gx
                    LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
     );
 ERROR:  element "t1" property "aa" expression mismatch: (1 + a) vs. (a + 1)
-DETAIL:  In a property graph element, a property of the same name has to have the same expression in each label.
+DETAIL:  In a property graph, a property of the same name has to have the same expression.
 ALTER PROPERTY GRAPH g2
     ADD VERTEX TABLES (
         t1 AS t1x KEY (a) LABEL foo PROPERTIES (a + 1 AS aa)
                           LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
     );
 ERROR:  element "t1x" property "aa" expression mismatch: (1 + a) vs. (a + 1)
-DETAIL:  In a property graph element, a property of the same name has to have the same expression in each label.
+DETAIL:  In a property graph, a property of the same name has to have the same expression.
 CREATE PROPERTY GRAPH gx
     VERTEX TABLES (
         t1 KEY (a) PROPERTIES (b AS p1),
         t2 PROPERTIES (k AS p1)  -- type mismatch
     );
 ERROR:  property "p1" data type mismatch: text vs. integer
-DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+DETAIL:  In a property graph, a property of the same name has to have the same data type.
 ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE t1 ADD LABEL foo PROPERTIES (b AS k);  -- type mismatch
 ERROR:  property "k" data type mismatch: integer vs. text
-DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+DETAIL:  In a property graph, a property of the same name has to have the same data type.
 CREATE PROPERTY GRAPH gx
     VERTEX TABLES (
         t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa),
-- 
2.39.5

---

From: Andreas Karlsson <[email-masked]>
Date: Sat, 21 Dec 2024 22:45:25 +0100

> would affect performance.

Updated the patch to remove the static variable. No clue why I thought 
that one was necessary.

Andreas
From 79e9474fd02fab7210bed6a5a3db3bc57d725193 Mon Sep 17 00:00:00 2001
From: Andreas Karlsson <[email-masked]>
Date: Tue, 29 Oct 2024 20:23:24 +0100
Subject: [PATCH v2] Broken out tokeniziation of arrows

---
 src/backend/parser/gram.y         | 20 +++++++++++++++---
 src/backend/parser/scan.l         | 35 +++++++++++++++++++++++++++++++
 src/fe_utils/psqlscan.l           |  5 +++++
 src/include/parser/scanner.h      |  1 +
 src/interfaces/ecpg/preproc/pgc.l | 34 ++++++++++++++++++++++++++++++
 src/pl/plpgsql/src/pl_gram.y      |  1 +
 6 files changed, 93 insertions(+), 3 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 67eb96396af..179069e0299 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -681,6 +681,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %token <ival>	ICONST PARAM
 %token			TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
 %token			LESS_EQUALS GREATER_EQUALS NOT_EQUALS
+%token			LEFT_ARROW_LESS LEFT_ARROW_MINUS RIGHT_ARROW
 
 /*
@@ -821,7 +822,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -874,8 +875,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -14893,6 +14894,8 @@ a_expr:		c_expr									{ $$ = $1; }
@@ -14907,6 +14910,8 @@ a_expr:		c_expr									{ $$ = $1; }
@@ -14917,6 +14922,8 @@ a_expr:		c_expr									{ $$ = $1; }
 
@@ -15386,6 +15393,8 @@ b_expr:		c_expr
@@ -15396,6 +15405,8 @@ b_expr:		c_expr
@@ -16554,16 +16565,19 @@ all_Op:		Op										{ $$ = $1; }

 qual_Op:	Op
index 72404e72fff..a17e42d0ef1 100644
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -892,8 +893,18 @@ other			.
 					return NOT_EQUALS;
 				}
 
+{right_arrow}	{
+					SET_YYLLOC();
+					return RIGHT_ARROW;
+				}
+
 {self}			{
 					SET_YYLLOC();
+					if (yytext[0] == '-' && yyextra->inleftarrow)
+					{
+						yyextra->inleftarrow = false;
+						return LEFT_ARROW_MINUS;
+					}
 					return yytext[0];
 				}
 
@@ -919,6 +930,26 @@ other			.
 					if (slashstar)
 						nchars = slashstar - yytext;
 
+					if (nchars == 2 && yytext[0] == '<' && yytext[1] == '-')
+					{
+						/* Strip the unwanted chars from the token */
+						yyless(1);
+
+						yyextra->inleftarrow = true;
+
+						return LEFT_ARROW_LESS;
+					}
+
+					if (nchars == 1 && yytext[0] == '-' && yyextra->inleftarrow)
+					{
+						/* Strip the unwanted chars from the token */
+						if (nchars < yyleng)
+							yyless(nchars);
+
+						yyextra->inleftarrow = false;
+						return LEFT_ARROW_MINUS;
+					}
+
 					/*
 					 * For SQL compatibility, '+' and '-' cannot be the
 					 * last char of a multi-char operator unless the operator
@@ -989,6 +1020,8 @@ other			.
 								return NOT_EQUALS;
 							if (yytext[0] == '!' && yytext[1] == '=')
 								return NOT_EQUALS;
+							if (yytext[0] == '-' && yytext[1] == '>')
+								return RIGHT_ARROW;
 						}
 					}
 
@@ -1294,6 +1327,8 @@ scanner_init(const char *str,
 	yyext->literalbuf = (char *) palloc(yyext->literalalloc);
 	yyext->literallen = 0;
 
+	yyext->inleftarrow = false;
+
 	return scanner;
 }

 /*
 
+{right_arrow}	{
+					ECHO;
+				}
+
 	/*
 	 * These rules are specific to psql --- they implement parenthesis
 	 * counting and detection of command-ending semicolon.  These must
diff --git a/src/include/parser/scanner.h b/src/include/parser/scanner.h
index d6293b1e878..61647be928c 100644
--- a/src/include/parser/scanner.h
+++ b/src/include/parser/scanner.h
@@ -105,6 +105,7 @@ typedef struct core_yy_extra_type
 	int			state_before_str_stop;	/* start cond. before end quote */
 	int			xcdepth;		/* depth of nesting in slash-star comments */
 	char	   *dolqstart;		/* current $foo$ quote start string */
+	bool		inleftarrow;	/* are we parsing a -> operator? */
 	YYLTYPE		save_yylloc;	/* one-element stack for PUSH_YYLLOC() */
 
 	/* first part of UTF16 surrogate pair for Unicode escapes */
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index 82708013ee6..5ea88117aa1 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -335,6 +335,7 @@ less_equals		"<="
 greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
+right_arrow		"->"
 
 /*
 
 %{

+{right_arrow}	{
+					return RIGHT_ARROW;
+				}
+
 {informix_special} {

 
 
 /*

---

From: Ashutosh Bapat <[email-masked]>
Date: Wed, 1 Jan 2025 14:22:29 +0530

On Sat, Dec 21, 2024 at 6:21 PM Junwang Zhao <[email-masked]> wrote:
Thanks Junwang for your review.

> that remembers the matched ForeignKeyCacheInfo to ref_rel. See

Nice catch. I fixed it in a slightly different way reducing overall
code by using foreach_node(). I have merged this as part of 0004 which
has fixes for several other issues. Interestingly there was a SQL that
had revealed the problem in create_property_graph.sql. But we had
accepted a wrong output. Corrected that as well.

>From a cursory glance those changes look useful and mostly correct. It
will be good if you can provide a SQL test for those, covering that
part of the code. Please post the whole patch-set with your changes as
a separate commit/patch.

     <para>

Each property graph element may have a property with the same name
associated with multiple labels. But each of those properties should
have the same expression. You may see that as the same property being
defined multiple times once in each label it is associated with OR
multiple properties with the same name. Current wording uses the
latter notion, so it looks fine to me. The changes made to error
messages are not needed with this notion.

--
Best Wishes,
Ashutosh Bapat
From 4f896f3e875603c9d284e56f8b8352d5aa64bdc2 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 8 Jul 2024 11:53:25 +0530
Subject: [PATCH 2/9] support WHERE clause in graph pattern

---

 

 

 

 
 -- Errors

From 948f9e8c68b260f9cafeab516656668830857faa Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 17 Oct 2024 14:59:26 +0530
Subject: [PATCH 5/9] Access permissions on property graph

Note:

 
  <refsect1>

 

+--
+-- Property graphs
+--
 
 -- clean up

 drop sequence x_seq;
 
+drop view atpgv1;
+drop view atpgv2;

From 6479e3b20159d9eea9ce388e99165c10cf59c431 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 1 Aug 2024 10:52:35 +0530
Subject: [PATCH 3/9] Support cyclic path pattern.

Author: Ashutosh Bapat
---

+/*

 

 
+	/*

-	foreach(lc, gep_elems)
 
 		/*

 

-			/*
 
 			/*

-		if (gep->whereClause)
+		if (pf->whereClause)
 		{
 			Node	   *tr;

 

 
 
 	if (!eletup)

 
 	/*

 
 	ReleaseSysCache(eletup);
 
-	return gpe;
+	return pe;
 }
 
 static const char *
 
 		label_oids = NIL;

 		ScanKeyInit(&key[0],
 
-				if (gpe)
+				if (pe)
 				{

 		systable_endscan(scan);
 	}

 static const char *

 
 
 				ReleaseSysCache(tup);
 			}

 				if (!n)
 				{
 
 		if (!n)
 
 		return n;
 	}

 
 
 -- Errors

From a03001f2952db974dbd397635285df8e448f30a4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 21 Aug 2024 19:41:48 +0530
Subject: [PATCH 4/9] Fixes following issues

5. If there are multiple foreign keys on an edge table, the last one that appears
in the list returned by RelationGetFKeyList() is used to create a vertex
reference from that edge. This may lead to wrong edge-vertex linkage. Remember
the correct one instead.

Additional changes

Author: Ashutosh Bapat
Reported By: Ajay Pal, Junwang Zhou
---
 src/backend/commands/propgraphcmds.c          |  12 +-
 src/backend/parser/parse_clause.c             |  30 +++-
 src/backend/parser/parse_expr.c               |   5 +
 src/backend/parser/parse_graphtable.c         |  43 +++--
 src/backend/parser/parse_relation.c           |   1 +
 src/backend/rewrite/rewriteGraphTable.c       | 148 ++++++++++--------
 src/include/nodes/parsenodes.h                |   6 +
 src/include/parser/parse_graphtable.h         |  11 +-
 src/include/parser/parse_node.h               |   5 +
 .../expected/create_property_graph.out        |   4 +-
 src/test/regress/expected/graph_table.out     |  81 +++++++++-
 src/test/regress/sql/graph_table.sql          |  45 ++++++
 12 files changed, 284 insertions(+), 107 deletions(-)

diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index 8430bfda7b7..bf333dd2dc2 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -377,18 +377,16 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 	}
 	else
 	{
-		List	   *fkeys;
-		ListCell   *lc;
 		int			count = 0;
 		ForeignKeyCacheInfo *fk = NULL;
 
-		fkeys = RelationGetFKeyList(edge_rel);
-		foreach(lc, fkeys)
+		foreach_node(ForeignKeyCacheInfo, tmp, RelationGetFKeyList(edge_rel))
 		{
-			fk = lfirst_node(ForeignKeyCacheInfo, lc);
-
-			if (fk->confrelid == RelationGetRelid(ref_rel))
+			if (tmp->confrelid == RelationGetRelid(ref_rel))
+			{
+				fk = tmp;
 				count++;
+			}
 		}
 
 		if (count == 0)

 

 

+	/*

 /*

 
 
 	return (Node *) result;
 }

 	return (Node *) result;
 }

 

 
 		if (gep->quantifier)
 
-		/*

 		/*

 		prev_pf = pf;

 

 			/*

 

 	/*
 
 	ReleaseSysCache(eletup);

 

 
 
index 43316fbc029..70c4afda52c 100644

 
 
+-- GRAPH_TABLE in UDFs
 
 -- Errors

+-- GRAPH_TABLE in UDFs

From 40f3faaa625315366de99ac24922d4230fc98284 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Thu, 27 Jun 2024 14:10:44 +0200
Subject: [PATCH 1/9] WIP: SQL Property Graph Queries (SQL/PGQ)

index cc6cf9bef09..335157fd758 100644
 
+     <row>
@@ -2120,7 +2145,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
@@ -6278,6 +6304,449 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
  </sect1>

 

  <para>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 47370e581ae..449e69c07e5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26098,6 +26098,21 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        </para></entry>
       </row>
 
+      <row>

 
+    <para>
 
  </sect1>

index 72f3347e53d..15634136aee 100644
 
         <listitem>
         <para>

         <listitem>
         <para>

+     <varlistentry>
 
 		case OBJECT_TABLE:
 		case OBJECT_SEQUENCE:
+		case OBJECT_PROPGRAPH:
 
 			/*
 
index 87af58fac3a..0f6c7f9200e 100644
@@ -3966,6 +4063,182 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
@@ -4151,6 +4424,10 @@ getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
@@ -4640,6 +4917,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 
@@ -4721,6 +5010,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
@@ -5881,6 +6173,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
@@ -6187,6 +6491,8 @@ get_relkind_objtype(char relkind)
index abb80fd1224..b83f696f049 100644
@@ -380,6 +380,7 @@ ExecRenameStmt(RenameStmt *stmt)
 
 		case OBJECT_COLUMN:
@@ -533,6 +534,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
+		case OBJECT_PROPGRAPH:
 			address = AlterTableNamespace(stmt,
 										  oldSchemaAddr ? &oldNspOid : NULL);
 			break;
@@ -866,6 +868,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 		case OBJECT_OPCLASS:
 		case OBJECT_OPFAMILY:
 		case OBJECT_PROCEDURE:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_ROUTINE:
 		case OBJECT_STATISTIC_EXT:
 		case OBJECT_TABLESPACE:
@@ -874,11 +877,26 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
 			{
 				ObjectAddress address;

index 1f5872554d4..f41537dbcc6 100644
index 49374782625..4e0b1e20478 100644
 
@@ -1546,6 +1552,10 @@ RemoveRelations(DropStmt *drop)
 			relkind = RELKIND_FOREIGN_TABLE;
 			break;
 
+		case OBJECT_PROPGRAPH:
+			relkind = RELKIND_PROPGRAPH;
+			break;
+
 		default:
 			elog(ERROR, "unrecognized drop object type: %d",
 				 (int) drop->removeType);
@@ -14892,6 +14902,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
 		case RELKIND_MATVIEW:
 		case RELKIND_FOREIGN_TABLE:
 		case RELKIND_PARTITIONED_TABLE:
+		case RELKIND_PROPGRAPH:
 			/* ok to change owner */
 			break;
 		case RELKIND_INDEX:
@@ -18435,6 +18446,11 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("\"%s\" is not a composite type", rv->relname)));
 
index 1c12d6ebff0..26a733a864a 100644
@@ -600,7 +600,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos,
 
@@ -1119,6 +1119,12 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
@@ -1183,6 +1189,13 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 
+		case RTE_GRAPH_TABLE:
index adad7ea9a9e..825d8b3dd3e 100644
 
 		/*
 
-		/*
 
-		if (targetlist)
-			*targetlist = NIL;
-		op->colTypes = NIL;
-		op->colTypmods = NIL;
 
-			/*

 
-			/*
 
-			/* emit results */
 
-			/*

-				/*

-			/*
 
-		return (Node *) op;
+		/*
 
index bd5ebb35c40..eb48c9e592d 100644

 /*

 

 

 	QUOTE QUOTES

 

 
 
 qual_Op:	Op
 
+

 			/*
 
+/*

 
 			/*
 
 			/*
 
 			/*
 
+ParseNamespaceItem *
 
 			/*
 
 			/*
index a152cff4117..a10a9f577f2 100644
 
 /*

+{right_arrow}	{
+					SET_YYLLOC();
+					return RIGHT_ARROW;
+				}
+
 {self}			{
 					SET_YYLLOC();
 					return yytext[0];

 OBJS = \
 	rewriteDefine.o \
+	rewriteGraphTable.o \
 	rewriteHandler.o \
 	rewriteManip.o \
 	rewriteRemove.o \

 
+		/*
index c2ed8214ef6..bccdb911007 100644

 
index be1f1f50b78..50ecb3240ee 100644
 
+/*
@@ -7478,6 +7811,171 @@ get_utility_query_def(Query *query, deparse_context *context)
 	}
 }
 
+
+/*
@@ -8079,6 +8577,7 @@ get_name_for_var_field(Var *var, int fieldno,
 
 			/*
@@ -10476,6 +10975,14 @@ get_rule_expr(Node *node, deparse_context *context,
 			get_tablefunc((TableFunc *) node, context, showimplicit);
 			break;
 
@@ -12334,6 +12841,36 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 
 	return subname;
 }
+
+char *
 
index 707a3fc844c..e287a31e23e 100644
index 19969e400fc..e08717dab15 100644

 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15977,8 +15981,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
 		reltypename = "VIEW";
 
@@ -16004,6 +16006,47 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
@@ -16079,8 +16122,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
@@ -16823,6 +16864,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 
@@ -18762,6 +18805,16 @@ getDependencies(Archive *fout)
 
+	/*

index cd16f27947a..4fe3a936132 100644

 
 
 	PQExpBufferData buf;
 	PGresult   *res;

 	initPQExpBuffer(&buf);

index 31c77214b4f..09cd583c3de 100644

 /* All relations */
 
@@ -3132,7 +3155,7 @@ match_previous_words(int pattern_id,
@@ -3170,6 +3193,8 @@ match_previous_words(int pattern_id,
@@ -3488,6 +3513,25 @@ match_previous_words(int pattern_id,

 /* CREATE PUBLICATION */
 	else if (Matches("CREATE", "PUBLICATION", MatchAny))
@@ -4197,6 +4241,12 @@ match_previous_words(int pattern_id,
 	else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
 
+	/* DROP PROPERTY GRAPH */
+	else if (Matches("DROP", "PROPERTY"))
+		COMPLETE_WITH("GRAPH");
+	else if (Matches("DROP", "PROPERTY", "GRAPH"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
+
 	/* DROP RULE */
 	else if (Matches("DROP", "RULE", MatchAny))
 		COMPLETE_WITH("ON");
@@ -4441,6 +4491,7 @@ match_previous_words(int pattern_id,
 											"LARGE OBJECT",
 											"PARAMETER",
 											"PROCEDURE",
+											"PROPERTY GRAPH",
 											"ROUTINE",
 											"SCHEMA",
 											"SEQUENCE",
@@ -4569,6 +4620,14 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH("FROM");
 	}
 
+/* GRAPH_TABLE */
@@ -4892,8 +4951,10 @@ match_previous_words(int pattern_id,
 
@@ -5324,6 +5385,8 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH("OBJECT");
index 0e40be994a8..0226503f49b 100644
 
 /*

+{right_arrow}	{
+					ECHO;
+				}
+
 	/*

 
 
index 2dcc2d42dac..1a318e646a4 100644

+/*
 
+/*
 
+	/*

+/*

 #endif							/* ANALYZE_H */

 
 
index 315973e9d1e..714b12fb2e3 100644
 
 /*

+{right_arrow}	{
+					return RIGHT_ARROW;
+				}
+
 {informix_special} {
 
 						/*
 
index 11714430c13..5bd2eeab4a4 100644
 
 /*
index 1edd9e45ebb..a0ca23b4ebe 100644
 
 # ----------

+--
+-- Property Graph
+--
index e1c4f913f84..ca8b967efad 100644
@@ -4197,3 +4197,24 @@ yyscan_t

From 89d8aa9fd307f269cd69e5e2897aed4401fe0cbe Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 28 Oct 2024 16:43:40 +0530
Subject: [PATCH 7/9] RLS tests

 
 \d document

 

 

 

 

 
 
+-- label disjunction

 

 

 

 

 

 

 
+EXECUTE ppg1(2);
+ id  | a |  b  
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+(6 rows)
+

+EXECUTE ppg2(2);
+ id  | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+

 

 

 

 

 

 

 

 

 

 

 

 
 
---
--- Check INSERT SELECT
---

 

 

 

 

 
 -- try a sampled version
 
 -- try a sampled version

 

 

 

 

 
 SELECT * FROM t1;

 

 

 
 
 -- pp1 ERROR

 

 

 

 

 

 

 

 

 

 

 INSERT INTO z1 VALUES

 

 

 

 

 

 

 

 

 

 

 

 

 

 INSERT INTO x1 VALUES

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
 
 -- No error, but no rows
 TABLE r1;
 
 -- RLS error

 

From 95fa152724ed42888d8816104aec576358a2c784 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Mon, 28 Oct 2024 16:36:27 +0530
Subject: [PATCH 6/9] Property collation and edge-vertex link support

Note for reviewers:

4. Tests
--------

Author: Ashutosh Bapat
---

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 335157fd758..b81aa6ad354 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6443,6 +6443,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
@@ -6471,6 +6483,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
@@ -6742,6 +6766,31 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
     WHERE pg.relkind = 'g'
index bf333dd2dc2..c26976b2a2a 100644

 	char	   *destvertex;
 	Oid			destvertexid;
 	Oid			destrelid;
 	ArrayType  *destkey;
 	ArrayType  *destref;
+	ArrayType  *desteqop;
 
 	List	   *labels;
 };

 
 
 	return a;
 
 	if (keycols)
 
@@ -402,25 +500,34 @@ propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List
 
 		Assert(fk);

 /*

 	i = 0;
 	foreach(lc, colnames)
@@ -436,14 +543,14 @@ array_from_column_list(ParseState *pstate, const List *colnames, int location, R
 
@@ -451,7 +558,7 @@ array_from_column_list(ParseState *pstate, const List *colnames, int location, R
 		}
 	}

 static ArrayType *
@@ -484,6 +591,23 @@ array_of_attnums_to_objectaddrs(Oid relid, ArrayType *arr, ObjectAddresses *addr
 	}
 }
 
+static void
@@ -524,6 +648,10 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
@@ -532,6 +660,10 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 
@@ -549,13 +681,17 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
 
@@ -563,10 +699,9 @@ insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)

 	table_close(rel, NoLock);
@@ -766,6 +901,7 @@ insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGra
 	table_close(rel, NoLock);

 	foreach(lc, tp)
 	{
@@ -784,9 +920,15 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 {
 	Oid			propoid;
 	Oid			exprtypid;
+	int32		exprtypmod;
+	Oid			exprcollation;
 	Oid			proptypid;
+	int32		proptypmod;
+	Oid			propcollation;

 	/*
 	 * Insert into pg_propgraph_property if not already existing.
@@ -803,6 +945,8 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 		ObjectAddress referenced;
 
 		proptypid = exprtypid;

@@ -812,6 +956,8 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 
@@ -828,7 +974,13 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 
 	/*
@@ -845,6 +997,32 @@ insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *pr
 				errdetail("In a property graph, a property of the same name has to have the same data type in each label."));
 	}
 
@@ -1236,10 +1414,10 @@ AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)

 
 
 			/*

 
 
 			return (Node *) gpr;
 		}

 
 	ReleaseSysCache(eletup);
 
 /*

 

 		/*

 
+	/*

 
 /* ----------------
 
index 70c4afda52c..d35c02b462c 100644

 
  regression             | create_property_graph_tests | g5                  | t13              | t11                | SOURCE      | d                      | a                        |                1

 

 

 
 
+-- collation
 
 -- information schema

 

 

From 3e7b3656982f4c7e1293d70797079a9294253083 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Wed, 1 Jan 2025 14:02:28 +0530
Subject: [PATCH 8/9] Document fixes

Assorted grammar and typo fixes.

Author: Junwang Zhou
Reviewed with minor adjustments by: Ashutosh Bapat
---
 doc/src/sgml/information_schema.sgml        | 18 +++++++++---------
 doc/src/sgml/ref/alter_property_graph.sgml  |  2 +-
 doc/src/sgml/ref/create_property_graph.sgml |  2 +-
 doc/src/sgml/ref/drop_property_graph.sgml   |  2 +-
 4 files changed, 12 insertions(+), 12 deletions(-)

index c92b65b03a5..dc0257902b1 100644

 

 

index 604c5180117..4ae53deb092 100644
index ae301b52e0a..76bfef317af 100644
+      aliases of the source and destination vertex tables respectively.
index 31cb77a2af1..e16de5507b1 100644
 
-- 
2.34.1

From 52776b3f02e526ac66485b26614bc53bdf23b9c6 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email-masked]>
Date: Thu, 5 Dec 2024 14:31:36 +0530
Subject: [PATCH 9/9] WIP: Do not print empty columns table for a property
 graph

   OR

Ashutosh Bapat
---

 
 
index d35c02b462c..824695358f8 100644
 
 \d+ g1
 
-- 
2.34.1

---

From: Ashutosh Bapat <[email-masked]>
Date: Wed, 1 Jan 2025 14:31:59 +0530

> messages are not needed with this notion.

My last email is held for moderation. It will arrive once moderators
release it. In the meantime trying to send the patches as a zip file
in a hope that it won't require moderation.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Junwang Zhao <[email-masked]>
Date: Sun, 19 Jan 2025 21:15:45 +0800

Hi Ashutosh,

> in a hope that it won't require moderation.

0007-RLS-tests-20250101.patch introduces regression test failure:

+WARNING:  outfuncs/readfuncs failed to produce an equal rewritten parse tree

P.S. It seems the patch set doesn't apply to master.

-- 
Regards
Junwang Zhao

---

From: Ashutosh Bapat <[email-masked]>
Date: Fri, 24 Jan 2025 19:06:11 +0530

> P.S. It seems the patch set doesn't apply to master.

Thanks for noticing it.

Here's rebased patch set on the current head with some minor conflicts
in various files fixed. I did not see the said failure on my laptop.

0001-0009 patches are the same as the previous set sans mergeconflict fixes.
0010 - is test for \dGx - to make sure that the extended format output
works with \dG. We may decide not to have this patch in the final
commit. But no harm in being there til that point.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Junwang Zhao <[email-masked]>
Date: Fri, 24 Jan 2025 23:46:30 +0800

> in various files fixed. I did not see the said failure on my laptop.

I figured out it's because I have `-DWRITE_READ_PARSE_PLAN_TREES` this option,
removing this option clears the warning, but I'm not sure if this is a
hidden issue.

> commit. But no harm in being there til that point.

I have some changes based on the latest patch set. I attached two patches with
the following summary.

0001:
- doc: some of the query in ddl.sgml can not be executed
- after path factor was introduced, some comments doesn't apply

0002:
- add a get_propgraph_element_alias_name function and do some trivial refactor

-- 
Regards
Junwang Zhao

---

From: Junwang Zhao <[email-masked]>
Date: Fri, 24 Jan 2025 23:56:06 +0800

> removing this option clears the warning, but I'm not sure if this is a

Cirrus meson FreeBSD build has this setting, the regression test
rowsecurity failed
the same as my local machine, see [0], so I think this is an issue we
have to resolve.

Cirrus meson Bookworm build failed 002_pg_upgrade.pl and 027_stream_regress.pl,
see [1]

[0] https://cirrus-ci.com/task/5702052891852800
[1] https://cirrus-ci.com/task/6265002845274112

-- 
Regards
Junwang Zhao

---

From: Ashutosh Bapat <[email-masked]>
Date: Tue, 28 Jan 2025 15:56:45 +0530

> removing this option clears the warning, but I'm not sure if this is a

Thanks for the hint. I found the issue and the fix appears in 0004.
Some members of RangeTblEntry were not being handled in read/out
functions.

> - doc: some of the query in ddl.sgml can not be executed

The standard seems to require a property reference to be qualified by
element pattern variable, even if the property is referenced within
the same element pattern bound to the variable. Hence I accepted your
document fixes which qualify property reference with element pattern
variable.

However, I didn't understand why you changed a LABEL name from order to order_.

> - after path factor was introduced, some comments doesn't apply

Thanks pointing those out. Accepted after rephrasing those and also
correcting some related comments.

> - add a get_propgraph_element_alias_name function and do some trivial refactor

I see you have added some negative tests - object not found tests, but
I didn't see positive tests. Hence I haven't added those changes in
the attached patchset. But we certainly need those changes. You may
want to submit a patch with positive tests. That code needs to be
fixed before committing anyway.

--
Best Wishes,
Ashutosh Bapat

---

From: Junwang Zhao <[email-masked]>
Date: Tue, 28 Jan 2025 19:01:41 +0800

> Some members of RangeTblEntry were not being handled in read/out

Thanks for the fix, I will apply and test again.

> However, I didn't understand why you changed a LABEL name from order to order_.

Ah, that's because `order` is a keyword of SQL(order by), so the alias
is a conflict.
I'm not saying `order_` is a good name though.

> fixed before committing anyway.

Ok, I'll add positive tests.

-- 
Regards
Junwang Zhao

---

From: Ashutosh Bapat <[email-masked]>
Date: Tue, 28 Jan 2025 16:33:19 +0530

> I'm not saying `order_` is a good name though.

I didn't realize that. Thanks for catching. In that case just orders
or cust_orders?

> Ok, I'll add positive tests.

Thanks.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Junwang Zhao <[email-masked]>
Date: Tue, 28 Jan 2025 19:16:30 +0800

> I didn't realize that. Thanks for catching. In that case just orders

Yeah, I think explicitly LABELed as `orders` makes sense.

-- 
Regards
Junwang Zhao

---

From: Junwang Zhao <[email-masked]>
Date: Thu, 6 Feb 2025 22:52:32 +0800

Hi Ashutosh,

> fixed before committing anyway.

The attached file adds the positive tests.

-- 
Regards
Junwang Zhao
From 5dfe5f0c7c7a72b7564b19c1d1e5396c66d5039f Mon Sep 17 00:00:00 2001
From: Junwang Zhao <[email-masked]>
Date: Sat, 21 Dec 2024 12:13:40 +0000
Subject: [PATCH v1] trivial refactor of property graph object address

---
 src/backend/catalog/objectaddress.c           | 54 +++++++++++++++----
 src/backend/commands/propgraphcmds.c          |  8 +--
 src/backend/parser/parse_clause.c             |  2 +-
 src/backend/rewrite/rewriteGraphTable.c       |  4 +-
 src/backend/utils/adt/ruleutils.c             |  8 +--
 src/backend/utils/cache/lsyscache.c           | 30 +++++++++--
 src/include/utils/lsyscache.h                 |  5 +-
 .../expected/create_property_graph.out        | 36 +++++++++++++
 src/test/regress/expected/object_address.out  |  6 +++
 .../regress/sql/create_property_graph.sql     |  5 ++
 src/test/regress/sql/object_address.sql       |  3 ++
 11 files changed, 134 insertions(+), 27 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6eaee345ad2..03ed830d5a1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -4070,8 +4070,12 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 
 				tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for property graph element %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for property graph element %u",
+							 object->objectId);
+					break;
+				}
 
 				pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup);
 
@@ -4118,7 +4122,7 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 
 				pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
 
-				appendStringInfo(&buffer, _("label %s of "), get_propgraph_label_name(pgelform->pgellabelid));
+				appendStringInfo(&buffer, _("label %s of "), get_propgraph_label_name(pgelform->pgellabelid, false));
 				ObjectAddressSet(oa, PropgraphElementRelationId, pgelform->pgelelid);
 				appendStringInfoString(&buffer, getObjectDescription(&oa, false));
 
@@ -4193,7 +4197,7 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 
 				plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple);
 
-				appendStringInfo(&buffer, _("property %s of "), get_propgraph_property_name(plpform->plppropid));
+				appendStringInfo(&buffer, _("property %s of "), get_propgraph_property_name(plpform->plppropid, false));
 				ObjectAddressSet(oa, PropgraphElementLabelRelationId, plpform->plpellabelid);
 				appendStringInfoString(&buffer, getObjectDescription(&oa, false));
 
@@ -6174,16 +6178,46 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
+				elemname = get_propgraph_element_alias_name(object->objectId, missing_ok);
 
+				labelname = get_propgraph_label_name(object->objectId, missing_ok);
 
+				propname = get_propgraph_property_name(object->objectId, missing_ok);
 
 		case PublicationRelationId:
 			{
diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c
index c26976b2a2a..68b81e1c807 100644
--- a/src/backend/commands/propgraphcmds.c
+++ b/src/backend/commands/propgraphcmds.c
@@ -1166,7 +1166,7 @@ check_element_properties(Oid peoid)
 						ereport(ERROR,
 								errcode(ERRCODE_SYNTAX_ERROR),
 								errmsg("element \"%s\" property \"%s\" expression mismatch: %s vs. %s",
-									   NameStr(elform->pgealias), get_propgraph_property_name(propoid), dpa, dpb),
+									   NameStr(elform->pgealias), get_propgraph_property_name(propoid, false), dpa, dpb),
 								errdetail("In a property graph element, a property of the same name has to have the same expression in each label."));
 
 						ReleaseSysCache(tuple3);
@@ -1283,7 +1283,7 @@ check_element_label_properties(Oid ellabeloid)
 	if (list_length(refprops) != list_length(myprops))
 		ereport(ERROR,
 				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				errmsg("mismatching number of properties in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+				errmsg("mismatching number of properties in definition of label \"%s\"", get_propgraph_label_name(labelid, false)));
 
 	diff1 = list_difference(myprops, refprops);
 	diff2 = list_difference(refprops, myprops);
@@ -1291,7 +1291,7 @@ check_element_label_properties(Oid ellabeloid)
 	if (diff1 || diff2)
 		ereport(ERROR,
 				errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				errmsg("mismatching properties names in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+				errmsg("mismatching properties names in definition of label \"%s\"", get_propgraph_label_name(labelid, false)));
 }
 
 /*
@@ -1844,7 +1844,7 @@ get_element_label_property_names(Oid ellabeloid)
 	{
 		Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple);
 
-		result = lappend(result, makeString(get_propgraph_property_name(plpform->plppropid)));
+		result = lappend(result, makeString(get_propgraph_property_name(plpform->plppropid, false)));
 	}
 
 	systable_endscan(scan);
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 29f518f6ada..b9c4934f393 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -959,7 +959,7 @@ transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
 		else
 		{
 			if (IsA(colexpr, GraphPropertyRef))
-				colname = get_propgraph_property_name(castNode(GraphPropertyRef, colexpr)->propid);
+				colname = get_propgraph_property_name(castNode(GraphPropertyRef, colexpr)->propid, false);
 			else
 			{
 				ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index a33ad5ba7fd..6938d118f17 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -958,7 +958,7 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_OBJECT),
 						 errmsg("can not find label \"%s\" in property graph \"%s\" for element type \"%s\"",
-								get_propgraph_label_name(labeloid),
+								get_propgraph_label_name(labeloid, false),
 								get_rel_name(propgraphid),
 								get_graph_elem_kind_name(pf->kind))));
 		}
@@ -1127,7 +1127,7 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
 
 		if (!n)
 			elog(ERROR, "property \"%s\" of element variable \"%s\" not found",
-				 get_propgraph_property_name(gpr->propid), mapping_factor->variable);
+				 get_propgraph_property_name(gpr->propid, false), mapping_factor->variable);
 
 		return n;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2e237023d95..ca29ce74601 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1809,7 +1809,7 @@ make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elre
 		Form_pg_propgraph_element_label pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
 		const char *labelname;
 
-		labelname = get_propgraph_label_name(pgelform->pgellabelid);
+		labelname = get_propgraph_label_name(pgelform->pgellabelid, false);
 
 		if (strcmp(labelname, elalias) == 0)
 		{
@@ -1885,7 +1885,7 @@ make_propgraphdef_properties(StringInfo buf, Oid ellabelid, Oid elrelid)
 		expr = stringToNode(tmp);
 		pfree(tmp);
 
-		propname = get_propgraph_property_name(plpform->plppropid);
+		propname = get_propgraph_property_name(plpform->plppropid, false);
 
 		outlist = lappend(outlist, list_make2(makeString(propname), expr));
 	}
@@ -7871,7 +7871,7 @@ get_graph_label_expr(Node *label_expr, deparse_context *context)
 			{
 				GraphLabelRef *lref = (GraphLabelRef *) label_expr;
 
-				appendStringInfoString(buf, quote_identifier(get_propgraph_label_name(lref->labelid)));
+				appendStringInfoString(buf, quote_identifier(get_propgraph_label_name(lref->labelid, false)));
 				break;
 			}
 
@@ -11048,7 +11048,7 @@ get_rule_expr(Node *node, deparse_context *context,
 			{
 				GraphPropertyRef *gpr = (GraphPropertyRef *) node;
 
-				appendStringInfo(buf, "%s.%s", quote_identifier(gpr->elvarname), quote_identifier(get_propgraph_property_name(gpr->propid)));
+				appendStringInfo(buf, "%s.%s", quote_identifier(gpr->elvarname), quote_identifier(get_propgraph_property_name(gpr->propid, false)));
 				break;
 			}
 
index b61533fd266..98833c7720b 100644
@@ -3718,7 +3719,26 @@ get_subscription_name(Oid subid, bool missing_ok)
 }
 
 char *
-get_propgraph_label_name(Oid labeloid)
+get_propgraph_element_alias_name(Oid elemoid, bool missing_ok)
+{
+	HeapTuple	tuple;
+	char	   *elemname;
+
+	tuple = SearchSysCache1(PROPGRAPHELOID, elemoid);
+	if (!tuple)
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for element %u", elemoid);
+		return NULL;
+	}
+	elemname = pstrdup(NameStr(((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgealias));
+	ReleaseSysCache(tuple);
+
+	return elemname;
+}
+
+char *
+get_propgraph_label_name(Oid labeloid, bool missing_ok)
 {
 	HeapTuple	tuple;
 	char	   *labelname;
@@ -3726,7 +3746,8 @@ get_propgraph_label_name(Oid labeloid)
 	tuple = SearchSysCache1(PROPGRAPHLABELOID, labeloid);
 	if (!tuple)
 	{
-		elog(ERROR, "cache lookup failed for label %u", labeloid);
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for label %u", labeloid);
 		return NULL;
 	}
 	labelname = pstrdup(NameStr(((Form_pg_propgraph_label) GETSTRUCT(tuple))->pgllabel));
@@ -3736,7 +3757,7 @@ get_propgraph_label_name(Oid labeloid)
 }
 
 char *
-get_propgraph_property_name(Oid propoid)
+get_propgraph_property_name(Oid propoid, bool missing_ok)
 {
 	HeapTuple	tuple;
 	char	   *propname;
@@ -3744,7 +3765,8 @@ get_propgraph_property_name(Oid propoid)
 	tuple = SearchSysCache1(PROPGRAPHPROPOID, propoid);
 	if (!tuple)
 	{
-		elog(ERROR, "cache lookup failed for property %u", propoid);
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for property %u", propoid);
 		return NULL;
 	}
 	propname = pstrdup(NameStr(((Form_pg_propgraph_property) GETSTRUCT(tuple))->pgpname));
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 5d3444bec24..6f12ad0ea1d 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -206,8 +206,9 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
 
-extern char *get_propgraph_label_name(Oid labeloid);
-extern char *get_propgraph_property_name(Oid propoid);
+extern char *get_propgraph_element_alias_name(Oid elemoid, bool missing_ok);
+extern char *get_propgraph_label_name(Oid labeloid, bool missing_ok);
+extern char *get_propgraph_property_name(Oid propoid, bool missing_ok);
 
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index 101bdeaeb54..fd90a25c6bf 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -22,6 +22,42 @@ CREATE PROPERTY GRAPH g2
             SOURCE KEY (a) REFERENCES t1 (a)
             DESTINATION KEY (x, t) REFERENCES t3 (x, y)
     );
+-- test object addresses
+SELECT pg_identify_object_as_address('pg_propgraph_element'::regclass, oid, 0) FROM pg_propgraph_element;
+   pg_identify_object_as_address    
+------------------------------------
+ ("property graph element",{t1},{})
+ ("property graph element",{t2},{})
+ ("property graph element",{t3},{})
+ ("property graph element",{e1},{})
+ ("property graph element",{e2},{})
+(5 rows)
+
+SELECT pg_identify_object_as_address('pg_propgraph_label'::regclass, oid, 0) FROM pg_propgraph_label;
+   pg_identify_object_as_address    
+------------------------------------
+ ("property graph label",{t1},{})
+ ("property graph label",{t2},{})
+ ("property graph label",{t3l1},{})
+ ("property graph label",{t3l2},{})
+ ("property graph label",{e1},{})
+ ("property graph label",{e2},{})
+(6 rows)
+
+SELECT pg_identify_object_as_address('pg_propgraph_property'::regclass, oid, 0) FROM pg_propgraph_property;
+   pg_identify_object_as_address    
+------------------------------------
+ ("property graph property",{a},{})
+ ("property graph property",{b},{})
+ ("property graph property",{i},{})
+ ("property graph property",{j},{})
+ ("property graph property",{k},{})
+ ("property graph property",{x},{})
+ ("property graph property",{y},{})
+ ("property graph property",{z},{})
+ ("property graph property",{t},{})
+(9 rows)
+
 -- test dependencies/object descriptions
 DROP TABLE t1;  -- fail
 ERROR:  cannot drop table t1 because other objects depend on it
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 432ba471fe3..3abb658de16 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -588,6 +588,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
     ('pg_parameter_acl'::regclass, 0, 0), -- no parameter ACL
     ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
+    ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
+    ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
     ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
@@ -644,5 +647,8 @@ ORDER BY objects.classid, objects.objid, objects.objsubid;
 ("(""publication relation"",,,)")|("(""publication relation"",,)")|NULL
 ("(""publication namespace"",,,)")|("(""publication namespace"",,)")|NULL
 ("(""parameter ACL"",,,)")|("(""parameter ACL"",,)")|NULL
+("(""property graph element"",,,)")|("(""property graph element"",,)")|NULL
+("(""property graph label"",,,)")|("(""property graph label"",,)")|NULL
+("(""property graph property"",,,)")|("(""property graph property"",,)")|NULL
 -- restore normal output mode
 \a\t
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 1e309b9f935..0081bedab4b 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -29,6 +29,11 @@ CREATE PROPERTY GRAPH g2
             DESTINATION KEY (x, t) REFERENCES t3 (x, y)
     );
 
+-- test object addresses
+SELECT pg_identify_object_as_address('pg_propgraph_element'::regclass, oid, 0) FROM pg_propgraph_element;
+SELECT pg_identify_object_as_address('pg_propgraph_label'::regclass, oid, 0) FROM pg_propgraph_label;
+SELECT pg_identify_object_as_address('pg_propgraph_property'::regclass, oid, 0) FROM pg_propgraph_property;
+
 -- test dependencies/object descriptions
 
 DROP TABLE t1;  -- fail
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 93f9f9c704f..e6010b3bf3f 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -280,6 +280,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
     ('pg_parameter_acl'::regclass, 0, 0), -- no parameter ACL
     ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
+    ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
+    ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
     ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
-- 
2.39.5

---

From: Ashutosh Bapat <[email-masked]>
Date: Mon, 10 Feb 2025 11:44:00 +0530

> The attached file adds the positive tests.

Hi Junwang,
Thanks for the patch, but please post it as a separate patch in the
full patch-set, otherwise CFBot gets confused.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Junwang Zhao <[email-masked]>
Date: Mon, 10 Feb 2025 23:00:05 +0800

Hi Ashutosh,

> full patch-set, otherwise CFBot gets confused.

Ok, see the attached.

0001-0009 are the original patches of 20250127 with rebase of master
0010 fix ci error due to `Show more-intuitive titles for psql
commands`, see a14707da564e
0011 fix ci error due to unstable collation tests, we should not
depend on pg_catalog."default", I observed 002_pg_upgrade.pl failure
caused by this.
0012 trivial refactor of property graph object address

-- 
Regards
Junwang Zhao

---

From: Junwang Zhao <[email-masked]>
Date: Sun, 23 Feb 2025 20:54:53 +0800

> 0012 trivial refactor of property graph object address

Here is v11 with another trivial refactor, I add a seperate patch file 0013, in
insert_property_record, there is no need to check `type`, `typmode` and
`collation` if the property doesn't exists before, in AlterPropGraph, there is
no need to call `check_all_labels_properties` for each added vertex or edge.

Other files remain unchanged except I’ve added some missing document and
typo fix we discussed in the list but not included in the previous
patch, I included
them in 0008.

-- 
Regards
Junwang Zhao

---

From: Vladlen Popolitov <[email-masked]>
Date: Tue, 25 Feb 2025 16:47:39 +0700

Hi!

  I would ask about CREATE GRAPH PROPERTY. From my point of view it makes 
very strong
check of types including check of typmod.

  Example:

CREATE TABLE userslist (
   user_id INT primary key,
   name   VARCHAR(40)
);

CREATE TABLE groupslist (
   group_id INT primary key,
   name   VARCHAR(30)
);

CREATE TABLE memberslist (
  member_id INT primary key,
  user_id INT,
  group_id INT,
  CONSTRAINT members_users_fk1 FOREIGN KEY (user_id) REFERENCES userslist 
(user_id),
  CONSTRAINT members_groups_fk1 FOREIGN KEY (group_id) REFERENCES 
groupslist (group_id)
);

CREATE PROPERTY GRAPH members_pg
   VERTEX TABLES (
     userslist
       KEY (user_id)
       LABEL luser
       PROPERTIES ALL COLUMNS,
     groupslist
       KEY (group_id)
       LABEL lgroup
       PROPERTIES ALL COLUMNS
  )
   edge TABLES (
     memberslist
       KEY (member_id)
       SOURCE KEY (user_id) REFERENCES userslist (user_id)
       DESTINATION KEY (group_id) REFERENCES groupslist (group_id)
       LABEL lmember
       PROPERTIES ALL COLUMNS
  );

Last command fails with error:
ERROR:  property "name" data type modifier mismatch: 19 vs. 14
DETAIL:  In a property graph, a property of the same name has to have
the same type modifier in each label.

Labels luser and lgroup both have property "name" originated from tables 
userslist and groupslist
with types VARCHAR(40) and VARCHAR(30). Current implementation treats 
them as fields with
different types. I think, they should not be treated as different types. 
Typmod is additional
constrain, it does not create another type.
Documentation includes:"modifiers, that is optional constraints attached 
to a type declaration,
such as char(5) or numeric(30,2)"

Operations with values using different typmod do not require cast, they  
treated as the
same type.

Also I would add, that Oracle executes the code above without error.

I propose to exclude the check of typmod from the equivalence of types 
check in
src/backend/commands/propgraphcmds.c .

I suppose there is other reason to make this check (not the Standart 
SQL-2023 requirement,
but internal dependency), but I have not realised this reason.

-- 
Best regards,

Vladlen Popolitov.

---

From: Ashutosh Bapat <[email-masked]>
Date: Tue, 25 Feb 2025 15:50:08 +0530

> Operations with values using different typmod do not require cast, they

When a property is projected in a COLUMNS clause, it will be added to
the SELECT list of the rewritten query, which in turn could be a UNION
query. Having the same typmod for all the properties with the same
name makes it easy to write the SELECT list of UNION. Otherwise, we
have to find a common typmod for all the properties. This restriction
is not expected to be permanent but makes the first set of patches
easy, which are quite large as is. I think in some next version, we
will lift this restriction as well as lift the restriction on all
properties with the same name needing the same datatype. According to
the SQL standard, they can be of different data types as long as
there's a common type to which they all can be cast to. It may be
possible to lift this restriction in this version itself, if we find
enough developer and reviewer bandwidth.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Vladlen Popolitov <[email-masked]>
Date: Tue, 25 Feb 2025 18:21:12 +0700

Hi!

  Thank you for explanation. I expected, that it has some internal
dependency, but did not find it.
I am running graph queries now, and I will see how they interact with
typmod.

-- 
Best regards,

Vladlen Popolitov.

---

From: Junwang Zhao <[email-masked]>
Date: Wed, 26 Feb 2025 22:34:38 +0800

> typo fix we discussed in the list but not included in the previous

Current patch set failed the cfbot, I did some analysis on this, the reason for
the failure is that backend has a core dump, I extract some queries from
graph_table.sql to reproduce the issue:

----------------- queries begin -----------------
CREATE SCHEMA graph_table_tests;
GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC;
SET search_path = graph_table_tests;

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name varchar,
    price numeric
);

CREATE TABLE customers (
    customer_id integer PRIMARY KEY,
    name varchar,
    address varchar
);

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    ordered_when date
);

CREATE TABLE order_items (
    order_items_id integer PRIMARY KEY,
    order_id integer REFERENCES orders (order_id),
    product_no integer REFERENCES products (product_no),
    quantity integer
);

CREATE TABLE customer_orders (
    customer_orders_id integer PRIMARY KEY,
    customer_id integer REFERENCES customers (customer_id),
    order_id integer REFERENCES orders (order_id)
);

CREATE VIEW customers_view AS SELECT customer_id, 'redacted' ||
customer_id AS name_redacted, address FROM customers;

CREATE PROPERTY GRAPH myshop2
    VERTEX TABLES (
        products,
        customers_view KEY (customer_id) LABEL customers,
        orders
    )
    EDGE TABLES (
        order_items KEY (order_items_id)
            SOURCE KEY (order_id) REFERENCES orders (order_id)
            DESTINATION KEY (product_no) REFERENCES products (product_no),
        customer_orders KEY (customer_orders_id)
            SOURCE KEY (customer_id) REFERENCES customers_view (customer_id)
            DESTINATION KEY (order_id) REFERENCES orders (order_id)
    );

CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE
(myshop2 MATCH (c IS customers WHERE c.address = 'US')-[IS
customer_orders]->(o IS orders) COLUMNS (c.name_redacted AS
customer_name_redacted));

SELECT * FROM customers_us_redacted;         <----- abort on this query
----------------- queries end -----------------

The backend aborted in ExecCheckPermissions, a recent commit 525392d57
add the following check:

----------------- code begin -----------------
/*
* Ensure that we have at least an AccessShareLock on relations
* whose permissions need to be checked.
*
* Skip this check in a parallel worker because locks won't be
* taken until ExecInitNode() performs plan initialization.
*
* XXX: ExecCheckPermissions() in a parallel worker may be
* redundant with the checks done in the leader process, so this
* should be reviewed to ensure it’s necessary.
*/
Assert(IsParallelWorker() ||
CheckRelationOidLockedByMe(rte->relid, AccessShareLock,
true));
----------------- code end -----------------

The query causing the core dump doesn't call transformRangeGraphTable
because the graph table is not in the `from` lists, so the graph table
is not locked.

I added a trivial fix(v12-0014) that called table_open/table_close in
rewriteGraphTable, it now passed the regression test and cirrus ci test,
but I'm not sure it's the correct fix.

I hope Ashutosh can chime in and take a look at this problem.

--
Regards
Junwang Zhao

---

From: Junwang Zhao <[email-masked]>
Date: Sat, 5 Apr 2025 20:50:34 +0800

Hi Ashutosh and Peter,

On Wed, Mar 12, 2025 at 12:31 PM Ashutosh Bapat
<[email-masked]> wrote:
>
> Thanks
>
> On Tue, Mar 11, 2025 at 7:51 PM Junwang Zhao <[email-masked]> wrote:
>
> >
> > Here is a new version with Amit's fix and my trivial refactors.
> >
> > 0001-0010 is the same as Ashutosh's last email
> > 0011 is Amit's fix of the crash in ExecCheckPermissions
>
> I think that fix is correct and it fixes the crash. I need to think
> more about it - especially whether there are more places where we are
> missing RTE_GRAPH_TABLE. But will do that as time permits.
>
> > 0012 is a trivial fix that remove the test with default collation, or
> > it will fail CI, see[1]
> > 0013-0015 are some trivial fix and refactor, feel free to incorporate
> > or drop when you review them.
> >
> > [1]: https://api.cirrus-ci.com/v1/artifact/task/5290818690351104/testrun/build/testrun/regress/regress/regression.diffs
> >
>
> Thanks Junwang. I have added your patches to my local repository. Next
> time I post an updated set, I will post it along with your patches.
> Will merge them into the original patches as time permits.
>
> --
> Best Wishes,
> Ashutosh Bapat

Since this PGQ feature won't be in PG 18, I'd like to raise a discussion of
the possibility of implementing the quantifier feature, which I think is a
quite useful feature in the graph database area.

I'll start with a graph definition first.

`Person(id, name, age, sex)` with id as PK
`Knows(id, start_id, end_id, since)` with id as PK, start_id and
end_id FK referencing Person's id

insert into Person values(1, 'A', 31, 'M'), (2, 'B', 30, 'F'), (3,
'C', 33, 'M'), (4, 'D', 31, 'F'), (5, 'E', 32, 'M'), (6, 'F', 33,
'M');
insert into Knows values (1, 1, 2, '2020');  -- A knows B since 2020
insert into Knows values (2, 1, 3, '2021');  -- A knows C since 2021
insert into Knows values (3, 1, 4, '2020');  -- A knows D since 2020
insert into Knows values (4, 2, 4, '2023');  -- B knows D since 2023
insert into Knows values (5, 3, 5, '2022');  -- C knows E since 2022
insert into Knows values (6, 2, 6, '2021');  -- B knows F since 2021
insert into Knows values (7, 4, 6, '2020');  -- D knows F since 2020

Then we create a property graph:

CREATE property graph new_graph
VERTEX TABLES (Person)
EDGE TABLES (Knows);

If we want to find A's non-directly known friends within 3 hops, we can query:

select name from graph_table (new_graph match (a:Person WHERE a.name =
'A') --> (b:Person) --> (c:Person) COLUMNS (c.name))
union
select name from graph_table (new_graph match (a:Person WHERE a.name =
'A') --> (b:Person) -->(c:Person)-->(d:Person) COLUMNS (d.name));

Or if we support quantifier, we can simply the query as:

select name from graph_table (new_graph match (a:Person WHERE a.name =
'A') -->{2,3} (b:Person) COLUMNS (b.name));

In the current design of PostgreSQL, we can rewrite this pattern with
quantifiers to
the union form with some effort.

But what if the pattern is more complicated, for example:

1. select name, since from graph_table (new_graph match (a:Person
WHERE a.name = 'A') -[r:Knows]->{2,3} (b:Person) COLUMNS (b.name,
r.since));
Can we support the r.since column? I guess not, in this case r is a
variable length edge.

2. select name, count from graph_table (new_graph match (a:Person
WHERE a.name = 'A') -[r:Knows]->{2,3} (b:Person) COLUMNS (b.name,
count(r)));
Can we support this count aggregation(this is called horizontal
aggregation in Oracle's pgql)? How can the executor know the length of
the variable length edge?

3. What if the query doesn't specify the Label of edge, and there can
be different edge labels of r, can we easily do the rewrite?

I did some study of the apache age, they have fixed columns for node
labels(id, agtype)
and edge labels(id, source_id, end_id, agtype), agtype is kind of
json. So they can
resolve the above question easily.

Above are just my random thoughts of the quantifier feature, I don't have a copy
of the PGQ standard, so I'd like to hear your opinion about this.

[1] https://pgql-lang.org/spec/1.2/#horizontal-aggregation-using-group-variables

-- 
Regards
Junwang Zhao

---

From: Ashutosh Bapat <[email-masked]>
Date: Mon, 7 Apr 2025 10:49:40 +0530

> quite useful feature in the graph database area.

I agree that quantifiers feature is very useful; it's being used in
many usecases. However, it's a bit of a complex feature. IMO, we
should keep that discussion as well as the patch in a separate thread,
so that this patchset doesn't grow too large to review and also
discussion in this thread can remain focused. Once we get the current
patch set reviewed and committed we can tackle the quantifier problem
in a separate discussion. Of course that doesn't mean that we can not
start discussion, try POC and even a working patch for quantifier
support.

Peter may think otherwise.

> of the PGQ standard, so I'd like to hear your opinion about this.

I think the questions you have raised are valid. If we decide to
discuss this in a separate thread, I will start that thread just by
responding to these questions and design I have in mind.

-- 
Best Wishes,
Ashutosh Bapat

---

From: Junwang Zhao <[email-masked]>
Date: Mon, 7 Apr 2025 16:13:35 +0800

Hi Ashutosh,

> responding to these questions and design I have in mind.

I'm ok with starting a new thread for quantifier discussion, and I'd
really happy to know your design on this.

-- 
Regards
Junwang Zhao