From 3595146327c87b3255bb2efc777f6952937d4cb7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 16 Feb 2024 15:50:43 +0100
Subject: [PATCH] WIP: SQL Property Graph Queries (SQL/PGQ)

Implementation of SQL property graph queries, according to SQL/PGQ
standard (ISO 9075-16:2023).
---
 doc/src/sgml/catalogs.sgml                    |   48 +-
 doc/src/sgml/ddl.sgml                         |  229 +++-
 doc/src/sgml/features.sgml                    |    4 +-
 doc/src/sgml/func.sgml                        |   15 +
 doc/src/sgml/information_schema.sgml          |   80 ++
 .../sgml/keywords/sql2023-16-nonreserved.txt  |   27 +
 doc/src/sgml/keywords/sql2023-16-reserved.txt |   12 +
 doc/src/sgml/queries.sgml                     |  156 +++
 doc/src/sgml/ref/allfiles.sgml                |    3 +
 doc/src/sgml/ref/alter_extension.sgml         |    3 +-
 doc/src/sgml/ref/alter_property_graph.sgml    |  150 +++
 doc/src/sgml/ref/comment.sgml                 |    1 +
 doc/src/sgml/ref/create_property_graph.sgml   |  232 ++++
 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/Makefile                  |    5 +-
 src/backend/catalog/aclchk.c                  |   24 +
 src/backend/catalog/dependency.c              |   17 +
 src/backend/catalog/information_schema.sql    |  363 ++++++
 src/backend/catalog/objectaddress.c           |  140 +++
 src/backend/catalog/pg_class.c                |    2 +
 src/backend/catalog/sql_features.txt          |  100 ++
 src/backend/commands/Makefile                 |    1 +
 src/backend/commands/alter.c                  |   31 +-
 src/backend/commands/dropcmds.c               |    1 +
 src/backend/commands/event_trigger.c          |    6 +
 src/backend/commands/meson.build              |    1 +
 src/backend/commands/propgraphcmds.c          | 1001 +++++++++++++++++
 src/backend/commands/seclabel.c               |    1 +
 src/backend/commands/tablecmds.c              |   14 +
 src/backend/executor/execMain.c               |    2 +-
 src/backend/nodes/nodeFuncs.c                 |   72 ++
 src/backend/nodes/outfuncs.c                  |    5 +
 src/backend/nodes/queryjumblefuncs.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/gram.y                     |  693 +++++++++++-
 src/backend/parser/meson.build                |    1 +
 src/backend/parser/parse_clause.c             |   85 ++
 src/backend/parser/parse_collate.c            |    7 +
 src/backend/parser/parse_graphtable.c         |  251 +++++
 src/backend/parser/parse_relation.c           |   94 ++
 src/backend/parser/parse_target.c             |    5 +
 src/backend/parser/scan.l                     |   48 +
 src/backend/rewrite/Makefile                  |    1 +
 src/backend/rewrite/meson.build               |    1 +
 src/backend/rewrite/rewriteGraphTable.c       |  422 +++++++
 src/backend/rewrite/rewriteHandler.c          |   12 +
 src/backend/tcop/utility.c                    |   34 +
 src/backend/utils/adt/ruleutils.c             |  502 +++++++++
 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                   |   65 +-
 src/fe_utils/psqlscan.l                       |    8 +
 src/include/catalog/dependency.h              |    3 +
 src/include/catalog/meson.build               |    3 +
 src/include/catalog/pg_class.h                |    1 +
 src/include/catalog/pg_proc.dat               |    3 +
 src/include/catalog/pg_propgraph_element.h    |  103 ++
 src/include/catalog/pg_propgraph_label.h      |   59 +
 src/include/catalog/pg_propgraph_property.h   |   74 ++
 src/include/commands/propgraphcmds.h          |   23 +
 src/include/nodes/parsenodes.h                |  135 +++
 src/include/nodes/primnodes.h                 |   22 +
 src/include/parser/kwlist.h                   |   10 +
 src/include/parser/parse_graphtable.h         |   31 +
 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/pl/plpgsql/src/pl_gram.y                  |    1 +
 src/test/regress/expected/alter_generic.out   |   51 +-
 .../expected/create_property_graph.out        |  370 ++++++
 src/test/regress/expected/graph_table.out     |  122 ++
 src/test/regress/expected/object_address.out  |   14 +-
 src/test/regress/expected/oidjoins.out        |    9 +
 src/test/regress/parallel_schedule            |    4 +-
 src/test/regress/sql/alter_generic.sql        |   34 +
 .../regress/sql/create_property_graph.sql     |  130 +++
 src/test/regress/sql/graph_table.sql          |   96 ++
 src/test/regress/sql/object_address.sql       |    4 +-
 src/tools/pgindent/typedefs.list              |   19 +
 94 files changed, 6617 insertions(+), 49 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_label.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 0ae97d1ada..8142d6fd4e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -240,6 +240,21 @@
       <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-label"><structname>pg_propgraph_label</structname></link></entry>
+      <entry>property graph labels</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>
@@ -2115,7 +2130,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>
 
@@ -6258,6 +6274,36 @@ 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>TODO</para>
+ </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>TODO</para>
+ </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>TODO</para>
+ </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 9d7e2c756b..7333cf57e3 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1951,7 +1951,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>,
@@ -5269,6 +5269,233 @@ 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 noun
+   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.)
+  </para>
+
+  <para>
+   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 e5fa82c161..2e720c62c8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -24999,6 +24999,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 9e66be4e83..02f18eedbd 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -4171,6 +4171,86 @@ ORDER BY c.ordinal_position;
   </table>
  </sect1>
 
+ <sect1 id="infoschema-pg-edge-table-components">
+  <title><literal>pg_edge_table_components</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-key-columns">
+  <title><literal>pg_element_table_key_columns</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-labels">
+  <title><literal>pg_element_table_labels</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-properties">
+  <title><literal>pg_element_table_properties</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-tables">
+  <title><literal>pg_element_tables</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-label-properties">
+  <title><literal>pg_element_label_properties</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-labels">
+  <title><literal>pg_element_labels</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-property-data-types">
+  <title><literal>pg_property_data_types</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-pg-property-graph-privileges">
+  <title><literal>pg_property_graph_privileges</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </sect1>
+
+ <sect1 id="infoschema-property-graphs">
+  <title><literal>property_graphs</literal></title>
+
+  <para>
+   TODO
+  </para>
+ </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 648b283b06..5b088331c6 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -2744,4 +2744,160 @@ 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>
+
+   <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 4a42999b18..990d2b1af3 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..e4e56ad652
--- /dev/null
+++ b/doc/src/sgml/ref/alter_property_graph.sgml
@@ -0,0 +1,150 @@
+<!--
+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_name</replaceable> [, ...] ) ]
+    [ {EDGE|RELATIONSHIP} TABLES ( { <replaceable class="parameter">edge_table_name</replaceable> SOURCE <replaceable class="parameter">source_table_name</replaceable> DESTINATION <replaceable class="parameter">dest_table_name</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>
+   TODO
+  </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>TODO</term>
+    <listitem>
+     <para>
+      TODO
+     </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>Examples</title>
+
+  <para>
+<programlisting>
+ALTER PROPERTY GRAPH g1 RENAME TO g2;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   TODO
+  </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..257186a6a9
--- /dev/null
+++ b/doc/src/sgml/ref/create_property_graph.sgml
@@ -0,0 +1,232 @@
+<!--
+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</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>
+  <title>Notes</title>
+
+  <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 9d27b7fcde..35a3a0234e 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 cc7d797159..c613f16b43 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1218,7 +1218,7 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
 
         <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
@@ -1258,9 +1258,9 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
         <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>
@@ -1528,6 +1528,7 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
 
       <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>
@@ -1536,10 +1537,10 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
 
         <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 2db66bbf37..8f43a1bbd7 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 9917df7839..2ab3eecb80 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 aa94f6adf6..d3fb783608 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/Makefile b/src/backend/catalog/Makefile
index 196ecafc90..e7fc979fc2 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -118,7 +118,10 @@ 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_label.h \
+	pg_propgraph_property.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h syscache_ids.h syscache_info.h system_fk_info.h
 
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 023938682d..7c3360c5a8 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);
@@ -2761,6 +2778,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;
@@ -2887,6 +2907,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;
@@ -3023,6 +3046,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 eadcf6af0d..30c6f77044 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -50,6 +50,9 @@
 #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_label.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
@@ -70,6 +73,7 @@
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
 #include "commands/policy.h"
+#include "commands/propgraphcmds.h"
 #include "commands/publicationcmds.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
@@ -1451,6 +1455,9 @@ doDeletion(const ObjectAddress *object, int flags)
 		case OCLASS_AM:
 		case OCLASS_AMOP:
 		case OCLASS_AMPROC:
+		case OCLASS_PROPGRAPH_ELEMENT:
+		case OCLASS_PROPGRAPH_LABEL:
+		case OCLASS_PROPGRAPH_PROPERTY:
 		case OCLASS_SCHEMA:
 		case OCLASS_TSPARSER:
 		case OCLASS_TSDICT:
@@ -2163,6 +2170,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;
@@ -2900,6 +2908,15 @@ getObjectClass(const ObjectAddress *object)
 		case PolicyRelationId:
 			return OCLASS_POLICY;
 
+		case PropgraphElementRelationId:
+			return OCLASS_PROPGRAPH_ELEMENT;
+
+		case PropgraphLabelRelationId:
+			return OCLASS_PROPGRAPH_LABEL;
+
+		case PropgraphPropertyRelationId:
+			return OCLASS_PROPGRAPH_PROPERTY;
+
 		case PublicationNamespaceRelationId:
 			return OCLASS_PUBLICATION_NAMESPACE;
 
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index c402ae7274..aab28fd708 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -3009,3 +3009,366 @@ 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_label l
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND l.pglelid = e.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(pr.pgpexpr, e.pgerelid) AS character_data) AS property_expression
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l, pg_propgraph_property pr
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND l.pglelid = e.oid
+          AND pr.pgplabelid = 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_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_property pr
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND l.pglelid = e.oid
+          AND pr.pgplabelid = 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 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
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND l.pglelid = e.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 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(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 360c6b2ba6..bf0ae45678 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -47,6 +47,9 @@
 #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_label.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 +373,48 @@ 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 label",
+		PropgraphLabelRelationId,
+		PropgraphLabelObjectIndexId,
+		-1,
+		PROPGRAPHLABELNAME,
+		Anum_pg_propgraph_label_oid,
+		Anum_pg_propgraph_label_pgllabel,
+		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,
@@ -680,6 +725,9 @@ static const struct object_type_map
 	{
 		"foreign table", OBJECT_FOREIGN_TABLE
 	},
+	{
+		"property graph", OBJECT_PROPGRAPH
+	},
 	{
 		"table column", OBJECT_COLUMN
 	},
@@ -849,6 +897,18 @@ static const struct object_type_map
 	{
 		"policy", OBJECT_POLICY
 	},
+	/* OCLASS_PROPGRAPH_ELEMENT */
+	{
+		"property graph element", -1
+	},
+	/* OCLASS_PROPGRAPH_LABEL */
+	{
+		"property graph label", -1
+	},
+	/* OCLASS_PROPGRAPH_PROPERTY */
+	{
+		"property graph property", -1
+	},
 	/* OCLASS_PUBLICATION */
 	{
 		"publication", OBJECT_PUBLICATION
@@ -989,6 +1049,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,
@@ -1397,6 +1458,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,
@@ -2312,6 +2380,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:
@@ -2431,6 +2500,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:
@@ -3961,6 +4031,43 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_PROPGRAPH_ELEMENT:
+			{
+				HeapTuple	tup;
+				Form_pg_propgraph_element pgeform;
+				StringInfoData rel;
+
+				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);
+
+				initStringInfo(&rel);
+				getRelationDescription(&rel, pgeform->pgepgid, false);
+
+				/* translator: second %s is, e.g., "property graph %s" */
+				appendStringInfo(&buffer, _("element %s in %s"),
+								 NameStr(pgeform->pgealias), rel.data);
+				pfree(rel.data);
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_PROPGRAPH_LABEL:
+			{
+				appendStringInfo(&buffer, "label %u TODO", object->objectId);
+				break;
+			}
+
+		case OCLASS_PROPGRAPH_PROPERTY:
+			{
+				appendStringInfo(&buffer, "property %u TODO", object->objectId);
+				break;
+			}
+
 		case OCLASS_PUBLICATION:
 			{
 				char	   *pubname = get_publication_name(object->objectId,
@@ -4148,6 +4255,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"),
@@ -4568,6 +4679,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "policy");
 			break;
 
+		case OCLASS_PROPGRAPH_ELEMENT:
+			appendStringInfoString(&buffer, "property graph element");
+			break;
+
+		case OCLASS_PROPGRAPH_LABEL:
+			appendStringInfoString(&buffer, "property graph label");
+			break;
+
+		case OCLASS_PROPGRAPH_PROPERTY:
+			appendStringInfoString(&buffer, "property graph property");
+			break;
+
 		case OCLASS_PUBLICATION:
 			appendStringInfoString(&buffer, "publication");
 			break;
@@ -4651,6 +4774,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");
@@ -5810,6 +5936,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_PROPGRAPH_ELEMENT:
+			appendStringInfo(&buffer, "%u TODO", object->objectId);
+			break;
+
+		case OCLASS_PROPGRAPH_LABEL:
+			appendStringInfo(&buffer, "%u TODO", object->objectId);
+			break;
+
+		case OCLASS_PROPGRAPH_PROPERTY:
+			appendStringInfo(&buffer, "%u TODO", object->objectId);
+			break;
+
 		case OCLASS_PUBLICATION:
 			{
 				char	   *pubname;
@@ -6118,6 +6256,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 925d15a2c3..1346c42ec9 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 XXX
+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			NO	XXX
+G941	Implicit removal of incomplete edges			NO	XXX
+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 48f7348f91..6260d6b79e 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 cd740140fd..af31144a17 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -378,6 +378,7 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_MATVIEW:
 		case OBJECT_INDEX:
 		case OBJECT_FOREIGN_TABLE:
+		case OBJECT_PROPGRAPH:
 			return RenameRelation(stmt);
 
 		case OBJECT_COLUMN:
@@ -534,6 +535,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
 		case OBJECT_TABLE:
 		case OBJECT_VIEW:
 		case OBJECT_MATVIEW:
+		case OBJECT_PROPGRAPH:
 			address = AlterTableNamespace(stmt,
 										  oldSchemaAddr ? &oldNspOid : NULL);
 			break;
@@ -685,6 +687,9 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 		case OCLASS_EVENT_TRIGGER:
 		case OCLASS_PARAMETER_ACL:
 		case OCLASS_POLICY:
+		case OCLASS_PROPGRAPH_ELEMENT:
+		case OCLASS_PROPGRAPH_LABEL:
+		case OCLASS_PROPGRAPH_PROPERTY:
 		case OCLASS_PUBLICATION:
 		case OCLASS_PUBLICATION_NAMESPACE:
 		case OCLASS_PUBLICATION_REL:
@@ -907,6 +912,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:
@@ -916,16 +922,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 ab11ab500b..4dbc48ea13 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1167,6 +1167,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_OPFAMILY:
 		case OBJECT_POLICY:
 		case OBJECT_PROCEDURE:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_PUBLICATION:
 		case OBJECT_PUBLICATION_NAMESPACE:
 		case OBJECT_PUBLICATION_REL:
@@ -1247,6 +1248,9 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
 		case OCLASS_POLICY:
+		case OCLASS_PROPGRAPH_ELEMENT:
+		case OCLASS_PROPGRAPH_LABEL:
+		case OCLASS_PROPGRAPH_PROPERTY:
 		case OCLASS_PUBLICATION:
 		case OCLASS_PUBLICATION_NAMESPACE:
 		case OCLASS_PUBLICATION_REL:
@@ -2265,6 +2269,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:
@@ -2349,6 +2354,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 6dd00a4abd..34dd12d7d5 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..2594886134
--- /dev/null
+++ b/src/backend/commands/propgraphcmds.c
@@ -0,0 +1,1001 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_label.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/syscache.h"
+
+
+struct element_info
+{
+	Oid			elementid;
+	char		kind;
+	Oid			relid;
+	char	   *aliasname;
+	ArrayType  *key;
+
+	char	   *srcvertex;
+	Oid			srcvertexid;
+	ArrayType  *srckey;
+	ArrayType  *srcref;
+
+	char	   *destvertex;
+	Oid			destvertexid;
+	ArrayType  *destkey;
+	ArrayType  *destref;
+
+	List	   *labels;
+};
+
+
+static ArrayType *propgraph_element_get_key(ParseState *pstate, const List *key_clause, int location, Relation element_rel);
+static ArrayType *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
+static void 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 labeloid, Oid pgerelid, const PropGraphProperties *properties);
+static void insert_property_record(Oid graphid, Oid labeloid, const char *propname, const Expr *expr);
+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);
+
+
+/*
+ * 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;
+
+	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, vertex->location, rel);
+
+		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, edge->location, rel);
+
+		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)));
+
+		if (!edge->esrckey || !edge->esrcvertexcols || !edge->edestkey || !edge->edestvertexcols)
+			elog(ERROR, "TODO foreign key support");
+
+		srcrel = table_open(srcrelid, NoLock);
+		destrel = table_open(destrelid, NoLock);
+
+		if (list_length(edge->esrckey) != list_length(edge->esrcvertexcols))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("mismatching number of columns in source vertex definition of edge \"%s\"",
+							einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		if (list_length(edge->edestkey) != list_length(edge->edestvertexcols))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("mismatching number of columns in destination vertex definition of edge \"%s\"",
+							einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		einfo->srckey = array_from_column_list(pstate, edge->esrckey, edge->location, rel);
+		einfo->srcref = array_from_column_list(pstate, edge->esrcvertexcols, edge->location, srcrel);
+		einfo->destkey = array_from_column_list(pstate, edge->edestkey, edge->location, rel);
+		einfo->destref = array_from_column_list(pstate, edge->edestvertexcols, edge->location, destrel);
+
+		/* TODO: various consistency checks */
+
+		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);
+
+		insert_element_record(pgaddress, vinfo);
+	}
+
+	foreach(lc, edge_infos)
+	{
+		struct element_info *einfo = lfirst(lc);
+		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;
+			if (strcmp(vinfo->aliasname, einfo->destvertex) == 0)
+				einfo->destvertexid = vinfo->elementid;
+			if (einfo->srcvertexid && einfo->destvertexid)
+				break;
+		}
+		Assert(einfo->srcvertexid);
+		Assert(einfo->destvertexid);
+		insert_element_record(pgaddress, einfo);
+	}
+
+	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, int location, Relation element_rel)
+{
+	ArrayType  *a;
+
+	if (key_clause == NIL)
+	{
+		Oid			pkidx = RelationGetPrimaryKeyIndex(element_rel);
+
+		if (!pkidx)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("element table key must be specified for table without primary key"),
+					 parser_errposition(pstate, location)));
+		else
+		{
+			Relation	indexDesc;
+			int			numattrs;
+			Datum	   *attnumsd;
+
+			indexDesc = index_open(pkidx, AccessShareLock);
+			numattrs = indexDesc->rd_index->indkey.dim1;
+			attnumsd = palloc_array(Datum, numattrs);
+			for (int i = 0; i < numattrs; i++)
+				attnumsd[i] = Int16GetDatum(indexDesc->rd_index->indkey.values[i]);
+			a = construct_array_builtin(attnumsd, numattrs, INT2OID);
+			index_close(indexDesc, NoLock);
+		}
+	}
+	else
+	{
+		a = array_from_column_list(pstate, key_clause, location, element_rel);
+	}
+
+	return a;
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Insert a record for an element into the pg_propgraph_element catalog.  Also
+ * inserts labels and properties into their respective catalogs.
+ */
+static void
+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;
+
+	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);
+
+	/* Add dependency on the relation */
+	ObjectAddressSet(referenced, RelationRelationId, einfo->relid);
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Add dependencies on vertices */
+	/* TODO: columns */
+	if (einfo->srcvertexid)
+	{
+		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+	if (einfo->destvertexid)
+	{
+		ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->destvertexid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	table_close(rel, NoLock);
+
+	if (einfo->labels)
+	{
+		ListCell   *lc;
+
+		foreach(lc, einfo->labels)
+		{
+			PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+			Oid			labeloid;
+
+			if (lp->label)
+				labeloid = insert_label_record(graphid, peoid, lp->label);
+			else
+				labeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+			insert_property_records(graphid, labeloid, einfo->relid, lp->properties);
+		}
+	}
+	else
+	{
+		Oid			labeloid;
+		PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+		pr->all = true;
+		pr->location = -1;
+
+		labeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+		insert_property_records(graphid, labeloid, einfo->relid, pr);
+	}
+}
+
+/*
+ * Insert a record for a label into the pg_propgraph_label catalog.
+ */
+static Oid
+insert_label_record(Oid graphid, Oid peoid, const char *label)
+{
+	Relation	rel;
+	NameData	labelname;
+	Oid			labeloid;
+	Datum		values[Natts_pg_propgraph_label] = {0};
+	bool		nulls[Natts_pg_propgraph_label] = {0};
+	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);
+	values[Anum_pg_propgraph_label_pglelid - 1] = ObjectIdGetDatum(peoid);
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PropgraphLabelRelationId, labeloid);
+
+	/* Add dependency on the property graph element */
+	ObjectAddressSet(referenced, PropgraphElementRelationId, peoid);
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+	table_close(rel, NoLock);
+
+	return labeloid;
+}
+
+/*
+ * Insert records for properties into the pg_propgraph_property catalog.
+ */
+static void
+insert_property_records(Oid graphid, Oid labeloid, 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_OTHER);
+
+	foreach(lc, tp)
+	{
+		TargetEntry *te = lfirst_node(TargetEntry, lc);
+
+		insert_property_record(graphid, labeloid, te->resname, te->expr);
+	}
+}
+
+/*
+ * Insert one record for a property into the pg_propgraph_property catalog.
+ */
+static void
+insert_property_record(Oid graphid, Oid labeloid, const char *propname, const Expr *expr)
+{
+	Relation	rel;
+	NameData	propnamedata;
+	Oid			propoid;
+	Datum		values[Natts_pg_propgraph_property] = {0};
+	bool		nulls[Natts_pg_propgraph_property] = {0};
+	HeapTuple	tup;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	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(exprType((const Node *) expr));
+	values[Anum_pg_propgraph_property_pgplabelid - 1] = ObjectIdGetDatum(labeloid);
+	values[Anum_pg_propgraph_property_pgpexpr - 1] = CStringGetTextDatum(nodeToString(expr));
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+	CatalogTupleInsert(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PropgraphPropertyRelationId, propoid);
+
+	/* Add dependency on the property graph label */
+	ObjectAddressSet(referenced, PropgraphLabelRelationId, labeloid);
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+	table_close(rel, NoLock);
+}
+
+/*
+ * 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;
+
+		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)
+			elog(ERROR, "TODO");
+
+		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, vertex->location, rel);
+
+		vinfo->labels = vertex->labels;
+
+		table_close(rel, NoLock);
+
+		insert_element_record(pgaddress, vinfo);
+	}
+
+	CommandCounterIncrement();
+
+	foreach(lc, stmt->add_edge_tables)
+	{
+		PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc);
+		struct element_info *einfo;
+		Relation	rel;
+		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)
+			elog(ERROR, "TODO");
+
+		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, edge->location, rel);
+
+		einfo->srcvertexid = get_vertex_oid(pstate, pgrelid, edge->esrcvertex, edge->location);
+		einfo->destvertexid = get_vertex_oid(pstate, pgrelid, edge->edestvertex, edge->location);
+
+		if (!edge->esrckey || !edge->esrcvertexcols || !edge->edestkey || !edge->edestvertexcols)
+			elog(ERROR, "TODO foreign key support");
+
+		srcrelid = get_element_relid(einfo->srcvertexid);
+		destrelid = get_element_relid(einfo->destvertexid);
+
+		srcrel = table_open(srcrelid, AccessShareLock);
+		destrel = table_open(destrelid, AccessShareLock);
+
+		if (list_length(edge->esrckey) != list_length(edge->esrcvertexcols))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("mismatching number of columns in source vertex definition of edge \"%s\"",
+							einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		if (list_length(edge->edestkey) != list_length(edge->edestvertexcols))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("mismatching number of columns in destination vertex definition of edge \"%s\"",
+							einfo->aliasname),
+					 parser_errposition(pstate, edge->location)));
+
+		einfo->srckey = array_from_column_list(pstate, edge->esrckey, edge->location, rel);
+		einfo->srcref = array_from_column_list(pstate, edge->esrcvertexcols, edge->location, srcrel);
+		einfo->destkey = array_from_column_list(pstate, edge->edestkey, edge->location, rel);
+		einfo->destref = array_from_column_list(pstate, edge->edestvertexcols, edge->location, destrel);
+
+		/* TODO: various consistency checks */
+
+		einfo->labels = edge->labels;
+
+		table_close(destrel, NoLock);
+		table_close(srcrel, NoLock);
+
+		table_close(rel, NoLock);
+
+		insert_element_record(pgaddress, einfo);
+	}
+
+	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);
+	}
+
+	foreach(lc, stmt->add_labels)
+	{
+		PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+		Oid			peoid;
+		Oid			pgerelid;
+		Oid			labeloid;
+
+		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);
+
+		labeloid = insert_label_record(pgrelid, peoid, lp->label);
+		insert_property_records(pgrelid, labeloid, pgerelid, lp->properties);
+	}
+
+	if (stmt->drop_label)
+	{
+		Oid			peoid;
+		Oid			labeloid;
+		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(peoid),
+								   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));
+
+		ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid);
+		performDeletion(&obj, stmt->drop_behavior, 0);
+	}
+
+	if (stmt->add_properties)
+	{
+		Oid			peoid;
+		Oid			pgerelid;
+		Oid			labeloid;
+
+		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);
+
+		labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+								   Anum_pg_propgraph_label_oid,
+								   ObjectIdGetDatum(peoid),
+								   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));
+
+		insert_property_records(pgrelid, labeloid, pgerelid, stmt->add_properties);
+	}
+
+	if (stmt->drop_properties)
+	{
+		Oid			peoid;
+		Oid			labeloid;
+		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(peoid),
+								   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->drop_label),
+					parser_errposition(pstate, -1));
+
+		foreach(lc, stmt->drop_properties)
+		{
+			char	   *propname = strVal(lfirst(lc));
+			Oid			propoid;
+
+			propoid = GetSysCacheOid2(PROPGRAPHPROPNAME,
+									  Anum_pg_propgraph_property_oid,
+									  ObjectIdGetDatum(labeloid),
+									  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));
+
+			ObjectAddressSet(obj, PropgraphPropertyRelationId, propoid);
+			performDeletion(&obj, stmt->drop_behavior, 0);
+		}
+	}
+
+	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;
+}
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5607273bf9..0a5e34abe9 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -49,6 +49,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_LARGEOBJECT:
 		case OBJECT_MATVIEW:
 		case OBJECT_PROCEDURE:
+		case OBJECT_PROPGRAPH:
 		case OBJECT_PUBLICATION:
 		case OBJECT_ROLE:
 		case OBJECT_ROUTINE:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c61f9305c2..f8888764d9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -298,6 +298,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}
 };
 
@@ -1528,6 +1534,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);
@@ -13989,6 +13999,9 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
 			case OCLASS_EXTENSION:
 			case OCLASS_EVENT_TRIGGER:
 			case OCLASS_PARAMETER_ACL:
+			case OCLASS_PROPGRAPH_ELEMENT:
+			case OCLASS_PROPGRAPH_LABEL:
+			case OCLASS_PROPGRAPH_PROPERTY:
 			case OCLASS_PUBLICATION:
 			case OCLASS_PUBLICATION_NAMESPACE:
 			case OCLASS_PUBLICATION_REL:
@@ -14814,6 +14827,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:
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 940499cc61..3641902661 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -594,7 +594,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 */
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 6ba8e73256..fc78c9e315 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -264,6 +264,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 */
@@ -500,6 +503,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;
 	}
@@ -999,6 +1005,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 */
@@ -2034,6 +2043,7 @@ expression_tree_walker_impl(Node *node,
 		case T_RangeTblRef:
 		case T_SortGroupClause:
 		case T_CTESearchClause:
+		case T_GraphPropertyRef:
 			/* primitive node types with no expression subnodes */
 			break;
 		case T_WithCheckOption:
@@ -2534,6 +2544,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));
@@ -2727,6 +2757,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:
@@ -3727,6 +3763,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:
@@ -4278,6 +4318,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;
@@ -4436,6 +4488,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 2c30bba212..7ec49cc76d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -555,6 +555,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/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 426112fa37..9fe9b56092 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -395,6 +395,11 @@ _jumbleRangeTblEntry(JumbleState *jstate, Node *node)
 		case RTE_NAMEDTUPLESTORE:
 			JUMBLE_STRING(enrname);
 			break;
+		case RTE_GRAPH_TABLE:
+			JUMBLE_FIELD(relid);
+			JUMBLE_NODE(graph_pattern);
+			JUMBLE_NODE(graph_table_columns);
+			break;
 		case RTE_RESULT:
 			break;
 		default:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b1e2f2b440..609166bc0c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -418,6 +418,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 0b98f0856e..f422d49440 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 300691cc4d..adb69159d2 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1206,6 +1206,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;
 			}
 		}
 	}
@@ -2267,6 +2271,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 401c16686c..b03dc4cdb0 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_merge.o \
 	parse_node.o \
 	parse_oper.o \
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 130f7fc7c3..bcc51cc5ff 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
@@ -660,6 +661,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.
@@ -677,6 +708,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			BRACKET_RIGHT_ARROW LEFT_ARROW_BRACKET MINUS_LEFT_BRACKET RIGHT_BRACKET_MINUS
 
 /*
  * If you want to make any keyword changes, update the keyword table in
@@ -703,18 +735,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 ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
+	EACH EDGE ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_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
 
@@ -735,7 +767,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
 
-	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
+	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NODE NONE
 	NORMALIZE NORMALIZED
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
 	NULLS_P NUMERIC
@@ -747,19 +779,19 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD
 	PLACING 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
 
 	RANGE READ REAL REASSIGN RECHECK 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
 
 	SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
 	SEQUENCE SEQUENCES
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
-	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
+	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
 	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
@@ -772,7 +804,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
 
@@ -809,6 +841,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %token		MODE_PLPGSQL_ASSIGN2
 %token		MODE_PLPGSQL_ASSIGN3
 
+ /*
+  * FIXME: The brace characters are assigned token symbols because if we
+  * mention literal braces in the rules then the ecpg parser assembly breaks.
+  */
+%token		LEFT_BRACE RIGHT_BRACE
 
 /* Precedence: lowest to highest */
 %left		UNION EXCEPT
@@ -867,7 +904,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	UNBOUNDED		/* 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
-%left		Op OPERATOR		/* multi-character ops and user-defined operators */
+%left		Op OPERATOR LEFT_ARROW RIGHT_ARROW	/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
 %left		'^'
@@ -998,6 +1035,7 @@ stmt:
 			| AlterOperatorStmt
 			| AlterTypeStmt
 			| AlterPolicyStmt
+			| AlterPropGraphStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
 			| AlterTableStmt
@@ -1038,6 +1076,7 @@ stmt:
 			| AlterOpFamilyStmt
 			| CreatePolicyStmt
 			| CreatePLangStmt
+			| CreatePropGraphStmt
 			| CreateSchemaStmt
 			| CreateSeqStmt
 			| CreateStmt
@@ -6892,6 +6931,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; }
@@ -7748,6 +7788,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));
@@ -9065,6 +9114,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
@@ -9365,6 +9774,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);
@@ -9990,6 +10409,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);
@@ -10333,6 +10772,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);
@@ -13409,6 +13857,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);
@@ -14590,6 +15049,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 LEFT_ARROW 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); }
@@ -15069,6 +15532,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 LEFT_ARROW 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
@@ -16173,6 +16640,8 @@ MathOp:		 '+'									{ $$ = "+"; }
 			| LESS_EQUALS							{ $$ = "<="; }
 			| GREATER_EQUALS						{ $$ = ">="; }
 			| NOT_EQUALS							{ $$ = "<>"; }
+			| LEFT_ARROW							{ $$ = "<-"; }
+			| RIGHT_ARROW							{ $$ = "->"; }
 		;
 
 qual_Op:	Op
@@ -16684,6 +17153,192 @@ 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 ]- */
+			| LEFT_ARROW_BRACKET opt_colid opt_is_label_expression where_clause RIGHT_BRACKET_MINUS
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_LEFT;
+					gep->variable = $2;
+					gep->labelexpr = $3;
+					gep->whereClause = $4;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge pointing right: -[ xxx ]-> */
+			| MINUS_LEFT_BRACKET opt_colid opt_is_label_expression where_clause BRACKET_RIGHT_ARROW
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_RIGHT;
+					gep->variable = $2;
+					gep->labelexpr = $3;
+					gep->whereClause = $4;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* full edge any direction: -[ xxx ]- */
+			| MINUS_LEFT_BRACKET opt_colid opt_is_label_expression where_clause RIGHT_BRACKET_MINUS
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_ANY;
+					gep->variable = $2;
+					gep->labelexpr = $3;
+					gep->whereClause = $4;
+					gep->location = @1;
+
+					$$ = (Node *) gep;
+				}
+			/* abbreviated edge patterns */
+			| LEFT_ARROW
+				{
+					GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+					gep->kind = EDGE_PATTERN_LEFT;
+					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:
+			LEFT_BRACE Iconst RIGHT_BRACE				{ $$ = list_make2_int($2, $2); }
+			| LEFT_BRACE ',' Iconst RIGHT_BRACE			{ $$ = list_make2_int(0, $3); }
+			| LEFT_BRACE Iconst ',' Iconst RIGHT_BRACE	{ $$ = 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
@@ -17202,6 +17857,7 @@ unreserved_keyword:
 			| DELIMITERS
 			| DEPENDS
 			| DEPTH
+			| DESTINATION
 			| DETACH
 			| DICTIONARY
 			| DISABLE_P
@@ -17211,6 +17867,7 @@ unreserved_keyword:
 			| DOUBLE_P
 			| DROP
 			| EACH
+			| EDGE
 			| ENABLE_P
 			| ENCODING
 			| ENCRYPTED
@@ -17238,6 +17895,7 @@ unreserved_keyword:
 			| GENERATED
 			| GLOBAL
 			| GRANTED
+			| GRAPH
 			| GROUPS
 			| HANDLER
 			| HEADER_P
@@ -17300,6 +17958,7 @@ unreserved_keyword:
 			| NFKC
 			| NFKD
 			| NO
+			| NODE
 			| NORMALIZED
 			| NOTHING
 			| NOTIFY
@@ -17338,6 +17997,8 @@ unreserved_keyword:
 			| PROCEDURE
 			| PROCEDURES
 			| PROGRAM
+			| PROPERTIES
+			| PROPERTY
 			| PUBLICATION
 			| QUOTE
 			| RANGE
@@ -17349,6 +18010,7 @@ unreserved_keyword:
 			| REFERENCING
 			| REFRESH
 			| REINDEX
+			| RELATIONSHIP
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
@@ -17388,6 +18050,7 @@ unreserved_keyword:
 			| SIMPLE
 			| SKIP
 			| SNAPSHOT
+			| SOURCE
 			| SQL_P
 			| STABLE
 			| STANDALONE_P
@@ -17434,6 +18097,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERTEX
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -17472,6 +18136,7 @@ col_name_keyword:
 			| EXISTS
 			| EXTRACT
 			| FLOAT_P
+			| GRAPH_TABLE
 			| GREATEST
 			| GROUPING
 			| INOUT
@@ -17757,6 +18422,7 @@ bare_label_keyword:
 			| DEPENDS
 			| DEPTH
 			| DESC
+			| DESTINATION
 			| DETACH
 			| DICTIONARY
 			| DISABLE_P
@@ -17768,6 +18434,7 @@ bare_label_keyword:
 			| DOUBLE_P
 			| DROP
 			| EACH
+			| EDGE
 			| ELSE
 			| ENABLE_P
 			| ENCODING
@@ -17803,6 +18470,8 @@ bare_label_keyword:
 			| GENERATED
 			| GLOBAL
 			| GRANTED
+			| GRAPH
+			| GRAPH_TABLE
 			| GREATEST
 			| GROUPING
 			| GROUPS
@@ -17891,6 +18560,7 @@ bare_label_keyword:
 			| NFKC
 			| NFKD
 			| NO
+			| NODE
 			| NONE
 			| NORMALIZE
 			| NORMALIZED
@@ -17942,6 +18612,8 @@ bare_label_keyword:
 			| PROCEDURE
 			| PROCEDURES
 			| PROGRAM
+			| PROPERTIES
+			| PROPERTY
 			| PUBLICATION
 			| QUOTE
 			| RANGE
@@ -17955,6 +18627,7 @@ bare_label_keyword:
 			| REFERENCING
 			| REFRESH
 			| REINDEX
+			| RELATIONSHIP
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
@@ -18001,6 +18674,7 @@ bare_label_keyword:
 			| SMALLINT
 			| SNAPSHOT
 			| SOME
+			| SOURCE
 			| SQL_P
 			| STABLE
 			| STANDALONE_P
@@ -18065,6 +18739,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 8e8295640b..4c03346dec 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_merge.c',
   'parse_node.c',
   'parse_oper.c',
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index d2ac86777c..69261b9ea8 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,
@@ -896,6 +899,76 @@ 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;
+	ParseState *pstate2;
+	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;
+
+	gp = transformGraphPattern(gpstate, rgt->graph_pattern);
+
+	pstate2 = make_parsestate(NULL);
+	pstate2->p_pre_columnref_hook = graph_table_property_reference;
+	pstate2->p_ref_hook_state = gpstate;
+
+	foreach(lc, rgt->columns)
+	{
+		ResTarget  *rt = lfirst_node(ResTarget, lc);
+		Node	   *colexpr;
+		TargetEntry *te;
+		char	   *colname;
+
+		colexpr = transformExpr(pstate2, rt->val, EXPR_KIND_OTHER);
+
+		if (rt->name)
+			colname = rt->name;
+		else
+		{
+			if (IsA(colexpr, GraphPropertyRef))
+				colname = pstrdup(castNode(GraphPropertyRef, colexpr)->propname);
+			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);
+
+	return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true);
+}
+
 /*
  * transformRangeTableSample --- transform a TABLESAMPLE clause
  *
@@ -1115,6 +1188,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_graphtable.c b/src/backend/parser/parse_graphtable.c
new file mode 100644
index 0000000000..8679b72627
--- /dev/null
+++ b/src/backend/parser/parse_graphtable.c
@@ -0,0 +1,251 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_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/relcache.h"
+
+
+/*
+ * Get the type of a property.
+ */
+static Oid
+get_property_type(Oid graphid, const char *propname)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tuple;
+	Oid			result = InvalidOid;
+
+	rel = table_open(PropgraphPropertyRelationId, RowShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_property_pgppgid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(graphid));
+	ScanKeyInit(&key[1],
+				Anum_pg_propgraph_property_pgpname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(propname));
+
+	scan = systable_beginscan(rel, PropgraphPropertyGraphNameIndexId, true, NULL, 2, key);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_property prop = (Form_pg_propgraph_property) GETSTRUCT(tuple);
+
+		result = prop->pgptypid;
+		break;
+	}
+
+	systable_endscan(scan);
+	table_close(rel, RowShareLock);
+
+	if (!result)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("property \"%s\" does not exist", propname));
+
+	return result;
+}
+
+/*
+ * Resolve a property reference.
+ */
+Node *
+graph_table_property_reference(ParseState *pstate, ColumnRef *cref)
+{
+	GraphTableParseState *gpstate = pstate->p_ref_hook_state;
+	GraphPropertyRef *gpr = makeNode(GraphPropertyRef);
+
+	gpr->location = cref->location;
+
+	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))
+			ereport(ERROR,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("graph pattern variable \"%s\" does not exist", elvarname),
+					parser_errposition(pstate, cref->location));
+
+		gpr->elvarname = elvarname;
+		gpr->propname = propname;
+
+	}
+	else
+		elog(ERROR, "invalid property reference");
+
+	gpr->typeId = get_property_type(gpstate->graphid, gpr->propname);
+
+	return (Node *) gpr;
+}
+
+/*
+ * 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;
+				GraphLabelRef *lref;
+
+				Assert(list_length(cref->fields) == 1);
+				labelname = strVal(linitial(cref->fields));
+
+				lref = makeNode(GraphLabelRef);
+				lref->labelname = labelname;
+				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(GraphTableParseState *gpstate, GraphElementPattern *gep)
+{
+	ParseState *pstate2;
+
+	pstate2 = make_parsestate(NULL);
+	pstate2->p_pre_columnref_hook = graph_table_property_reference;
+	pstate2->p_ref_hook_state = gpstate;
+
+	if (gep->variable)
+		gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable)));
+
+	gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
+
+	gep->whereClause = transformExpr(pstate2, gep->whereClause, EXPR_KIND_OTHER);
+	assign_expr_collations(pstate2, gep->whereClause);
+
+	return (Node *) gep;
+}
+
+/*
+ * Transform a path term (list of GraphElementPattern's).
+ */
+static Node *
+transformPathTerm(GraphTableParseState *gpstate, List *path_term)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach(lc, path_term)
+	{
+		Node	   *n = transformGraphElementPattern(gpstate, lfirst_node(GraphElementPattern, lc));
+
+		result = lappend(result, n);
+	}
+
+	return (Node *) result;
+}
+
+/*
+ * Transform a path pattern list (list of path terms).
+ */
+static Node *
+transformPathPatternList(GraphTableParseState *gpstate, List *path_pattern)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach(lc, path_pattern)
+	{
+		Node	   *n = transformPathTerm(gpstate, lfirst(lc));
+
+		result = lappend(result, n);
+	}
+
+	return (Node *) result;
+}
+
+/*
+ * Transform a GraphPattern.
+ */
+Node *
+transformGraphPattern(GraphTableParseState *gpstate, GraphPattern *graph_pattern)
+{
+	ParseState *pstate2;
+
+	pstate2 = make_parsestate(NULL);
+	pstate2->p_pre_columnref_hook = graph_table_property_reference;
+	pstate2->p_ref_hook_state = gpstate;
+
+	graph_pattern->path_pattern_list = (List *) transformPathPatternList(gpstate, graph_pattern->path_pattern_list);
+	graph_pattern->whereClause = transformExpr(pstate2, graph_pattern->whereClause, EXPR_KIND_OTHER);
+	assign_expr_collations(pstate2, graph_pattern->whereClause);
+
+	return (Node *) graph_pattern;
+}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 34a0ec5901..c4b2b61390 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2127,6 +2127,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.
@@ -2941,6 +3033,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);
@@ -3318,6 +3411,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 f10fc420e6..ce839ef540 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)
@@ -1564,6 +1568,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 5eaadf53b2..a8a5baef09 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -353,6 +353,11 @@ typecast		"::"
 dot_dot			\.\.
 colon_equals	":="
 
+bracket_right_arrow	"]->"
+left_arrow_bracket	"<-["
+minus_left_bracket	"-["
+right_bracket_minus	"]-"
+
 /*
  * These operator-like tokens (unlike the above ones) also match the {operator}
  * rule, which means that they might be overridden by a longer match if they
@@ -367,6 +372,9 @@ greater_equals	">="
 less_greater	"<>"
 not_equals		"!="
 
+left_arrow		"<-"
+right_arrow		"->"
+
 /*
  * "self" is the set of chars that should be returned as single-character
  * tokens.  "op_chars" is the set of chars that can make up "Op" tokens,
@@ -850,6 +858,26 @@ other			.
 					return COLON_EQUALS;
 				}
 
+{bracket_right_arrow} {
+					SET_YYLLOC();
+					return BRACKET_RIGHT_ARROW;
+				}
+
+{left_arrow_bracket} {
+					SET_YYLLOC();
+					return LEFT_ARROW_BRACKET;
+				}
+
+{minus_left_bracket} {
+					SET_YYLLOC();
+					return MINUS_LEFT_BRACKET;
+				}
+
+{right_bracket_minus} {
+					SET_YYLLOC();
+					return RIGHT_BRACKET_MINUS;
+				}
+
 {equals_greater} {
 					SET_YYLLOC();
 					return EQUALS_GREATER;
@@ -877,11 +905,31 @@ other			.
 					return NOT_EQUALS;
 				}
 
+{left_arrow}	{
+					SET_YYLLOC();
+					return LEFT_ARROW;
+				}
+
+{right_arrow}	{
+					SET_YYLLOC();
+					return RIGHT_ARROW;
+				}
+
 {self}			{
 					SET_YYLLOC();
 					return yytext[0];
 				}
 
+\{				{
+					SET_YYLLOC();
+					return LEFT_BRACE;
+				}
+
+\}				{
+					SET_YYLLOC();
+					return RIGHT_BRACE;
+				}
+
 {operator}		{
 					/*
 					 * Check for embedded slash-star or dash-dash; those
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..94f1be3de5
--- /dev/null
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -0,0 +1,422 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_label.h"
+#include "catalog/pg_propgraph_property.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parsetree.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/syscache.h"
+
+
+static Oid	get_labelid(Oid graphid, const char *labelname);
+static List *get_elements_for_label(Oid graphid, const char *labelname);
+static Oid	get_table_for_element(Oid elid);
+static Node *replace_property_refs(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);
+
+struct elvar_rt_mapping
+{
+	const char *elvarname;
+	Oid			labelid;
+	int			rt_index;
+};
+
+
+/*
+ * Convert GRAPH_TABLE clause into a subquery using relational
+ * operators.
+ */
+Query *
+rewriteGraphTable(Query *parsetree, int rt_index)
+{
+	RangeTblEntry *rte;
+	Query	   *newsubquery;
+	ListCell   *lc;
+	List	   *element_patterns;
+	List	   *element_ids = NIL;
+	List	   *fromlist = NIL;
+	List	   *qual_exprs = NIL;
+	List	   *elvar_rt_mappings = NIL;
+
+	rte = rt_fetch(rt_index, parsetree->rtable);
+
+	newsubquery = makeNode(Query);
+	newsubquery->commandType = CMD_SELECT;
+
+	if (list_length(rte->graph_pattern->path_pattern_list) != 1)
+		elog(ERROR, "unsupported path pattern list length");
+
+	element_patterns = linitial(rte->graph_pattern->path_pattern_list);
+
+	foreach(lc, element_patterns)
+	{
+		GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc);
+		Oid			labelid = InvalidOid;
+		struct elvar_rt_mapping *erm;
+
+		if (!(gep->kind == VERTEX_PATTERN || gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT))
+			elog(ERROR, "unsupported element pattern kind: %u", gep->kind);
+
+		if (gep->quantifier)
+			elog(ERROR, "element pattern quantifier not supported yet");
+
+		if (IsA(gep->labelexpr, GraphLabelRef))
+		{
+			GraphLabelRef *glr = castNode(GraphLabelRef, gep->labelexpr);
+			RangeTblEntry *r;
+			Oid			elid;
+			Oid			relid;
+			RTEPermissionInfo *rpi;
+			RangeTblRef *rtr;
+
+			r = makeNode(RangeTblEntry);
+			r->rtekind = RTE_RELATION;
+			elid = linitial_oid(get_elements_for_label(rte->relid, glr->labelname));
+			element_ids = lappend_oid(element_ids, elid);
+			relid = get_table_for_element(elid);
+			labelid = get_labelid(rte->relid, glr->labelname);
+			r->relid = relid;
+			r->relkind = get_rel_relkind(relid);
+			r->rellockmode = AccessShareLock;
+			r->inh = true;
+			newsubquery->rtable = lappend(newsubquery->rtable, r);
+
+			rpi = makeNode(RTEPermissionInfo);
+			rpi->relid = relid;
+			rpi->checkAsUser = 0;
+			rpi->requiredPerms = ACL_SELECT;
+			newsubquery->rteperminfos = lappend(newsubquery->rteperminfos, rpi);
+
+			r->perminfoindex = list_length(newsubquery->rteperminfos);
+
+			rtr = makeNode(RangeTblRef);
+			rtr->rtindex = list_length(newsubquery->rtable);
+			fromlist = lappend(fromlist, rtr);
+		}
+		else
+			elog(ERROR, "unsupported label expression type: %d", (int) nodeTag(gep->labelexpr));
+
+		erm = palloc0_object(struct elvar_rt_mapping);
+
+		erm->elvarname = gep->variable;
+		erm->labelid = labelid;
+		erm->rt_index = list_length(newsubquery->rtable);
+
+		elvar_rt_mappings = lappend(elvar_rt_mappings, erm);
+
+		if (gep->whereClause)
+		{
+			Node	   *tr;
+
+			tr = replace_property_refs(gep->whereClause, list_make1(erm));
+
+			qual_exprs = lappend(qual_exprs, tr);
+		}
+	}
+
+	/* Iterate over edges only */
+	for (int k = 1; k < list_length(element_ids); k += 2)
+	{
+		Oid			elid = list_nth_oid(element_ids, k);
+		HeapTuple	tuple;
+		Form_pg_propgraph_element pgeform;
+		GraphElementPattern *gep = list_nth_node(GraphElementPattern, element_patterns, k);
+		int			srcvertexoffset;
+		int			destvertexoffset;
+
+		tuple = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elid));
+		if (!tuple)
+			elog(ERROR, "cache lookup failed for property graph element %u", elid);
+		pgeform = ((Form_pg_propgraph_element) GETSTRUCT(tuple));
+
+		if (gep->kind == EDGE_PATTERN_RIGHT)
+		{
+			srcvertexoffset = -1;
+			destvertexoffset = +1;
+		}
+		else if (gep->kind == EDGE_PATTERN_LEFT)
+		{
+			srcvertexoffset = +1;
+			destvertexoffset = -1;
+		}
+		else
+		{
+			Assert(false);
+			srcvertexoffset = 0;
+			destvertexoffset = 0;
+		}
+
+		/*
+		 * source link
+		 */
+		if (pgeform->pgesrcvertexid != list_nth_oid(element_ids, k + srcvertexoffset))
+		{
+			qual_exprs = lappend(qual_exprs, makeBoolConst(false, false));
+		}
+		else
+		{
+			qual_exprs = list_concat(qual_exprs,
+									 build_edge_vertex_link_quals(tuple, k + 1, k + 1 + srcvertexoffset,
+																  Anum_pg_propgraph_element_pgesrckey, Anum_pg_propgraph_element_pgesrcref));
+		}
+
+		/*
+		 * dest link
+		 */
+		if (pgeform->pgedestvertexid != list_nth_oid(element_ids, k + destvertexoffset))
+		{
+			qual_exprs = lappend(qual_exprs, makeBoolConst(false, false));
+		}
+		else
+		{
+			qual_exprs = list_concat(qual_exprs,
+									 build_edge_vertex_link_quals(tuple, k + 1, k + 1 + destvertexoffset,
+																  Anum_pg_propgraph_element_pgedestkey, Anum_pg_propgraph_element_pgedestref));
+		}
+
+		ReleaseSysCache(tuple);
+	}
+
+	newsubquery->jointree = makeFromExpr(fromlist, (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1));
+
+	foreach(lc, rte->graph_table_columns)
+	{
+		TargetEntry *te = lfirst_node(TargetEntry, lc);
+		Node	   *nte;
+
+		nte = replace_property_refs((Node *) te, elvar_rt_mappings);
+		newsubquery->targetList = lappend(newsubquery->targetList, nte);
+	}
+
+	AcquireRewriteLocks(newsubquery, true, false);
+
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = newsubquery;
+
+	/*
+	 * Reset no longer applicable fields, to appease
+	 * WRITE_READ_PARSE_PLAN_TREES.
+	 */
+	rte->graph_pattern = NULL;
+	rte->graph_table_columns = NIL;
+
+	return parsetree;
+}
+
+/*
+ * Get label OID from graph OID and label name.
+ *
+ * TODO: This could match more than one entry.  Right now it only returns the
+ * first one.
+ */
+static Oid
+get_labelid(Oid graphid, const char *labelname)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tup;
+	Oid			result = InvalidOid;
+
+	rel = table_open(PropgraphLabelRelationId, RowShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_label_pglpgid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(graphid));
+	ScanKeyInit(&key[1],
+				Anum_pg_propgraph_label_pgllabel,
+				BTEqualStrategyNumber,
+				F_NAMEEQ, CStringGetDatum(labelname));
+
+	scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
+							  true, NULL, 2, key);
+
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		result = ((Form_pg_propgraph_label) GETSTRUCT(tup))->oid;
+		break;
+	}
+
+	systable_endscan(scan);
+	table_close(rel, RowShareLock);
+
+	return result;
+}
+
+/*
+ * Get list of element OIDs that have a given label.
+ */
+static List *
+get_elements_for_label(Oid graphid, const char *labelname)
+{
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[2];
+	HeapTuple	tup;
+	List	   *result = NIL;
+
+	rel = table_open(PropgraphLabelRelationId, RowShareLock);
+	ScanKeyInit(&key[0],
+				Anum_pg_propgraph_label_pglpgid,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(graphid));
+	ScanKeyInit(&key[1],
+				Anum_pg_propgraph_label_pgllabel,
+				BTEqualStrategyNumber,
+				F_NAMEEQ, CStringGetDatum(labelname));
+
+	scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
+							  true, NULL, 2, key);
+
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		result = lappend_oid(result, ((Form_pg_propgraph_label) GETSTRUCT(tup))->pglelid);
+	}
+
+	systable_endscan(scan);
+	table_close(rel, RowShareLock);
+
+	return result;
+}
+
+/*
+ * Get the element table OID for a given element.
+ */
+static Oid
+get_table_for_element(Oid elid)
+{
+	return GetSysCacheOid1(PROPGRAPHELOID, Anum_pg_propgraph_element_pgerelid, ObjectIdGetDatum(elid));
+}
+
+/*
+ * Mutating property references into table variables
+ */
+
+struct replace_property_refs_context
+{
+	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, GraphPropertyRef))
+	{
+		GraphPropertyRef *gpr = (GraphPropertyRef *) node;
+		HeapTuple	tup;
+		Node	   *n;
+		ListCell   *lc;
+		struct elvar_rt_mapping *found_mapping = NULL;
+
+		foreach(lc, context->mappings)
+		{
+			struct elvar_rt_mapping *m = lfirst(lc);
+
+			if (m->elvarname && strcmp(gpr->elvarname, m->elvarname) == 0)
+			{
+				found_mapping = m;
+				break;
+			}
+		}
+		if (!found_mapping)
+			elog(ERROR, "undefined element variable \"%s\"", gpr->elvarname);
+
+		tup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(found_mapping->labelid), CStringGetDatum(gpr->propname));
+		if (!tup)
+			elog(ERROR, "property \"%s\" of label %u not found", gpr->propname, found_mapping->labelid);
+
+		n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHPROPNAME, tup, Anum_pg_propgraph_property_pgpexpr)));
+		ChangeVarNodes(n, 1, found_mapping->rt_index, 0);
+
+		ReleaseSysCache(tup);
+
+		return n;
+	}
+	return expression_tree_mutator(node, replace_property_refs_mutator, context);
+}
+
+static Node *
+replace_property_refs(Node *node, const List *mappings)
+{
+	struct replace_property_refs_context context;
+
+	context.mappings = mappings;
+
+	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;
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 89187d9af2..6e41784a2d 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -37,6 +37,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"
@@ -2032,6 +2033,17 @@ 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);
+			continue;
+		}
+
 		/*
 		 * 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 83f86a42f7..c71228cd27 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:
@@ -2281,6 +2293,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;
@@ -2557,6 +2572,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;
@@ -2938,6 +2956,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;
@@ -3635,6 +3661,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 2a1ee69970..2844ee2f85 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -34,6 +34,9 @@
 #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_label.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 +348,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 labelid, 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,
@@ -1569,6 +1575,298 @@ 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(PropgraphLabelRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey[0],
+				Anum_pg_propgraph_label_pglelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(elid));
+
+	count = 0;
+	scan = systable_beginscan(pglrel, PropgraphLabelLabelIndexId, 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_label_pglelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(elid));
+
+	scan = systable_beginscan(pglrel, PropgraphLabelLabelIndexId, true, NULL, 1, scankey);
+
+	while ((tup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_label pglform = (Form_pg_propgraph_label) GETSTRUCT(tup);
+
+		if (strcmp(NameStr(pglform->pgllabel), 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(NameStr(pglform->pgllabel)));
+
+		make_propgraphdef_properties(buf, pglform->oid, elrelid);
+	}
+
+	systable_endscan(scan);
+
+	table_close(pglrel, AccessShareLock);
+}
+
+/*
+ * 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 labelid, Oid elrelid)
+{
+	List	   *context;
+	Relation	pgprel;
+	ScanKeyData scankey[1];
+	SysScanDesc scan;
+	HeapTuple	tup;
+	bool		first;
+
+	context = deparse_context_for(get_relation_name(elrelid), elrelid);
+
+	pgprel = table_open(PropgraphPropertyRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey[0],
+				Anum_pg_propgraph_property_pgplabelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(labelid));
+
+	/*
+	 * Note: Use of index on property name also ensures that properties are
+	 * put out in a deterministic order.
+	 */
+	scan = systable_beginscan(pgprel, PropgraphPropertyNameIndexId, true, NULL, 1, scankey);
+
+	first = true;
+	while ((tup = systable_getnext(scan)))
+	{
+		Form_pg_propgraph_property pgpform = (Form_pg_propgraph_property) GETSTRUCT(tup);
+		Datum		exprDatum;
+		bool		isnull;
+		char	   *tmp;
+		Node	   *expr;
+
+		if (first)
+		{
+			appendStringInfo(buf, " PROPERTIES (");
+			first = false;
+		}
+		else
+			appendStringInfo(buf, ", ");
+
+		exprDatum = heap_getattr(tup, Anum_pg_propgraph_property_pgpexpr, RelationGetDescr(pgprel), &isnull);
+		Assert(!isnull);
+		tmp = TextDatumGetCString(exprDatum);
+		expr = stringToNode(tmp);
+		pfree(tmp);
+
+		if (IsA(expr, Var) && strcmp(NameStr(pgpform->pgpname), get_attname(elrelid, castNode(Var, expr)->varattno, false)) == 0)
+			appendStringInfo(buf, "%s",
+							 NameStr(pgpform->pgpname));
+		else
+			appendStringInfo(buf, "%s AS %s",
+							 deparse_expression_pretty(expr, context, false, false, 0, 0),
+							 NameStr(pgpform->pgpname));
+	}
+
+	if (first)
+		appendStringInfo(buf, " NO PROPERTIES");
+	else
+		appendStringInfo(buf, ")");
+
+	systable_endscan(scan);
+	table_close(pgprel, AccessShareLock);
+}
+
 /*
  * pg_get_statisticsobjdef
  *		Get the definition of an extended statistics object
@@ -7231,6 +7529,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(lref->labelname));
+				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.
  *
@@ -7793,6 +8256,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:
 
 			/*
@@ -9812,6 +10276,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(gpr->propname));
+				break;
+			}
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			break;
@@ -11438,6 +11910,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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 0ed18b72d6..bc34fc7d6b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -519,7 +519,8 @@ flagInhAttrs(Archive *fout, DumpOptions *dopt, 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 d97ebaff5b..ebd74e4053 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3456,6 +3456,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 10cbf02beb..71842192e1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1601,10 +1601,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,
@@ -2748,6 +2748,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;
@@ -6805,7 +6808,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);
@@ -15869,8 +15873,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, false);
@@ -15896,6 +15898,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;
@@ -15971,8 +16014,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, false);
@@ -16590,6 +16631,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,
@@ -18423,6 +18466,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 00b5092713..ea0d6beb09 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2959,6 +2959,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 5c906e4806..ea53da0f5a 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -775,7 +775,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':
 				{
@@ -906,6 +906,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 c05befbb6f..d3018c416f 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1025,6 +1025,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"
 					  "  ",
@@ -1035,6 +1036,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"));
 
@@ -1126,6 +1128,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)
@@ -2009,6 +2012,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\"",
@@ -3099,6 +3106,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)
@@ -3950,6 +3983,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
@@ -3961,6 +3995,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;
@@ -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};
 
 	/* 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);
 
@@ -3987,6 +4022,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"),
@@ -4000,6 +4036,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;
@@ -4083,6 +4120,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 4e79a819d8..a8f04c156b 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -246,6 +246,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 aa1acf8523..a847cf553c 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},
@@ -2305,6 +2314,12 @@ 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("OWNER TO", "RENAME TO", "SET SCHEMA");
+
 	/* ALTER RULE <name>, add ON */
 	else if (Matches("ALTER", "RULE", MatchAny))
 		COMPLETE_WITH("ON");
@@ -2782,7 +2797,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",
@@ -2820,6 +2835,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"))
@@ -3134,6 +3151,31 @@ 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", "(", MatchAny))
+		COMPLETE_WITH(",", ")");
+	else if (HeadMatches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(") &&
+			 TailMatches(")"))
+		COMPLETE_WITH("EDGE");	/* FIXME: doesn't seem to work */
+	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))
@@ -3801,6 +3843,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");
@@ -4035,6 +4083,7 @@ psql_completion(const char *text, int start, int end)
 											"LARGE OBJECT",
 											"PARAMETER",
 											"PROCEDURE",
+											"PROPERTY GRAPH",
 											"ROUTINE",
 											"SCHEMA",
 											"SEQUENCE",
@@ -4161,6 +4210,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");
@@ -4466,8 +4523,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");
 
@@ -4884,6 +4943,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 c9df0594fd..3a10a92418 100644
--- a/src/fe_utils/psqlscan.l
+++ b/src/fe_utils/psqlscan.l
@@ -788,6 +788,14 @@ other			.
 					ECHO;
 				}
 
+\{				{
+					ECHO;
+				}
+
+\}				{
+					ECHO;
+				}
+
 {operator}		{
 					/*
 					 * Check for embedded slash-star or dash-dash; those
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 436b081738..df47d2ac62 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -123,6 +123,9 @@ typedef enum ObjectClass
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
 	OCLASS_PARAMETER_ACL,		/* pg_parameter_acl */
 	OCLASS_POLICY,				/* pg_policy */
+	OCLASS_PROPGRAPH_ELEMENT,	/* pg_propgraph_element */
+	OCLASS_PROPGRAPH_LABEL,		/* pg_propgraph_label */
+	OCLASS_PROPGRAPH_PROPERTY,	/* pg_propgraph_property */
 	OCLASS_PUBLICATION,			/* pg_publication */
 	OCLASS_PUBLICATION_NAMESPACE,	/* pg_publication_namespace */
 	OCLASS_PUBLICATION_REL,		/* pg_publication_rel */
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 6b3c56c20e..e4b7b626be 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -69,6 +69,9 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_propgraph_element.h',
+  'pg_propgraph_label.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 3b7533e7bb..1635d2c5b3 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 291ed876fc..7b0971e0fc 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3780,6 +3780,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..fb4bbf8154
--- /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, 8312, 8313);
+
+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_label.h b/src/include/catalog/pg_propgraph_label.h
new file mode 100644
index 0000000000..a704bdfabd
--- /dev/null
+++ b/src/include/catalog/pg_propgraph_label.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_label.h
+ *	  definition of the "property graph labels" system catalog (pg_propgraph_label)
+ *
+ * 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.  This can also be found out by
+	 * chasing via pglelid, but having it here is more efficient.
+	 */
+	Oid			pglpgid BKI_LOOKUP(pg_class);
+
+	/* label name */
+	NameData	pgllabel;
+
+	/* OID of the property graph element */
+	Oid			pglelid BKI_LOOKUP(pg_propgraph_element);
+} 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_label_index, 8305, PropgraphLabelLabelIndexId, pg_propgraph_label, btree(pglelid oid_ops, pgllabel name_ops));
+
+DECLARE_INDEX(pg_propgraph_label_graph_name_index, 8314, PropgraphLabelGraphNameIndexId, pg_propgraph_label, btree(pglpgid oid_ops, pgllabel name_ops));
+
+MAKE_SYSCACHE(PROPGRAPHLABELNAME, pg_propgraph_label_label_index, 128);
+
+#endif							/* PG_PROPGRAPH_LABEL_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..2d5f2b8fe7
--- /dev/null
+++ b/src/include/catalog/pg_propgraph_property.h
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_property.h
+ *	  definition of the "property graph properties" system catalog (pg_propgraph_property)
+ *
+ * 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.  This can also be found out by
+	 * chasing via pgplabelid, but having it here is more efficient.
+	 */
+	Oid			pgppgid BKI_LOOKUP(pg_class);
+
+	/* property name */
+	NameData	pgpname;
+
+	/*
+	 * Type of the property.  (This can be computed from pgpexpr, but storing
+	 * it makes parsing GRAPH_TABLE more efficient.)
+	 */
+	Oid			pgptypid BKI_LOOKUP_OPT(pg_type);
+
+	/* OID of the label */
+	Oid			pgplabelid BKI_LOOKUP(pg_propgraph_label);
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+
+	/* property expression */
+	pg_node_tree pgpexpr BKI_FORCE_NOT_NULL;
+
+#endif
+} 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_TOAST(pg_propgraph_property, 8309, 8310);
+
+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(pgplabelid oid_ops, pgpname name_ops));
+
+DECLARE_INDEX(pg_propgraph_property_graph_name_index, 8311, PropgraphPropertyGraphNameIndexId, pg_propgraph_property, btree(pgppgid oid_ops, pgpname name_ops));
+
+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 d60e148ff2..fa08a5ee29 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -677,6 +677,19 @@ typedef struct RangeTableFuncCol
 	int			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 */
+	int			location;		/* token location, or -1 if unknown */
+} RangeGraphTable;
+
 /*
  * RangeTableSample - TABLESAMPLE appearing in a raw FROM clause
  *
@@ -943,6 +956,38 @@ 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;
+
+typedef struct GraphElementPattern
+{
+	NodeTag		type;
+	GraphElementPatternKind kind;
+	const char *variable;
+	Node	   *labelexpr;
+	List	   *subexpr;
+	Node	   *whereClause;
+	List	   *quantifier;
+	int			location;
+} GraphElementPattern;
+
 /****************************************************************************
  *	Nodes for a Query tree
  ****************************************************************************/
@@ -1019,6 +1064,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 */
@@ -1153,6 +1199,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):
 	 */
@@ -2131,6 +2183,7 @@ typedef enum ObjectType
 	OBJECT_PARAMETER_ACL,
 	OBJECT_POLICY,
 	OBJECT_PROCEDURE,
+	OBJECT_PROPGRAPH,
 	OBJECT_PUBLICATION,
 	OBJECT_PUBLICATION_NAMESPACE,
 	OBJECT_PUBLICATION_REL,
@@ -3858,6 +3911,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;
+	int			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;
+	int			location;
+} PropGraphEdge;
+
+typedef struct PropGraphLabelAndProperties
+{
+	NodeTag		type;
+	const char *label;
+	struct PropGraphProperties *properties;
+	int			location;
+} PropGraphLabelAndProperties;
+
+typedef struct PropGraphProperties
+{
+	NodeTag		type;
+	List	   *properties;
+	bool		all;
+	int			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 4a154606d2..43c94aa9f9 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1860,6 +1860,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;
+	const char *labelname;
+	int			location;
+} GraphLabelRef;
+
+/*
+ * GraphPropertyRef - property reference inside GRAPH_TABLE clause
+ */
+typedef struct GraphPropertyRef
+{
+	Expr		xpr;
+	const char *elvarname;
+	const char *propname;
+	Oid			typeId;
+	int			location;
+} GraphPropertyRef;
+
 /*--------------------
  * TargetEntry -
  *	   a target entry (used in query target lists)
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 2331acac09..34092ed075 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -135,6 +135,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)
@@ -146,6 +147,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("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -187,6 +189,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)
@@ -284,6 +288,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)
@@ -342,6 +347,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("range", RANGE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -355,6 +362,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)
@@ -403,6 +411,7 @@ PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("some", SOME, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("source", SOURCE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -470,6 +479,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..7de98a71f7
--- /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	   *transformGraphPattern(GraphTableParseState *gpstate, GraphPattern *graph_pattern);
+
+#endif							/* PARSE_GRAPHTABLE_H */
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 a803eb1291..b659040716 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -165,6 +165,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/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index bef33d58a2..67d14c58aa 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -233,6 +233,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..9ad28f6fc0
--- /dev/null
+++ b/src/test/regress/expected/create_property_graph.out
@@ -0,0 +1,370 @@
+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)
+    );
+-- 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 element t2 in property graph g3 because other objects depend on it
+DETAIL:  element e1 in property graph g3 depends on element t2 in 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 element e1 in 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);
+-- 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
+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
+(4 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         | 
+(12 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
+(16 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
+(10 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
+(15 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
+(30 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
+(37 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
+(15 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
+(22 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..f22ba091a7
--- /dev/null
+++ b/src/test/regress/expected/graph_table.out
@@ -0,0 +1,122 @@
+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 PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products,
+        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 (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_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:  graph pattern variable "cx" does not exist
+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
+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, '2024-01-01'),
+    (2, '2024-01-02'),
+    (3, '2024-01-03');
+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);
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(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 = '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)
+
+-- TODO: should approximately match this query:
+SET debug_print_parse = on;
+SELECT customer_name FROM (
+    SELECT c.name AS customer_name
+    FROM customers c, customer_orders _co, orders o
+    WHERE _co.customer_id = c.customer_id
+      AND _co.order_id = o.order_id
+      AND c.address = 'US'
+) myshop;
+ customer_name 
+---------------
+ customer1
+(1 row)
+
+RESET debug_print_parse;
+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)
+
+-- 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..08b84962c8 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -266,3 +266,12 @@ 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_label {pglpgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_label {pglelid} => pg_propgraph_element {oid}
+NOTICE:  checking pg_propgraph_property {pgppgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_property {pgptypid} => pg_type {oid}
+NOTICE:  checking pg_propgraph_property {pgplabelid} => pg_propgraph_label {oid}
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1d8a414eea..09e69e16cd 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.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.icu.utf8 incremental_sort create_role without_overlaps graph_table
 
 # collate.*.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..2f0b42ecd2
--- /dev/null
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -0,0 +1,130 @@
+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)
+    );
+
+-- 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);
+
+-- 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';
+
+
+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..0632cbda8b
--- /dev/null
+++ b/src/test/regress/sql/graph_table.sql
@@ -0,0 +1,96 @@
+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 PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products,
+        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 (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_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
+
+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, '2024-01-01'),
+    (2, '2024-01-02'),
+    (3, '2024-01-03');
+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);
+
+SELECT * 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 * FROM GRAPH_TABLE (myshop MATCH (c:customers)-[co:customer_orders]->(o:orders WHERE o.ordered_when = '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));
+
+-- TODO: should approximately match this query:
+SET debug_print_parse = on;
+SELECT customer_name FROM (
+    SELECT c.name AS customer_name
+    FROM customers c, customer_orders _co, orders o
+    WHERE _co.customer_id = c.customer_id
+      AND _co.order_id = o.order_id
+      AND c.address = 'US'
+) myshop;
+RESET debug_print_parse;
+
+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);
+
+-- 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 95ae7845d8..4219b72526 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -4054,3 +4054,22 @@ rfile
 ws_options
 ws_file_info
 PathKeyInfo
+
+# TODO
+AlterPropGraphElementKind
+AlterPropGraphStmt
+CreatePropGraphStmt
+FormData_pg_propgraph_element
+FormData_pg_propgraph_label
+FormData_pg_propgraph_property
+GraphElementPattern
+GraphElementPatternKind
+GraphLabelRef
+GraphPattern
+GraphPropertyRef
+GraphTableParseState
+PropGraphEdge
+PropGraphLabelAndProperties
+PropGraphProperties
+PropGraphVertex
+RangeGraphTable
-- 
2.25.1

