]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
SQL Property Graph Queries (SQL/PGQ)
authorPeter Eisentraut <peter@eisentraut.org>
Mon, 16 Mar 2026 09:14:18 +0000 (10:14 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Mon, 16 Mar 2026 09:14:18 +0000 (10:14 +0100)
Implementation of SQL property graph queries, according to SQL/PGQ
standard (ISO/IEC 9075-16:2023).

This adds:

- GRAPH_TABLE table function for graph pattern matching
- DDL commands CREATE/ALTER/DROP PROPERTY GRAPH
- several new system catalogs and information schema views
- psql \dG command
- pg_get_propgraphdef() function for pg_dump and psql

A property graph is a relation with a new relkind RELKIND_PROPGRAPH.
It acts like a view in many ways.  It is rewritten to a standard
relational query in the rewriter.  Access privileges act similar to a
security invoker view.  (The security definer variant is not currently
implemented.)

Starting documentation can be found in doc/src/sgml/ddl.sgml and
doc/src/sgml/queries.sgml.

Author: Peter Eisentraut <peter@eisentraut.org>
Author: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Reviewed-by: Junwang Zhao <zhjwpku@gmail.com>
Reviewed-by: Ajay Pal <ajay.pal.k@gmail.com>
Reviewed-by: Henson Choi <assam258@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/a855795d-e697-4fa5-8698-d20122126567@eisentraut.org

122 files changed:
contrib/pg_overexplain/expected/pg_overexplain.out
contrib/pg_overexplain/pg_overexplain.c
contrib/pg_overexplain/sql/pg_overexplain.sql
doc/src/sgml/catalogs.sgml
doc/src/sgml/ddl.sgml
doc/src/sgml/features.sgml
doc/src/sgml/func/func-info.sgml
doc/src/sgml/information_schema.sgml
doc/src/sgml/keywords/sql2023-16-nonreserved.txt [new file with mode: 0644]
doc/src/sgml/keywords/sql2023-16-reserved.txt [new file with mode: 0644]
doc/src/sgml/queries.sgml
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/alter_extension.sgml
doc/src/sgml/ref/alter_property_graph.sgml [new file with mode: 0644]
doc/src/sgml/ref/comment.sgml
doc/src/sgml/ref/create_property_graph.sgml [new file with mode: 0644]
doc/src/sgml/ref/drop_property_graph.sgml [new file with mode: 0644]
doc/src/sgml/ref/grant.sgml
doc/src/sgml/ref/psql-ref.sgml
doc/src/sgml/ref/revoke.sgml
doc/src/sgml/ref/security_label.sgml
doc/src/sgml/ref/select.sgml
doc/src/sgml/reference.sgml
src/backend/catalog/aclchk.c
src/backend/catalog/dependency.c
src/backend/catalog/information_schema.sql
src/backend/catalog/objectaddress.c
src/backend/catalog/pg_class.c
src/backend/catalog/sql_features.txt
src/backend/commands/Makefile
src/backend/commands/alter.c
src/backend/commands/dropcmds.c
src/backend/commands/event_trigger.c
src/backend/commands/meson.build
src/backend/commands/propgraphcmds.c [new file with mode: 0644]
src/backend/commands/seclabel.c
src/backend/commands/tablecmds.c
src/backend/executor/execMain.c
src/backend/nodes/nodeFuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/print.c
src/backend/nodes/readfuncs.c
src/backend/optimizer/path/allpaths.c
src/backend/optimizer/prep/prepjointree.c
src/backend/parser/Makefile
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/meson.build
src/backend/parser/parse_agg.c
src/backend/parser/parse_clause.c
src/backend/parser/parse_collate.c
src/backend/parser/parse_expr.c
src/backend/parser/parse_func.c
src/backend/parser/parse_graphtable.c [new file with mode: 0644]
src/backend/parser/parse_relation.c
src/backend/parser/parse_target.c
src/backend/parser/scan.l
src/backend/rewrite/Makefile
src/backend/rewrite/meson.build
src/backend/rewrite/rewriteGraphTable.c [new file with mode: 0644]
src/backend/rewrite/rewriteHandler.c
src/backend/tcop/utility.c
src/backend/utils/adt/acl.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/lsyscache.c
src/backend/utils/cache/plancache.c
src/bin/pg_dump/common.c
src/bin/pg_dump/dumputils.c
src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/t/002_pg_dump.pl
src/bin/psql/command.c
src/bin/psql/describe.c
src/bin/psql/help.c
src/bin/psql/tab-complete.in.c
src/fe_utils/psqlscan.l
src/include/catalog/Makefile
src/include/catalog/catversion.h
src/include/catalog/meson.build
src/include/catalog/pg_class.h
src/include/catalog/pg_proc.dat
src/include/catalog/pg_propgraph_element.h [new file with mode: 0644]
src/include/catalog/pg_propgraph_element_label.h [new file with mode: 0644]
src/include/catalog/pg_propgraph_label.h [new file with mode: 0644]
src/include/catalog/pg_propgraph_label_property.h [new file with mode: 0644]
src/include/catalog/pg_propgraph_property.h [new file with mode: 0644]
src/include/commands/propgraphcmds.h [new file with mode: 0644]
src/include/nodes/parsenodes.h
src/include/nodes/primnodes.h
src/include/parser/analyze.h
src/include/parser/kwlist.h
src/include/parser/parse_graphtable.h [new file with mode: 0644]
src/include/parser/parse_node.h
src/include/parser/parse_relation.h
src/include/rewrite/rewriteGraphTable.h [new file with mode: 0644]
src/include/tcop/cmdtaglist.h
src/include/utils/acl.h
src/include/utils/lsyscache.h
src/interfaces/ecpg/preproc/pgc.l
src/interfaces/ecpg/test/ecpg_schedule
src/interfaces/ecpg/test/expected/sql-sqlpgq.c [new file with mode: 0644]
src/interfaces/ecpg/test/expected/sql-sqlpgq.stderr [new file with mode: 0644]
src/interfaces/ecpg/test/expected/sql-sqlpgq.stdout [new file with mode: 0644]
src/interfaces/ecpg/test/sql/.gitignore
src/interfaces/ecpg/test/sql/Makefile
src/interfaces/ecpg/test/sql/meson.build
src/interfaces/ecpg/test/sql/sqlpgq.pgc [new file with mode: 0644]
src/test/regress/expected/alter_generic.out
src/test/regress/expected/create_property_graph.out [new file with mode: 0644]
src/test/regress/expected/graph_table.out [new file with mode: 0644]
src/test/regress/expected/graph_table_rls.out [new file with mode: 0644]
src/test/regress/expected/object_address.out
src/test/regress/expected/oidjoins.out
src/test/regress/expected/privileges.out
src/test/regress/parallel_schedule
src/test/regress/sql/alter_generic.sql
src/test/regress/sql/create_property_graph.sql [new file with mode: 0644]
src/test/regress/sql/graph_table.sql [new file with mode: 0644]
src/test/regress/sql/graph_table_rls.sql [new file with mode: 0644]
src/test/regress/sql/object_address.sql
src/test/regress/sql/privileges.sql
src/tools/pgindent/typedefs.list

index f376d2e7996169a2b06478d4468c7eef078fdb52..05c6686d67763aa1a9e195405928e07a076c8700 100644 (file)
@@ -612,3 +612,57 @@ SELECT * FROM vegetables v,
  Unprunable RTIs: 1 3 4 5 6
 (51 rows)
 
+-- Property graph test
+CREATE PROPERTY GRAPH vegetables_graph
+VERTEX TABLES
+(
+       daucus KEY(name) DEFAULT LABEL LABEL vegetables,
+       brassica KEY(name) DEFAULT LABEL LABEL vegetables
+);
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM GRAPH_TABLE (vegetables_graph MATCH (v1 IS vegetables) WHERE v1.genus = 'daucus' COLUMNS (v1.name));
+                  QUERY PLAN                  
+----------------------------------------------
+ Append
+   Append RTIs: 1
+   Child Append RTIs: none
+   ->  Seq Scan on daucus
+         Filter: (genus = 'daucus'::text)
+         Scan RTI: 4
+         Elided Node Type: SubqueryScan
+         Elided Node RTIs: 2
+   ->  Seq Scan on brassica
+         Filter: (genus = 'daucus'::text)
+         Scan RTI: 5
+         Elided Node Type: SubqueryScan
+         Elided Node RTIs: 3
+ RTI 1 (subquery, inherited, in-from-clause):
+   Eref: "graph_table" (name)
+   Relation: vegetables_graph
+   Relation Kind: property_graph
+   Relation Lock Mode: AccessShareLock
+   Permission Info Index: 1
+   Lateral: true
+ RTI 2 (subquery):
+   Eref: unnamed_subquery (name)
+   Lateral: true
+ RTI 3 (subquery):
+   Eref: unnamed_subquery (name)
+   Lateral: true
+ RTI 4 (relation):
+   Subplan: unnamed_subquery
+   Eref: daucus (id, name, genus)
+   Relation: daucus
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+   Permission Info Index: 2
+ RTI 5 (relation):
+   Subplan: unnamed_subquery_1
+   Eref: brassica (id, name, genus)
+   Relation: brassica
+   Relation Kind: relation
+   Relation Lock Mode: AccessShareLock
+   Permission Info Index: 3
+ Unprunable RTIs: 1 4 5
+(41 rows)
+
index 964e35240db0b19888f6c55dc0f35a384c3f7d5b..c2b90493cc6a4780216a97ade1076e2fdef32f20 100644 (file)
@@ -505,6 +505,17 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
                        case RTE_GROUP:
                                kind = "group";
                                break;
+                       case RTE_GRAPH_TABLE:
+
+                               /*
+                                * We should not see RTE of this kind here since property
+                                * graph RTE gets converted to subquery RTE in
+                                * RewriteGraphTable(). In case we decide not to do the
+                                * conversion and leave RTEkind unchanged in future, print
+                                * correct name of RTE kind.
+                                */
+                               kind = "graph_table";
+                               break;
                }
 
                /* Begin group for this specific RTE */
@@ -618,6 +629,9 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
                        case RELKIND_PARTITIONED_INDEX:
                                relkind = "partitioned_index";
                                break;
+                       case RELKIND_PROPGRAPH:
+                               relkind = "property_graph";
+                               break;
                        case '\0':
                                relkind = NULL;
                                break;
@@ -740,6 +754,12 @@ overexplain_range_table(PlannedStmt *plannedstmt, ExplainState *es)
                        ExplainPropertyFloat("ENR Tuples", NULL, rte->enrtuples, 0, es);
                }
 
+               /*
+                * rewriteGraphTable() clears graph_pattern and graph_table_columns
+                * fields, so skip them. No graph table specific fields are required
+                * to be printed.
+                */
+
                /*
                 * add_rte_to_flat_rtable will clear groupexprs and securityQuals, so
                 * skip that field. We have handled inFromCl above, so the only thing
index 34a957cbed312cd73294119be6b45443ececf8e7..d07f93688a9f1a9da58c20f62973e613fedbc537 100644 (file)
@@ -120,3 +120,14 @@ SELECT * FROM vegetables v,
 EXPLAIN (RANGE_TABLE, COSTS OFF)
 SELECT * FROM vegetables v,
        (SELECT * FROM vegetables WHERE genus = 'daucus' OFFSET 0);
+
+-- Property graph test
+CREATE PROPERTY GRAPH vegetables_graph
+VERTEX TABLES
+(
+       daucus KEY(name) DEFAULT LABEL LABEL vegetables,
+       brassica KEY(name) DEFAULT LABEL LABEL vegetables
+);
+
+EXPLAIN (RANGE_TABLE, COSTS OFF)
+SELECT * FROM GRAPH_TABLE (vegetables_graph MATCH (v1 IS vegetables) WHERE v1.genus = 'daucus' COLUMNS (v1.name));
index 69588937719d28476e97ab559004866698656936..61ffe804ee8bc3f67693c5da1f97f7d6b829b77a 100644 (file)
       <entry>functions and procedures</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link></entry>
+      <entry>property graph elements (vertices and edges)</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-element-label"><structname>pg_propgraph_element_label</structname></link></entry>
+      <entry>property graph links between elements and labels</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-label"><structname>pg_propgraph_label</structname></link></entry>
+      <entry>property graph labels</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-label-property"><structname>pg_propgraph_label_property</structname></link></entry>
+      <entry>property graph label-specific property definitions</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-propgraph-property"><structname>pg_propgraph_property</structname></link></entry>
+      <entry>property graph properties</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-publication"><structname>pg_publication</structname></link></entry>
       <entry>publications for logical replication</entry>
@@ -2141,7 +2166,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>
 
@@ -6308,6 +6334,498 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
 
  </sect1>
 
+ <sect1 id="catalog-pg-propgraph-element">
+  <title><structname>pg_propgraph_element</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-element">
+   <primary>pg_propgraph_element</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_element</structname> stores
+   information about the vertices and edges of a property graph, collectively
+   called the elements of the property graph.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_element</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgepgid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property graph that this element belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgerelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the table that contains the data for this property graph element
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgealias</structfield> <type>name</type>
+      </para>
+      <para>
+       The alias of the element.  This is a unique identifier for the element
+       within the graph.  It is set when the property graph is defined and
+       defaults to the name of the underlying element table.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgekind</structfield> <type>char</type>
+      </para>
+      <para>
+       <literal>v</literal> for a vertex, <literal>e</literal> for an edge
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrcvertexid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, a link to the source vertex.  (Zero for a vertex.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedestvertexid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, a link to the destination vertex.  (Zero for a vertex.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgekey</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       An array of column numbers in the table referenced by
+       <structname>pgerelid</structname> that defines the key to use for this
+       element table.  (This defaults to the primary key when the property
+       graph is created.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrckey</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table referenced by
+       <structname>pgerelid</structname> that defines the source key to use
+       for this element table.  (Null for a vertex.)  The combination of
+       <structfield>pgesrckey</structfield> and
+       <structfield>pgesrcref</structfield> creates the link between the edge
+       and the source vertex.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrcref</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table reached via
+       <structname>pgesrcvertexid</structname>.  (Null for a vertex.)  The
+       combination of <structfield>pgesrckey</structfield> and
+       <structfield>pgesrcref</structfield> creates the link between the edge
+       and the source vertex.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgesrceqop</structfield> <type>oid[]</type>
+       (references <link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, an array of equality operators for
+       <structfield>pgesrcref</structfield> =
+       <structfield>pgesrckey</structfield> comparison. (Null for a vertex.)
+       </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedestkey</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table referenced by
+       <structname>pgerelid</structname> that defines the destination key to use
+       for this element table.  (Null for a vertex.)  The combination of
+       <structfield>pgedestkey</structfield> and
+       <structfield>pgedestref</structfield> creates the link between the edge
+       and the destination vertex.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedestref</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       For an edge, an array of column numbers in the table reached via
+       <structname>pgedestvertexid</structname>.  (Null for a vertex.)  The
+       combination of <structfield>pgedestkey</structfield> and
+       <structfield>pgedestref</structfield> creates the link between the edge
+       and the destination vertex.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgedesteqop</structfield> <type>oid[]</type>
+       (references <link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       For an edge, an array of equality operators for
+       <structfield>pgedestref</structfield> =
+       <structfield>pgedestkey</structfield> comparison. (Null for a vertex.)
+       </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-element-label">
+  <title><structname>pg_propgraph_element_label</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-element-label">
+   <primary>pg_propgraph_element_label</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_element_label</structname> stores
+   information about which labels apply to which elements.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_element_label</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgellabelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-label"><structname>pg_propgraph_label</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the label
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgelelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element"><structname>pg_propgraph_element</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the element
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-label">
+  <title><structname>pg_propgraph_label</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-label">
+   <primary>pg_propgraph_label</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_label</structname> stores
+   information about the labels in a property graph.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_label</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pglpgid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property graph that this label belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgllabel</structfield> <type>name</type>
+      </para>
+      <para>
+       The name of the label.  This is unique among the labels in a graph.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-label-property">
+  <title><structname>pg_propgraph_label_property</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-label-property">
+   <primary>pg_propgraph_label_property</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_label_property</structname> stores
+   information about the properties in a property graph that are specific to a
+   label.  In particular, this stores the expression that defines the
+   property.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_label_property</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>plppropid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-property"><structname>pg_propgraph_property</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>plpellabelid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-propgraph-element-label"><structname>pg_propgraph_element_label</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the label (indirectly via
+       <structname>pg_propgraph_element_label</structname>, which then links
+       to <structname>pg_propgraph_label</structname>)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>plpexpr</structfield> <type>pg_node_tree</type>
+      </para>
+      <para>
+       Expression tree (in <function>nodeToString()</function> representation)
+       for the property's definition.  The expression references the table
+       reached via <structname>pg_propgraph_element_label</structname> and
+       <structname>pg_propgraph_element</structname>.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-propgraph-property">
+  <title><structname>pg_propgraph_property</structname></title>
+
+  <indexterm zone="catalog-pg-propgraph-property">
+   <primary>pg_propgraph_property</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_propgraph_property</structname> stores
+   information about the properties in a property graph.  This only stores
+   information that applies to a property throughout the graph, independent of
+   what label or element it is on.  Additional information, including the
+   actual expressions that define the properties are in the catalog <link
+   linkend="catalog-pg-propgraph-label-property"><structname>pg_propgraph_label_property</structname></link>.
+  </para>
+
+  <table>
+   <title><structname>pg_propgraph_property</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>oid</structfield> <type>oid</type>
+      </para>
+      <para>
+       Row identifier
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgppgid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       Reference to the property graph that this property belongs to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgpname</structfield> <type>name</type>
+      </para>
+      <para>
+       The name of the property.  This is unique among the properties in a
+       graph.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgptypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The data type of this property.  (This is required to be fixed for a
+       given property in a property graph, even if the property is defined
+       multiple times in different elements and labels.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgptypmod</structfield> <type>int4</type>
+      </para>
+      <para>
+      <literal>typmod</literal> to be applied to the data type of this property.
+      (This is required to be fixed for a given property in a property graph,
+      even if the property is defined multiple times in different elements and
+      labels.)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pgpcollation</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-collation"><structname>pg_collation</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       The defined collation of this property, or zero if the property is not of
+       a collatable data type.  (This is required to be fixed for a given
+       property in a property graph, even if the property is defined multiple
+       times in different elements and labels.)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-publication">
   <title><structname>pg_publication</structname></title>
 
index 9070aaa5a7cd85b7a17f0a4fde7d3ad6b03d8e58..8421ecace1b625b9f864991a4af16ee2c4373983 100644 (file)
@@ -2315,6 +2315,8 @@ REVOKE ALL ON accounts FROM PUBLIC;
        This privilege is also needed to reference existing column values in
        <command>UPDATE</command>, <command>DELETE</command>,
        or <command>MERGE</command>.
+       For property graphs, this privilege allows the object to be referenced
+       in a <literal>GRAPH_TABLE</literal> clause.
        For sequences, this privilege also allows use of the
        <function>currval</function> function.
        For large objects, this privilege allows the object to be read.
@@ -5693,6 +5695,242 @@ 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, as an alternative
+   to the usual (in SQL) approach of representing database contents 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 infrastructure, 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 o.ordered_when = current_date) COLUMNS (c.name AS customer_name));
+</programlisting>
+   corresponding approximately to this relational query:
+<programlisting>
+-- get list of customers active today
+SELECT customers.name FROM customers JOIN customer_orders USING (customer_id) JOIN orders USING (order_id) WHERE orders.ordered_when = current_date;
+</programlisting>
+  </para>
+
+  <para>
+   The above definition requires that all tables have primary keys and that
+   for each edge there is an appropriate foreign key.  Otherwise, additional
+   clauses have to be specified to identify the key columns.  For example,
+   this would be the fully verbose definition that does not rely on primary
+   and foreign keys:
+<programlisting>
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products KEY (product_no),
+        customers KEY (customer_id),
+        orders KEY (order_id)
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+    );
+</programlisting>
+  </para>
+
+  <para>
+   As mentioned above, by default, the names of the tables and columns are
+   exposed as labels and properties, respectively.  The clauses <literal>IS
+   customer</literal>, <literal>IS order</literal>, etc. in the
+   <literal>MATCH</literal> clause in fact refer to labels, not table names.
+  </para>
+
+  <para>
+   One use of labels is to expose a table through a different name in the graph.
+   For example, in graphs, vertices typically have singular nouns as labels and
+   edges typically have verbs or phrases derived from verbs as labels, such as
+   <quote>has</quote>, <quote>contains</quote>, or something specific like
+   <quote>approved_by</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_placed
+    );
+</programlisting>
+  </para>
+
+  <para>
+   With this definition, we can write a query like this:
+<programlisting>
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customer)-[IS has_placed]->(o IS "order" WHERE o.ordered_when = current_date) COLUMNS (c.name AS customer_name));
+</programlisting>
+   With the new labels the <literal>MATCH</literal> clause is now more intuitive.
+  </para>
+
+  <para>
+   Notice that the label <literal>order</literal> is quoted. If we run above
+   statements without adding quotes around <literal>order</literal>, we will get
+   a syntax error since <literal>order</literal> is a keyword.
+  </para>
+
+  <para>
+   Another use is to apply the same label to multiple element tables.  For
+   example, consider this additional table:
+<programlisting>
+CREATE TABLE employees (
+    employee_id integer PRIMARY KEY,
+    employee_name varchar,
+    ...
+);
+</programlisting>
+and the following graph definition:
+<programlisting>
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products LABEL product,
+        customers LABEL customer LABEL person PROPERTIES (name),
+        orders LABEL order,
+        employees LABEL employee LABEL person PROPERTIES (employee_name AS name)
+    )
+    EDGE TABLES (
+        order_items SOURCE orders DESTINATION products LABEL contains,
+        customer_orders SOURCE customers DESTINATION orders LABEL has
+    );
+</programlisting>
+   (In practice, there ought to be an edge linking the
+   <literal>employees</literal> table to something, but it is allowed like
+   this.)  Then we can run a query like this (incomplete):
+<programlisting>
+SELECT ... FROM GRAPH_TABLE (myshop MATCH (IS 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>
+   Using more than one label associated with an element table and each label
+   exposing a different set of properties, the same relational data, and the
+   graph structure contained therein, can be exposed through multiple
+   co-existing logical views, which can be queried using graph pattern matching
+   constructs.
+  </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>
 
index 966fd39882760786f1dc5f60df2de0d88663d854..1abe6ccd3d50c3f05db1f2ad5af291424064030e 100644 (file)
 
  <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>
 
index 294f45e82a3d8568d1231d43aa8cda6d89232f0a..5b5f1f3c5df368ab7d5681039f63b8e810eae40e 100644 (file)
@@ -1677,6 +1677,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>
index 60b4c4ae8c091c57d3a4c935a8f110fea8f419c0..4be4f1ef1eff82b7bc9b53626c8d8ff2b6b21fe9 100644 (file)
@@ -4171,6 +4171,1098 @@ ORDER BY c.ordinal_position;
   </table>
  </sect1>
 
+ <sect1 id="infoschema-pg-edge-table-components">
+  <title><literal>pg_edge_table_components</literal></title>
+
+  <para>
+   The view <literal>pg_edge_table_components</literal> identifies which
+   columns are part of the source or destination vertex keys, as well as their
+   corresponding columns in the vertex tables being linked to, in the edge
+   tables of property graphs defined in the current database.  Only those
+   property graphs are shown that the current user has access to (by way of
+   being the owner or having some privilege).
+  </para>
+
+  <para>
+   The source and destination vertex links of edge tables are specified in
+   <command>CREATE PROPERTY GRAPH</command> and default to foreign keys in
+   certain cases.
+  </para>
+
+  <table>
+   <title><structname>pg_edge_table_components</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>edge_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       The element table alias of the edge table being described
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vertex_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       The element table alias of the source or destination vertex table being linked to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>edge_end</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Either <literal>SOURCE</literal> or <literal>DESTINATION</literal>;
+       specifies which edge link is being described.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>edge_table_column_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the column that is part of the source or destination vertex key in this edge table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>vertex_table_column_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the column that is part of the key in the source or destination
+       vertex table being linked to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ordinal_position</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Ordinal position of the columns within the key (count starts at 1)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-key-columns">
+  <title><literal>pg_element_table_key_columns</literal></title>
+
+  <para>
+   The view <literal>pg_element_table_key_columns</literal> identifies which
+   columns are part of the keys of the element tables of property graphs defined
+   in the current database.  Only those property graphs are shown that the
+   current user has access to (by way of being the owner or having some
+   privilege).
+  </para>
+
+  <para>
+   The key of an element table uniquely identifies the rows in it.  It is
+   either specified using the <literal>KEY</literal> clause in <command>CREATE
+   PROPERTY GRAPH</command> or defaults to the primary key.
+  </para>
+
+  <table>
+   <title><structname>pg_element_table_key_columns</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>column_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the column that is part of the key
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>ordinal_position</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Ordinal position of the column within the key (count starts at 1)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-labels">
+  <title><literal>pg_element_table_labels</literal></title>
+
+  <para>
+   The view <literal>pg_element_table_labels</literal> shows which labels are
+   defined on the element tables of property graphs defined in the current
+   database.  Only those property graphs are shown that the current user has
+   access to (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_element_table_labels</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>label_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the label
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-table-properties">
+  <title><literal>pg_element_table_properties</literal></title>
+
+  <para>
+   The view <literal>pg_element_table_properties</literal> shows the definitions
+   of the properties for the element tables of property graphs defined in the
+   current database.  Only those property graphs are shown that the current user
+   has access to (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_element_table_properties</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_expression</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Expression of the property definition for this element table
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-element-tables">
+  <title><literal>pg_element_tables</literal></title>
+
+  <para>
+   The view <literal>pg_element_tables</literal> contains information about
+   the element tables of property graphs defined in the current database.
+   Only those property graphs are shown that the current user has access to
+   (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_element_tables</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_alias</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Element table alias (unique identifier of an element table within a
+       property graph)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_kind</structfield> <type>character_data</type>
+      </para>
+      <para>
+       The kind of the element table: <literal>EDGE</literal> or <literal>VERTEX</literal>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>table_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the referenced table (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>table_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the referenced table
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>table_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the table being referenced by the element table definition
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>element_table_definition</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-label-properties">
+  <title><literal>pg_label_properties</literal></title>
+
+  <para>
+   The view <literal>pg_label_properties</literal> shows which properties are
+   defined on labels defined in property graphs defined in the current
+   database.  Only those property graphs are shown that the current user has
+   access to (by way of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_label_properties</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>label_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the label
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-labels">
+  <title><literal>pg_labels</literal></title>
+
+  <para>
+   The view <literal>pg_labels</literal> contains all the labels defined in
+   property graphs defined in the current database.  Only those property
+   graphs are shown that the current user has access to (by way of being the
+   owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_labels</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>label_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the label
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-property-data-types">
+  <title><literal>pg_property_data_types</literal></title>
+
+  <para>
+   The view <literal>pg_property_data_types</literal> shows the data types of
+   the properties in property graphs defined in the current database.  Only
+   those property graphs are shown that the current user has access to (by way
+   of being the owner or having some privilege).
+  </para>
+
+  <table>
+   <title><structname>pg_property_data_types</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>data_type</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Data type of the property, if it is a built-in type, or
+       <literal>ARRAY</literal> if it is some array (in that case, see the
+       view <literal>element_types</literal>), else
+       <literal>USER-DEFINED</literal> (in that case, the type is identified
+       in <literal>attribute_udt_name</literal> and associated columns).
+       </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_maximum_length</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a character or bit
+       string type, the declared maximum length; null for all other
+       data types or if no maximum length was declared.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_octet_length</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a character type,
+       the maximum possible length in octets (bytes) of a datum; null
+       for all other data types.  The maximum octet length depends on
+       the declared character maximum length (see above) and the
+       server encoding.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_set_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_set_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>character_set_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collation_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database containing the collation of the property (always
+       the current database), null if default or the data type of the
+       property is not collatable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collation_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema containing the collation of the property, null if
+       default or the data type of the property is not collatable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>collation_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the collation of the property, null if default or the data type
+       of the property is not collatable
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>numeric_precision</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a numeric type, this
+       column contains the (declared or implicit) precision of the
+       type for this attribute.  The precision indicates the number of
+       significant digits.  It can be expressed in decimal (base 10)
+       or binary (base 2) terms, as specified in the column
+       <literal>numeric_precision_radix</literal>.  For all other data
+       types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>numeric_precision_radix</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a numeric type, this
+       column indicates in which base the values in the columns
+       <literal>numeric_precision</literal> and
+       <literal>numeric_scale</literal> are expressed.  The value is
+       either 2 or 10.  For all other data types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>numeric_scale</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies an exact numeric
+       type, this column contains the (declared or implicit) scale of
+       the type for this attribute.  The scale indicates the number of
+       significant digits to the right of the decimal point.  It can
+       be expressed in decimal (base 10) or binary (base 2) terms, as
+       specified in the column
+       <literal>numeric_precision_radix</literal>.  For all other data
+       types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>datetime_precision</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies a date, time,
+       timestamp, or interval type, this column contains the (declared
+       or implicit) fractional seconds precision of the type for this
+       attribute, that is, the number of decimal digits maintained
+       following the decimal point in the seconds value.  For all
+       other data types, this column is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>interval_type</structfield> <type>character_data</type>
+      </para>
+      <para>
+       If <literal>data_type</literal> identifies an interval type,
+       this column contains the specification which fields the
+       intervals include for this attribute, e.g., <literal>YEAR TO
+       MONTH</literal>, <literal>DAY TO SECOND</literal>, etc.  If no
+       field restrictions were specified (that is, the interval
+       accepts all fields), and for all other data types, this field
+       is null.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>interval_precision</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Applies to a feature not available
+       in <productname>PostgreSQL</productname>
+       (see <literal>datetime_precision</literal> for the fractional
+       seconds precision of interval type properties)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_defined_type_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that the property data type is defined in
+       (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_defined_type_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that the property data type is defined in
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>user_defined_type_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property data type
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>scope_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>scope_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>scope_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Applies to a feature not available in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>maximum_cardinality</structfield> <type>cardinal_number</type>
+      </para>
+      <para>
+       Always null, because arrays always have unlimited maximum cardinality in <productname>PostgreSQL</productname>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>dtd_identifier</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       An identifier of the data type descriptor of the property, unique
+       among the data type descriptors pertaining to the property graph.  This
+       is mainly useful for joining with other instances of such
+       identifiers.  (The specific format of the identifier is not
+       defined and not guaranteed to remain the same in future
+       versions.)
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-pg-property-graph-privileges">
+  <title><literal>pg_property_graph_privileges</literal></title>
+
+  <para>
+   The view <literal>pg_property_graph_privileges</literal> identifies all
+   privileges granted on property graphs to a currently enabled role or by a
+   currently enabled role.  There is one row for each combination of property
+   graph, grantor, and grantee.
+  </para>
+
+  <table>
+   <title><structname>pg_property_graph_privileges</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>grantor</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the role that granted the privilege
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>grantee</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the role that the privilege was granted to
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>privilege_type</structfield> <type>character_data</type>
+      </para>
+      <para>
+       Type of the privilege: <literal>SELECT</literal> is the only privilege
+       type applicable to property graphs.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>is_grantable</structfield> <type>yes_or_no</type>
+      </para>
+      <para>
+       <literal>YES</literal> if the privilege is grantable, <literal>NO</literal> if not
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="infoschema-property-graphs">
+  <title><literal>property_graphs</literal></title>
+
+  <para>
+   The view <literal>property_graphs</literal> contains all property graphs
+   defined in the current database.  Only those property graphs are shown that
+   the current user has access to (by way of being the owner or having some
+   privilege).
+  </para>
+
+  <table>
+   <title><structname>property_graphs</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_catalog</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the database that contains the property graph (always the current database)
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_schema</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the schema that contains the property graph
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>property_graph_name</structfield> <type>sql_identifier</type>
+      </para>
+      <para>
+       Name of the property graph
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="infoschema-referential-constraints">
   <title><literal>referential_constraints</literal></title>
 
diff --git a/doc/src/sgml/keywords/sql2023-16-nonreserved.txt b/doc/src/sgml/keywords/sql2023-16-nonreserved.txt
new file mode 100644 (file)
index 0000000..39756c6
--- /dev/null
@@ -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 (file)
index 0000000..3bdd7e2
--- /dev/null
@@ -0,0 +1,12 @@
+ALL_DIFFERENT
+BINDING_COUNT
+ELEMENT_ID
+ELEMENT_NUMBER
+EXPORT
+GRAPH
+GRAPH_TABLE
+MATCHNUM
+PATH_LENGTH
+PATH_NAME
+PROPERTY_EXISTS
+SAME
index 4b522213171b86c004b5657bceb89670cd141ede..ec4ca01cd16445682e2525489417fd6b9a04aeed 100644 (file)
@@ -863,6 +863,11 @@ ORDER BY p;
      to columns provided by preceding <literal>FROM</literal> items in any case.
     </para>
 
+    <para>
+     A <literal>GRAPH_TABLE</literal> <literal>FROM</literal> item can also
+     always contain lateral references.
+    </para>
+
     <para>
      A <literal>LATERAL</literal> item can appear at the top level in the
      <literal>FROM</literal> list, or within a <literal>JOIN</literal> tree.  In the latter
@@ -2771,4 +2776,161 @@ SELECT * FROM t;
 
  </sect1>
 
+ <sect1 id="queries-graph">
+  <title>Graph Queries</title>
+
+  <para>
+   This section describes the sublanguage for querying property graphs,
+   defined as described in <xref linkend="ddl-property-graphs"/>.
+  </para>
+
+  <sect2 id="queries-graph-overview">
+   <title>Overview</title>
+
+   <para>
+    Consider this example from <xref linkend="ddl-property-graphs"/>:
+<programlisting>
+-- get list of customers active today
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders]->(o IS orders WHERE o.ordered_when = current_date) COLUMNS (c.name AS customer_name));
+</programlisting>
+    The graph query part happens inside the <literal>GRAPH_TABLE</literal>
+    construct.  As far as the rest of the query is concerned, this acts like a
+    table function in that it produces a computed table as output.  Like other
+    <literal>FROM</literal> clause elements, table alias and column alias
+    names can be assigned to the result, and the result can be joined with
+    other tables, subsequently filtered, and so on, for example:
+<programlisting>
+SELECT ... FROM GRAPH_TABLE (mygraph MATCH ... COLUMNS (...)) AS myresult (a, b, c) JOIN othertable USING (a) WHERE b > 0 ORDER BY c;
+</programlisting>
+   </para>
+
+   <para>
+    The <literal>GRAPH_TABLE</literal> clause consists of the graph name,
+    followed by the keyword <literal>MATCH</literal>, followed by a graph
+    pattern expression (see below), followed by the keyword
+    <literal>COLUMNS</literal> and a column list.
+   </para>
+  </sect2>
+
+  <sect2 id="queries-graph-patterns">
+   <title>Graph Patterns</title>
+
+   <para>
+    The core of the graph querying functionality is the graph pattern, which
+    appears after the keyword <literal>MATCH</literal>.  Formally, a graph
+    pattern consists of one or more path patterns.  A path is a sequence of
+    graph elements, starting and ending with a vertex and alternating between
+    vertices and edges.  A path pattern is a syntactic expressions that
+    matches paths.
+   </para>
+
+   <para>
+    A path pattern thus matches a sequence of vertices and edges.  The
+    simplest possible path pattern is
+<programlisting>
+()
+</programlisting>
+    which matches a single vertex.  The next simplest pattern would be
+<programlisting>
+()-[]->()
+</programlisting>
+    which matches a vertex followed by an edge followed by a vertex.  The
+    characters <literal>()</literal> are a vertex pattern and the characters
+    <literal>-[]-></literal> are an edge pattern.
+   </para>
+
+   <para>
+    These characters can also be separated by whitespace, for example:
+<programlisting>
+( ) - [ ] - > ( )
+</programlisting>
+   </para>
+
+   <tip>
+    <para>
+     A way to remember these symbols is that in visual representations of
+     property graphs, vertices are usually circles (like
+     <literal>()</literal>) and edges have rectangular labels (like
+     <literal>[]</literal>).
+    </para>
+   </tip>
+
+   <para>
+    The above patterns would match any vertex, or any two vertices connected
+    by any edge, which isn't very interesting.  Normally, we want to search
+    for elements (vertices and edges) that have certain characteristics.
+    These characteristics are written in between the parentheses or brackets.
+    (This is also called an element pattern filler.)  Typically, we would
+    search for elements with a certain label.  This is written by <literal>IS
+    <replaceable>labelname</replaceable></literal>.  For example, this would
+    match all vertices with the label <literal>person</literal>:
+<programlisting>
+(IS person)
+</programlisting>
+    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>
+(IS person)-[IS has]->(IS account)
+</programlisting>
+    Multiple labels can also be matched, using <quote>or</quote> semantics:
+<programlisting>
+(IS person)-[IS has]->(IS account|creditcard)
+</programlisting>
+   </para>
+
+   <para>
+    Recall that edges are directed.  The other direction is also possible in a
+    path pattern, for example:
+<programlisting>
+(IS account)&lt;-[IS has]-(IS person)
+</programlisting>
+    It is also possible to match both directions:
+<programlisting>
+(IS person)-[IS is_friend_of]-(IS 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>
+(IS person)->(IS account)
+(IS account)&lt;-(IS person)
+(IS person)-(IS 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 IS person)-[h IS has]->(a IS 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>
+(IS person)-[IS has]->(a IS account WHERE a.type = 'savings')
+</programlisting>
+   </para>
+
+   <!-- TODO: multiple path patterns in a graph pattern (comma-separated) -->
+
+   <!-- TODO: quantifiers -->
+
+  </sect2>
+ </sect1>
+
 </chapter>
index 141ada9c50a05a7e0c81ca057f8d66da0f740a30..e1a56c362219a63d60f42400ac0e039e5a4728fc 100644 (file)
@@ -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">
index c819c7bb4e3c474d4efcb25a9538a1cb5c978175..60218fcd01c070080bcded74f5e7ba34a31a1320 100644 (file)
@@ -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 (file)
index 0000000..19352c0
--- /dev/null
@@ -0,0 +1,299 @@
+<!--
+doc/src/sgml/ref/alter_property_graph.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-alter-property-graph">
+ <indexterm zone="sql-alter-property-graph">
+  <primary>ALTER PROPERTY GRAPH</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER PROPERTY GRAPH</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER PROPERTY GRAPH</refname>
+  <refpurpose>change the definition of an SQL-property graph</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ADD
+    [ {VERTEX|NODE} TABLES ( <replaceable class="parameter">vertex_table_definition</replaceable> [, ...] ) ]
+    [ {EDGE|RELATIONSHIP} TABLES ( <replaceable class="parameter">edge_table_definition</replaceable> [, ...] ) ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> DROP
+    {VERTEX|NODE} TABLES ( <replaceable class="parameter">vertex_table_alias</replaceable> [, ...] ) [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> DROP
+    {EDGE|RELATIONSHIP} TABLES ( <replaceable class="parameter">edge_table_alias</replaceable> [, ...] ) [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    { ADD LABEL <replaceable class="parameter">label_name</replaceable> [ NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] ) ] } [ ... ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    DROP LABEL <replaceable class="parameter">label_name</replaceable> [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    ALTER LABEL <replaceable class="parameter">label_name</replaceable> ADD PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] )
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> ALTER
+    {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE <replaceable class="parameter">element_table_alias</replaceable>
+    ALTER LABEL <replaceable class="parameter">label_name</replaceable> DROP PROPERTIES ( <replaceable class="parameter">property_name</replaceable> [, ...] ) [ CASCADE | RESTRICT ]
+
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
+ALTER PROPERTY GRAPH <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
+ALTER PROPERTY GRAPH [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER PROPERTY GRAPH</command> changes the definition of an
+   existing property graph.  There are several subforms:
+
+   <variablelist>
+    <varlistentry>
+     <term><literal>ADD {VERTEX|NODE|EDGE|RELATIONSHIP} TABLES</literal></term>
+     <listitem>
+      <para>
+       This form adds new vertex or edge tables to the property graph, using the
+       same syntax as <link linkend="sql-create-property-graph"><command>CREATE
+       PROPERTY GRAPH</command></link>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>DROP {VERTEX|NODE|EDGE|RELATIONSHIP} TABLES</literal></term>
+     <listitem>
+      <para>
+       This form removes vertex or edge tables from the property graph.  (Only
+       the association of the tables with the graph is removed.  The tables
+       themself are not dropped.)
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ADD LABEL</literal></term>
+     <listitem>
+      <para>
+       This form adds a new label to an existing vertex or edge table, using
+       the same syntax as <link
+       linkend="sql-create-property-graph"><command>CREATE PROPERTY
+       GRAPH</command></link>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... DROP LABEL</literal></term>
+     <listitem>
+      <para>
+       This form removes a label from an existing vertex or edge table.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ALTER LABEL ... ADD PROPERTIES</literal></term>
+     <listitem>
+      <para>
+       This form adds new properties to an existing label on an existing
+       vertex or edge table.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ALTER LABEL ... DROP PROPERTIES</literal></term>
+     <listitem>
+      <para>
+       This form removes properties from an existing label on an existing
+       vertex or edge table.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>OWNER</literal></term>
+     <listitem>
+      <para>
+       This form changes the owner of the property graph to the specified user.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>RENAME</literal></term>
+     <listitem>
+      <para>
+       This form changes the name of a property graph.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>SET SCHEMA</literal></term>
+     <listitem>
+      <para>
+       This form moves the property graph into another schema.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </para>
+
+  <para>
+   You must own the property graph to use <command>ALTER PROPERTY
+   GRAPH</command>.  To change a property graph's schema, you must also have
+   <literal>CREATE</literal> privilege on the new schema.  To alter the owner,
+   you must be able to <literal>SET ROLE</literal> to the new owning role, and
+   that role must have <literal>CREATE</literal> privilege on the property
+   graph's schema.  (These restrictions enforce that altering the owner
+   doesn't do anything you couldn't do by dropping and recreating the property
+   graph.  However, a superuser can alter ownership of any property graph
+   anyway.)
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of a property graph to be altered.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the property graph does not exist.  A notice is
+      issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">vertex_table_definition</replaceable></term>
+    <term><replaceable class="parameter">edge_table_definition</replaceable></term>
+    <listitem>
+     <para>
+      See <link linkend="sql-create-property-graph"><command>CREATE PROPERTY
+      GRAPH</command></link>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">vertex_table_alias</replaceable></term>
+    <term><replaceable class="parameter">edge_table_alias</replaceable></term>
+    <listitem>
+     <para>
+      The alias of an existing vertex or edge table to operate on.  (Note that
+      the alias is potentially different from the name of the underlying
+      table, if the vertex or edge table was created with <literal>AS
+      <replaceable class="parameter">alias</replaceable></literal>.)
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">label_name</replaceable></term>
+    <term><replaceable class="parameter">property_name</replaceable></term>
+    <term><replaceable class="parameter">expression</replaceable></term>
+    <listitem>
+     <para>
+      See <link linkend="sql-create-property-graph"><command>CREATE PROPERTY
+      GRAPH</command></link>.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_owner</replaceable></term>
+    <listitem>
+     <para>
+      The user name of the new owner of the property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name for the property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_schema</replaceable></term>
+    <listitem>
+     <para>
+      The new schema for the property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The consistency checks on a property graph described at <xref
+   linkend="sql-create-property-graph-notes"/> must be maintained by
+   <command>ALTER PROPERTY GRAPH</command> operations.  In some cases, it
+   might be necessary to make multiple alterations in a single command to
+   satisfy the checks.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+<programlisting>
+ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (v2);
+
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL foo;
+
+ALTER PROPERTY GRAPH g1 RENAME TO g2;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER PROPERTY GRAPH</command> conforms to ISO/IEC 9075-16
+   (SQL/PGQ).
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-create-property-graph"/></member>
+   <member><xref linkend="sql-drop-property-graph"/></member>
+  </simplelist>
+ </refsect1>
+</refentry>
index fa71144c9145b887b32fea59b75159def523d0e1..6d8479d6829ab6948baa5f7b6fcd637690b17495 100644 (file)
@@ -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 (file)
index 0000000..92f8703
--- /dev/null
@@ -0,0 +1,318 @@
+<!--
+doc/src/sgml/ref/create_property_graph.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-create-property-graph">
+ <indexterm zone="sql-create-property-graph">
+  <primary>CREATE PROPERTY GRAPH</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE PROPERTY GRAPH</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE PROPERTY GRAPH</refname>
+  <refpurpose>define an SQL-property graph</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE [ TEMP | TEMPORARY ] PROPERTY GRAPH <replaceable class="parameter">name</replaceable>
+    [ {VERTEX|NODE} TABLES ( <replaceable class="parameter">vertex_table_definition</replaceable> [, ...] ) ]
+    [ {EDGE|RELATIONSHIP} TABLES ( <replaceable class="parameter">edge_table_definition</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">vertex_table_definition</replaceable> is:</phrase>
+
+    <replaceable class="parameter">vertex_table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ] [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] [ <replaceable class="parameter">element_table_label_and_properties</replaceable> ]
+
+<phrase>and <replaceable class="parameter">edge_table_definition</replaceable> is:</phrase>
+
+    <replaceable class="parameter">edge_table_name</replaceable> [ AS <replaceable class="parameter">alias</replaceable> ] [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
+        SOURCE [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) REFERENCES ] <replaceable class="parameter">source_table</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
+        DESTINATION [ KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] ) REFERENCES ] <replaceable class="parameter">dest_table</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
+        [ <replaceable class="parameter">element_table_label_and_properties</replaceable> ]
+
+<phrase>and <replaceable class="parameter">element_table_label_and_properties</replaceable> is either:</phrase>
+
+    NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] )
+
+<phrase>or:</phrase>
+
+   { { LABEL <replaceable class="parameter">label_name</replaceable> | DEFAULT LABEL } [ NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { <replaceable class="parameter">expression</replaceable> [ AS <replaceable class="parameter">property_name</replaceable> ] } [, ...] ) ] } [...]
+</synopsis>
+</refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE PROPERTY GRAPH</command> defines a property graph.  A
+   property graph consists of vertices and edges, together called elements,
+   each with associated labels and properties, and can be queried using the
+   <literal>GRAPH_TABLE</literal> clause of <xref linkend="sql-select"/> with
+   a special path matching syntax.  The data in the graph is stored in regular
+   tables (or views, foreign tables, etc.).  Each vertex or edge corresponds
+   to a table.  The property graph definition links these tables together into
+   a graph structure that can be queried using graph query techniques.
+  </para>
+
+  <para>
+   <command>CREATE PROPERTY GRAPH</command> does not physically materialize a
+   graph.  It is thus similar to <command>CREATE VIEW</command> in that it
+   records a structure that is used only when the defined object is queried.
+  </para>
+
+  <para>
+   If a schema name is given (for example, <literal>CREATE PROPERTY GRAPH
+   myschema.mygraph ...</literal>) then the property graph is created in the
+   specified schema.  Otherwise it is created in the current schema.
+   Temporary property graphs exist in a special schema, so a schema name
+   cannot be given when creating a temporary property graph.  Property graphs
+   share a namespace with tables and other relation types, so the name of the
+   property graph must be distinct from the name of any other relation (table,
+   sequence, index, view, materialized view, or foreign table) in the same
+   schema.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the new property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>VERTEX</literal>/<literal>NODE</literal></term>
+    <term><literal>EDGE</literal>/<literal>RELATIONSHIP</literal></term>
+    <listitem>
+     <para>
+      These keywords are synonyms, respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">vertex_table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a table that will contain vertices in the new property
+      graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">edge_table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a table that will contain edges in the new property graph.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">alias</replaceable></term>
+    <listitem>
+     <para>
+      A unique identifier for the vertex or edge table.  This defaults to the
+      name of the table.  Aliases must be unique in a property graph
+      definition (across all vertex table and edge table definitions).
+      (Therefore, if a table is used more than once as a vertex or edge table,
+      then an explicit alias must be specified for at least one of them to
+      distinguish them.)
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>KEY ( <replaceable class="parameter">column_name</replaceable> [, ...] )</literal></term>
+    <listitem>
+     <para>
+      A set of columns that uniquely identifies a row in the vertex or edge
+      table.  Defaults to the primary key.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">source_table</replaceable></term>
+    <term><replaceable class="parameter">dest_table</replaceable></term>
+    <listitem>
+     <para>
+      The vertex tables that the edge table is linked to.  These refer to the
+      aliases of the source and destination vertex tables respectively.
+     </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 can be useful to assign the same
+      label to different elements.)  Each label has a list (possibly empty) of
+      properties.  By default, all columns of a table are automatically
+      exposed as properties.  This can be specified explicitly as
+      <literal>PROPERTIES ALL COLUMNS</literal>.  Alternatively, a list of
+      expressions, which can refer to the columns of the underlying table, can
+      be specified as properties.  If the expressions are not a plain column
+      reference, then an explicit property name must also be specified.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1 id="sql-create-property-graph-notes">
+  <title>Notes</title>
+
+  <para>
+   The following consistency checks must be satisfied by a property graph definition:
+
+   <itemizedlist>
+    <listitem>
+     <para>
+      In a property graph, labels with the same name applied to different
+      property graph elements must have the same number of properties and
+      those properties must have the same names.  For example, the following
+      would be allowed:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (x, y),
+        v2 LABEL foo PROPERTIES (x, y)
+    ) ...
+</programlisting>
+      but this would not:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (x, y),
+        v2 LABEL foo PROPERTIES (z)
+    ) ...
+</programlisting></para>
+    </listitem>
+
+    <listitem>
+     <para>
+      In a property graph, all properties with the same name must have the
+      same data type, independent of which label they are on.  For example,
+      this would be allowed:
+<programlisting>
+CREATE TABLE v1 (a int, b int);
+CREATE TABLE v2 (a int, b int);
+
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (a, b),
+        v2 LABEL bar PROPERTIES (a, b)
+    ) ...
+</programlisting>
+      but this would not:
+<programlisting>
+CREATE TABLE v1 (a int, b int);
+CREATE TABLE v2 (a int, b varchar);
+
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (a, b),
+        v2 LABEL bar PROPERTIES (a, b)
+    ) ...
+</programlisting></para>
+    </listitem>
+
+    <listitem>
+     <para>
+      For each property graph element, all properties with the same name must
+      have the same expression for each label.  For example, this would be
+      allowed:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (a * 2 AS x) LABEL bar PROPERTIES (a * 2 AS x)
+    ) ...
+</programlisting>
+      but this would not:
+<programlisting>
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1 LABEL foo PROPERTIES (a * 2 AS x) LABEL bar PROPERTIES (a * 10 AS x)
+    ) ...
+</programlisting></para>
+    </listitem>
+
+   </itemizedlist>
+  </para>
+
+  <para>
+   Property graphs are queried using the <literal>GRAPH_TABLE</literal> clause
+   of <xref linkend="sql-select"/>.
+  </para>
+
+  <para>
+   Access to the base relations underlying the <literal>GRAPH_TABLE</literal>
+   clause is determined by the permissions of the user executing the query,
+   rather than the property graph owner. Thus, the user of a property graph must
+   have the relevant permissions on the property graph and base relations
+   underlying the <literal>GRAPH_TABLE</literal> clause.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <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 (file)
index 0000000..e16de55
--- /dev/null
@@ -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>
index 043f5d5a40af47ef808acbd90da82a376101fccd..0e57348d893ef0b6b2c5234e3800c9f8147f439e 100644 (file)
@@ -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>
index 0e543fdd6a2120d9d48413646c4dba9f4035f924..18ba22b40d678fafb8c77471909b80b49cec81ef 100644 (file)
@@ -1293,7 +1293,7 @@ SELECT $1 \parse stmt1
 
         <listitem>
         <para>
-        For each relation (table, view, materialized view, index, sequence,
+        For each relation (table, view, materialized view, index, property graph, sequence,
         or foreign table)
         or composite type matching the
         <replaceable class="parameter">pattern</replaceable>, show all
@@ -1333,9 +1333,9 @@ SELECT $1 \parse stmt1
         <para>
         If <command>\d</command> is used without a
         <replaceable class="parameter">pattern</replaceable> argument, it is
-        equivalent to <command>\dtvmsE</command> which will show a list of
-        all visible tables, views, materialized views, sequences and
-        foreign tables.
+        equivalent to <command>\dtvmsEG</command> which will show a list of
+        all visible tables, views, materialized views, sequences,
+        foreign tables, and property graphs.
         This is purely a convenience measure.
         </para>
         <para>
@@ -1643,6 +1643,7 @@ SELECT $1 \parse stmt1
 
       <varlistentry id="app-psql-meta-command-de">
         <term><literal>\dE[Sx+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+        <term><literal>\dG[Sx+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <term><literal>\di[Sx+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <term><literal>\dm[Sx+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <term><literal>\ds[Sx+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
@@ -1651,10 +1652,10 @@ SELECT $1 \parse stmt1
 
         <listitem>
         <para>
-        In this group of commands, the letters <literal>E</literal>,
+        In this group of commands, the letters <literal>E</literal>, <literal>G</literal>,
         <literal>i</literal>, <literal>m</literal>, <literal>s</literal>,
         <literal>t</literal>, and <literal>v</literal>
-        stand for foreign table, index, materialized view,
+        stand for foreign table, index, property graph, materialized view,
         sequence, table, and view,
         respectively.
         You can specify any or all of
index 8df492281a1c519754da38197b0a865c8a30c94d..948ac534446b800cd92e77915596c8a8d6028f8d 100644 (file)
@@ -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> [, ...]
index aa45c0af2487b03b23f4e6e835cde1470875306a..c112f7a08a745b7932d45580a9354eea77e7c848 100644 (file)
@@ -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> [, ...] ] ) ] |
index ca5dd14d62778c70f4ea4abf3e7dcb15ead30343..09b6ce809bb16ae30ed359dbc91b5ae9ca38eea5 100644 (file)
@@ -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>
index d9fdbb5d254cc4e5d5875aa873899a1c111f847a..674ac17e82c863b931edc307778e259e11887a30 100644 (file)
@@ -55,6 +55,7 @@
    &alterOperatorFamily;
    &alterPolicy;
    &alterProcedure;
+   &alterPropertyGraph;
    &alterPublication;
    &alterRole;
    &alterRoutine;
    &createOperatorFamily;
    &createPolicy;
    &createProcedure;
+   &createPropertyGraph;
    &createPublication;
    &createRole;
    &createRule;
    &dropOwned;
    &dropPolicy;
    &dropProcedure;
+   &dropPropertyGraph;
    &dropPublication;
    &dropRole;
    &dropRoutine;
index 8811d41df24fb6d9d80feb0c91c8e9dbac79e334..52f38480c52ea286f451ac837aa5ecc343dc2662 100644 (file)
@@ -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:
@@ -700,6 +708,7 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant)
 
                case OBJECT_TABLE:
                case OBJECT_SEQUENCE:
+               case OBJECT_PROPGRAPH:
 
                        /*
                         * Here, we don't use get_object_address().  It requires that the
@@ -817,6 +826,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:
@@ -1022,6 +1035,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
                        all_privileges = ACL_ALL_RIGHTS_LARGEOBJECT;
                        errormsg = gettext_noop("invalid privilege type %s for large object");
                        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);
@@ -1836,11 +1853,20 @@ ExecGrant_Relation(InternalGrant *istmt)
                                         errmsg("\"%s\" is not a sequence",
                                                        NameStr(pg_class_tuple->relname))));
 
+               if (istmt->objtype == OBJECT_PROPGRAPH &&
+                       pg_class_tuple->relkind != RELKIND_PROPGRAPH)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                        errmsg("\"%s\" is not a property graph",
+                                                       NameStr(pg_class_tuple->relname))));
+
                /* Adjust the default permissions based on object type */
                if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
                {
                        if (pg_class_tuple->relkind == RELKIND_SEQUENCE)
                                this_privileges = ACL_ALL_RIGHTS_SEQUENCE;
+                       else if (pg_class_tuple->relkind == RELKIND_PROPGRAPH)
+                               this_privileges = ACL_ALL_RIGHTS_PROPGRAPH;
                        else
                                this_privileges = ACL_ALL_RIGHTS_RELATION;
                }
@@ -1934,6 +1960,9 @@ ExecGrant_Relation(InternalGrant *istmt)
                                case RELKIND_SEQUENCE:
                                        old_acl = acldefault(OBJECT_SEQUENCE, ownerId);
                                        break;
+                               case RELKIND_PROPGRAPH:
+                                       old_acl = acldefault(OBJECT_PROPGRAPH, ownerId);
+                                       break;
                                default:
                                        old_acl = acldefault(OBJECT_TABLE, ownerId);
                                        break;
@@ -2731,6 +2760,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;
@@ -2857,6 +2889,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;
@@ -2993,6 +3028,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);
index 09575278de31d1e0b0da5db336b9d3f3df7fe2ed..fdb8e67e1f5e650b8687b166f030e93064c90875 100644 (file)
 #include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
@@ -1514,6 +1519,11 @@ doDeletion(const ObjectAddress *object, int flags)
                case AccessMethodRelationId:
                case AccessMethodOperatorRelationId:
                case AccessMethodProcedureRelationId:
+               case PropgraphElementRelationId:
+               case PropgraphElementLabelRelationId:
+               case PropgraphLabelRelationId:
+               case PropgraphLabelPropertyRelationId:
+               case PropgraphPropertyRelationId:
                case NamespaceRelationId:
                case TSParserRelationId:
                case TSDictionaryRelationId:
@@ -2267,6 +2277,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;
index 49adf66ba9b291d0084b1d80c1475eff1cdda186..4f0e2492937743df45955676cd3022688c95bc3b 100644 (file)
@@ -3009,3 +3009,369 @@ CREATE VIEW user_mappings AS
     FROM _pg_user_mappings;
 
 GRANT SELECT ON user_mappings TO PUBLIC;
+
+
+-- SQL/PGQ views; these use section numbers from part 16 of the standard.
+
+/*
+ * 15.2
+ * PG_DEFINED_LABEL_SETS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.3
+ * PG_DEFINED_LABEL_SET_LABELS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.4
+ * PG_EDGE_DEFINED_LABEL_SETS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.5
+ * PG_EDGE_TABLE_COMPONENTS view
+ */
+
+CREATE VIEW pg_edge_table_components AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(eg.pgealias AS sql_identifier) AS edge_table_alias,
+           CAST(v.pgealias AS sql_identifier) AS vertex_table_alias,
+           CAST(CASE eg.end WHEN 'src' THEN 'SOURCE' WHEN 'dest' THEN 'DESTINATION' END AS character_data) AS edge_end,
+           CAST(ae.attname AS sql_identifier) AS edge_table_column_name,
+           CAST(av.attname AS sql_identifier) AS vertex_table_column_name,
+           CAST((eg.egkey).n AS cardinal_number) AS ordinal_position
+    FROM pg_namespace npg
+         JOIN
+         (SELECT * FROM pg_class WHERE relkind = 'g') AS pg
+           ON npg.oid = pg.relnamespace
+         JOIN
+         (SELECT pgepgid, pgealias, pgerelid, 'src' AS end, pgesrcvertexid AS vertexid, _pg_expandarray(pgesrckey) AS egkey, _pg_expandarray(pgesrcref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e'
+          UNION ALL
+          SELECT pgepgid, pgealias, pgerelid, 'dest' AS end, pgedestvertexid AS vertexid, _pg_expandarray(pgedestkey) AS egkey, _pg_expandarray(pgedestref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e'
+         ) AS eg
+           ON pg.oid = eg.pgepgid
+         JOIN
+         (SELECT * FROM pg_propgraph_element WHERE pgekind = 'v') AS v
+           ON eg.vertexid = v.oid
+         JOIN
+         (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS ae
+           ON eg.pgerelid = ae.attrelid AND (eg.egkey).x = ae.attnum
+         JOIN
+         (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS av
+           ON v.pgerelid = av.attrelid AND (eg.egref).x = av.attnum
+    WHERE NOT pg_is_other_temp_schema(npg.oid)
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_edge_table_components TO PUBLIC;
+
+
+/*
+ * 15.6
+ * PG_EDGE_TRIPLETS view
+ */
+
+-- TODO
+
+
+/*
+ * 15.7
+ * PG_ELEMENT_TABLE_KEY_COLUMNS view
+ */
+
+CREATE VIEW pg_element_table_key_columns AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(pgealias AS sql_identifier) AS element_table_alias,
+           CAST(a.attname AS sql_identifier) AS column_name,
+           CAST((el.ekey).n AS cardinal_number) AS ordinal_position
+    FROM pg_namespace npg
+         JOIN
+         (SELECT * FROM pg_class WHERE relkind = 'g') AS pg
+           ON npg.oid = pg.relnamespace
+         JOIN
+         (SELECT pgepgid, pgealias, pgerelid, _pg_expandarray(pgekey) AS ekey FROM pg_propgraph_element) AS el
+           ON pg.oid = el.pgepgid
+         JOIN
+         (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS a
+           ON el.pgerelid = a.attrelid AND (el.ekey).x = a.attnum
+    WHERE NOT pg_is_other_temp_schema(npg.oid)
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_table_key_columns TO PUBLIC;
+
+
+/*
+ * 15.8
+ * PG_ELEMENT_TABLE_LABELS view
+ */
+
+CREATE VIEW pg_element_table_labels AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(e.pgealias AS sql_identifier) AS element_table_alias,
+           CAST(l.pgllabel AS sql_identifier) AS label_name
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_element_label el, pg_propgraph_label l
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND el.pgelelid = e.oid
+          AND el.pgellabelid = l.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_table_labels TO PUBLIC;
+
+
+/*
+ * 15.9
+ * PG_ELEMENT_TABLE_PROPERTIES view
+ */
+
+CREATE VIEW pg_element_table_properties AS
+    SELECT DISTINCT
+           CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(e.pgealias AS sql_identifier) AS element_table_alias,
+           CAST(pr.pgpname AS sql_identifier) AS property_name,
+           CAST(pg_get_expr(plp.plpexpr, e.pgerelid) AS character_data) AS property_expression
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_element_label el, pg_propgraph_label_property plp, pg_propgraph_property pr
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND el.pgelelid = e.oid
+          AND plp.plpellabelid = el.oid
+          AND pr.oid = plp.plppropid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_table_properties TO PUBLIC;
+
+
+/*
+ * 15.10
+ * PG_ELEMENT_TABLES view
+ */
+
+CREATE VIEW pg_element_tables AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(e.pgealias AS sql_identifier) AS element_table_alias,
+           CAST(CASE e.pgekind WHEN 'e' THEN 'EDGE' WHEN 'v' THEN 'VERTEX' END AS character_data) AS element_table_kind,
+           CAST(current_database() AS sql_identifier) AS table_catalog,
+           CAST(nt.nspname AS sql_identifier) AS table_schema,
+           CAST(t.relname AS sql_identifier) AS table_name,
+           CAST(NULL AS character_data) AS element_table_definition
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_class t, pg_namespace nt
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND e.pgerelid = t.oid
+          AND t.relnamespace = nt.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_element_tables TO PUBLIC;
+
+
+/*
+ * 15.11
+ * PG_LABEL_PROPERTIES view
+ */
+
+CREATE VIEW pg_label_properties AS
+    SELECT DISTINCT
+           CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(l.pgllabel AS sql_identifier) AS label_name,
+           CAST(pr.pgpname AS sql_identifier) AS property_name
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l, pg_propgraph_element_label el, pg_propgraph_label_property plp, pg_propgraph_property pr
+    WHERE pg.relnamespace = npg.oid
+          AND e.pgepgid = pg.oid
+          AND el.pgelelid = e.oid
+          AND plp.plpellabelid = el.oid
+          AND pr.oid = plp.plppropid
+          AND el.pgellabelid = l.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_label_properties TO PUBLIC;
+
+
+/*
+ * 15.12
+ * PG_LABELS view
+ */
+
+CREATE VIEW pg_labels AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(l.pgllabel AS sql_identifier) AS label_name
+    FROM pg_namespace npg, pg_class pg, pg_propgraph_label l
+    WHERE pg.relnamespace = npg.oid
+          AND l.pglpgid = pg.oid
+          AND pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          AND (pg_has_role(pg.relowner, 'USAGE')
+               OR has_table_privilege(pg.oid, 'SELECT'));
+
+GRANT SELECT ON pg_labels TO PUBLIC;
+
+
+/*
+ * 15.13
+ * PG_PROPERTY_DATA_TYPES view
+ */
+
+CREATE VIEW pg_property_data_types AS
+    SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog,
+           CAST(npg.nspname AS sql_identifier) AS property_graph_schema,
+           CAST(pg.relname AS sql_identifier) AS property_graph_name,
+           CAST(pgp.pgpname AS sql_identifier) AS property_name,
+
+           CAST(
+             CASE WHEN t.typtype = 'd' THEN
+               CASE WHEN bt.typelem <> 0 AND bt.typlen = -1 THEN 'ARRAY'
+                    WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, null)
+                    ELSE 'USER-DEFINED' END
+             ELSE
+               CASE WHEN t.typelem <> 0 AND t.typlen = -1 THEN 'ARRAY'
+                    WHEN nt.nspname = 'pg_catalog' THEN format_type(pgp.pgptypid, null)
+                    ELSE 'USER-DEFINED' END
+             END
+             AS character_data)
+             AS data_type,
+
+           CAST(null AS cardinal_number) AS character_maximum_length,
+           CAST(null AS cardinal_number) AS character_octet_length,
+           CAST(null AS sql_identifier) AS character_set_catalog,
+           CAST(null AS sql_identifier) AS character_set_schema,
+           CAST(null AS sql_identifier) AS character_set_name,
+           CAST(current_database() AS sql_identifier) AS collation_catalog,
+           CAST(nc.nspname AS sql_identifier) AS collation_schema,
+           CAST(c.collname AS sql_identifier) AS collation_name,
+           CAST(null AS cardinal_number) AS numeric_precision,
+           CAST(null AS cardinal_number) AS numeric_precision_radix,
+           CAST(null AS cardinal_number) AS numeric_scale,
+           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)
+         LEFT JOIN (pg_collation c JOIN pg_namespace nc ON (c.collnamespace = nc.oid))
+           ON pgp.pgpcollation = c.oid AND (nc.nspname, c.collname) <> ('pg_catalog', 'default')
+
+    WHERE pg.relkind = 'g'
+          AND (NOT pg_is_other_temp_schema(npg.oid))
+          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;
index d32aaff2821141dfeeebeaa5757ea8eb21de0a8f..7c93f5240eda5cee890403a14608aaeb29fc50cc 100644 (file)
 #include "catalog/pg_parameter_acl.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
@@ -370,6 +375,76 @@ static const ObjectPropertyType ObjectProperty[] =
                OBJECT_OPFAMILY,
                true
        },
+       {
+               "property graph element",
+               PropgraphElementRelationId,
+               PropgraphElementObjectIndexId,
+               PROPGRAPHELOID,
+               PROPGRAPHELALIAS,
+               Anum_pg_propgraph_element_oid,
+               Anum_pg_propgraph_element_pgealias,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               -1,
+               false
+       },
+       {
+               "property graph element label",
+               PropgraphElementLabelRelationId,
+               PropgraphElementLabelObjectIndexId,
+               -1,
+               -1,
+               Anum_pg_propgraph_element_label_oid,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               -1,
+               false
+       },
+       {
+               "property graph label",
+               PropgraphLabelRelationId,
+               PropgraphLabelObjectIndexId,
+               PROPGRAPHLABELOID,
+               PROPGRAPHLABELNAME,
+               Anum_pg_propgraph_label_oid,
+               Anum_pg_propgraph_label_pgllabel,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               -1,
+               false
+       },
+       {
+               "property graph label property",
+               PropgraphLabelPropertyRelationId,
+               PropgraphLabelPropertyObjectIndexId,
+               -1,
+               -1,
+               Anum_pg_propgraph_label_property_oid,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               -1,
+               false
+       },
+       {
+               "property graph property",
+               PropgraphPropertyRelationId,
+               PropgraphPropertyObjectIndexId,
+               -1,
+               PROPGRAPHPROPNAME,
+               Anum_pg_propgraph_property_oid,
+               Anum_pg_propgraph_property_pgpname,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               InvalidAttrNumber,
+               -1,
+               false
+       },
        {
                "role",
                AuthIdRelationId,
@@ -679,6 +754,9 @@ static const struct object_type_map
        {
                "foreign table", OBJECT_FOREIGN_TABLE
        },
+       {
+               "property graph", OBJECT_PROPGRAPH
+       },
        {
                "table column", OBJECT_COLUMN
        },
@@ -814,6 +892,15 @@ static const struct object_type_map
        {
                "policy", OBJECT_POLICY
        },
+       {
+               "property graph element", -1
+       },
+       {
+               "property graph label", -1
+       },
+       {
+               "property graph property", -1
+       },
        {
                "publication", OBJECT_PUBLICATION
        },
@@ -949,6 +1036,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,
@@ -1361,6 +1449,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,
@@ -2280,6 +2375,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:
@@ -2399,6 +2495,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:
@@ -3976,6 +4073,156 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
                                break;
                        }
 
+               case PropgraphElementRelationId:
+                       {
+                               HeapTuple       tup;
+                               Form_pg_propgraph_element pgeform;
+
+                               tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(object->objectId));
+                               if (!HeapTupleIsValid(tup))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "cache lookup failed for property graph element %u",
+                                                        object->objectId);
+                                       break;
+                               }
+
+                               pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup);
+
+                               if (pgeform->pgekind == PGEKIND_VERTEX)
+                                       /* translator: followed by, e.g., "property graph %s" */
+                                       appendStringInfo(&buffer, _("vertex %s of "), NameStr(pgeform->pgealias));
+                               else if (pgeform->pgekind == PGEKIND_EDGE)
+                                       /* translator: followed by, e.g., "property graph %s" */
+                                       appendStringInfo(&buffer, _("edge %s of "), NameStr(pgeform->pgealias));
+                               else
+                                       appendStringInfo(&buffer, "??? element %s of ", NameStr(pgeform->pgealias));
+                               getRelationDescription(&buffer, pgeform->pgepgid, false);
+
+                               ReleaseSysCache(tup);
+                               break;
+                       }
+
+               case PropgraphElementLabelRelationId:
+                       {
+                               Relation        rel;
+                               SysScanDesc scan;
+                               ScanKeyData key[1];
+                               HeapTuple       tuple;
+                               Form_pg_propgraph_element_label pgelform;
+                               ObjectAddress oa;
+
+                               rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+                               ScanKeyInit(&key[0],
+                                                       Anum_pg_propgraph_element_label_oid,
+                                                       BTEqualStrategyNumber, F_OIDEQ,
+                                                       ObjectIdGetDatum(object->objectId));
+
+                               scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key);
+                               tuple = systable_getnext(scan);
+                               if (!HeapTupleIsValid(tuple))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "could not find tuple for element label %u", object->objectId);
+
+                                       systable_endscan(scan);
+                                       table_close(rel, AccessShareLock);
+                                       break;
+                               }
+
+                               pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+                               appendStringInfo(&buffer, _("label %s of "), get_propgraph_label_name(pgelform->pgellabelid));
+                               ObjectAddressSet(oa, PropgraphElementRelationId, pgelform->pgelelid);
+                               appendStringInfoString(&buffer, getObjectDescription(&oa, false));
+
+                               systable_endscan(scan);
+                               table_close(rel, AccessShareLock);
+                               break;
+                       }
+
+               case PropgraphLabelRelationId:
+                       {
+                               HeapTuple       tuple;
+                               Form_pg_propgraph_label pglform;
+
+                               tuple = SearchSysCache1(PROPGRAPHLABELOID, object->objectId);
+                               if (!HeapTupleIsValid(tuple))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "could not find tuple for label %u", object->objectId);
+                                       break;
+                               }
+
+                               pglform = (Form_pg_propgraph_label) GETSTRUCT(tuple);
+
+                               /* translator: followed by, e.g., "property graph %s" */
+                               appendStringInfo(&buffer, _("label %s of "), NameStr(pglform->pgllabel));
+                               getRelationDescription(&buffer, pglform->pglpgid, false);
+                               ReleaseSysCache(tuple);
+                               break;
+                       }
+
+               case PropgraphLabelPropertyRelationId:
+                       {
+                               Relation        rel;
+                               SysScanDesc scan;
+                               ScanKeyData key[1];
+                               HeapTuple       tuple;
+                               Form_pg_propgraph_label_property plpform;
+                               ObjectAddress oa;
+
+                               rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+                               ScanKeyInit(&key[0],
+                                                       Anum_pg_propgraph_label_property_oid,
+                                                       BTEqualStrategyNumber, F_OIDEQ,
+                                                       ObjectIdGetDatum(object->objectId));
+
+                               scan = systable_beginscan(rel, PropgraphLabelPropertyObjectIndexId, true, NULL, 1, key);
+                               tuple = systable_getnext(scan);
+                               if (!HeapTupleIsValid(tuple))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "could not find tuple for label property %u", object->objectId);
+
+                                       systable_endscan(scan);
+                                       table_close(rel, AccessShareLock);
+                                       break;
+                               }
+
+                               plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple);
+
+                               appendStringInfo(&buffer, _("property %s of "), get_propgraph_property_name(plpform->plppropid));
+                               ObjectAddressSet(oa, PropgraphElementLabelRelationId, plpform->plpellabelid);
+                               appendStringInfoString(&buffer, getObjectDescription(&oa, false));
+
+                               systable_endscan(scan);
+                               table_close(rel, AccessShareLock);
+                               break;
+                       }
+
+               case PropgraphPropertyRelationId:
+                       {
+                               HeapTuple       tuple;
+                               Form_pg_propgraph_property pgpform;
+
+                               tuple = SearchSysCache1(PROPGRAPHPROPOID, object->objectId);
+                               if (!HeapTupleIsValid(tuple))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "could not find tuple for property %u", object->objectId);
+                                       break;
+                               }
+
+                               pgpform = (Form_pg_propgraph_property) GETSTRUCT(tuple);
+
+                               /* translator: followed by, e.g., "property graph %s" */
+                               appendStringInfo(&buffer, _("property %s of "), NameStr(pgpform->pgpname));
+                               getRelationDescription(&buffer, pgpform->pgppgid, false);
+                               ReleaseSysCache(tuple);
+                               break;
+                       }
+
                case PublicationRelationId:
                        {
                                char       *pubname = get_publication_name(object->objectId,
@@ -4161,6 +4408,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"),
@@ -4650,6 +4901,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
                        appendStringInfoString(&buffer, "policy");
                        break;
 
+               case PropgraphElementRelationId:
+                       appendStringInfoString(&buffer, "property graph element");
+                       break;
+
+               case PropgraphLabelRelationId:
+                       appendStringInfoString(&buffer, "property graph label");
+                       break;
+
+               case PropgraphPropertyRelationId:
+                       appendStringInfoString(&buffer, "property graph property");
+                       break;
+
                case PublicationRelationId:
                        appendStringInfoString(&buffer, "publication");
                        break;
@@ -4731,6 +4994,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");
@@ -5895,6 +6161,73 @@ getObjectIdentityParts(const ObjectAddress *object,
                                break;
                        }
 
+               case PropgraphElementRelationId:
+                       {
+                               HeapTuple       tup;
+                               Form_pg_propgraph_element pge;
+
+                               tup = SearchSysCache1(PROPGRAPHELOID, object->objectId);
+                               if (!HeapTupleIsValid(tup))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "cache lookup failed for property graph element %u", object->objectId);
+                                       break;
+                               }
+                               pge = (Form_pg_propgraph_element) GETSTRUCT(tup);
+                               appendStringInfo(&buffer, "%s of ", quote_identifier(NameStr(pge->pgealias)));
+
+                               getRelationIdentity(&buffer, pge->pgepgid, objname, false);
+                               if (objname)
+                                       *objname = lappend(*objname, pstrdup(NameStr(pge->pgealias)));
+
+                               ReleaseSysCache(tup);
+                               break;
+                       }
+
+               case PropgraphLabelRelationId:
+                       {
+                               HeapTuple       tup;
+                               Form_pg_propgraph_label pgl;
+
+                               tup = SearchSysCache1(PROPGRAPHLABELOID, object->objectId);
+                               if (!HeapTupleIsValid(tup))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "cache lookup failed for property graph label %u", object->objectId);
+                                       break;
+                               }
+
+                               pgl = (Form_pg_propgraph_label) GETSTRUCT(tup);
+                               appendStringInfo(&buffer, "%s of ", quote_identifier(NameStr(pgl->pgllabel)));
+                               getRelationIdentity(&buffer, pgl->pglpgid, objname, false);
+                               if (objname)
+                                       *objname = lappend(*objname, pstrdup(NameStr(pgl->pgllabel)));
+                               ReleaseSysCache(tup);
+                               break;
+                       }
+
+               case PropgraphPropertyRelationId:
+                       {
+                               HeapTuple       tup;
+                               Form_pg_propgraph_property pgp;
+
+                               tup = SearchSysCache1(PROPGRAPHPROPOID, object->objectId);
+                               if (!HeapTupleIsValid(tup))
+                               {
+                                       if (!missing_ok)
+                                               elog(ERROR, "cache lookup failed for property graph property %u", object->objectId);
+                                       break;
+                               }
+
+                               pgp = (Form_pg_propgraph_property) GETSTRUCT(tup);
+                               appendStringInfo(&buffer, "%s of ", quote_identifier(NameStr(pgp->pgpname)));
+                               getRelationIdentity(&buffer, pgp->pgppgid, objname, false);
+                               if (objname)
+                                       *objname = lappend(*objname, pstrdup(NameStr(pgp->pgpname)));
+                               ReleaseSysCache(tup);
+                               break;
+                       }
+
                case PublicationRelationId:
                        {
                                char       *pubname;
@@ -6201,6 +6534,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:
index 38cf89f09fa16192592cc481dcde57deace60c88..0a927ac46f2cdf8646e5034dd38b6c92af90604e 100644 (file)
@@ -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;
index 3a8ad201607f649a2429ce4d9b2635e2f1bcaebc..626054cbcefd8b49db3824556d66a62124b5ea89 100644 (file)
@@ -348,6 +348,106 @@ F866      FETCH FIRST clause: PERCENT option                      NO
 F867   FETCH FIRST clause: WITH TIES option                    YES     
 F868   ORDER BY in grouped table                       YES     
 F869   SQL implementation info population                      YES     
+G000   Graph pattern                   YES     SQL/PGQ required
+G001   Repeatable-elements match mode                  YES     SQL/PGQ required
+G002   Different-edges match mode                      NO      
+G003   Explicit REPEATABLE ELEMENTS keyword                    NO      
+G004   Path variables                  NO      
+G005   Path search prefix in a path pattern                    NO      
+G006   Graph pattern KEEP clause: path mode prefix                     NO      
+G007   Graph pattern KEEP clause: path search prefix                   NO      
+G008   Graph pattern WHERE clause                      YES     SQL/PGQ required
+G010   Explicit WALK keyword                   NO      
+G011   Advanced path modes: TRAIL                      NO      
+G012   Advanced path modes: SIMPLE                     NO      
+G013   Advanced path modes: ACYCLIC                    NO      
+G014   Explicit PATH/PATHS keywords                    NO      
+G015   All path search: explicit ALL keyword                   NO      
+G016   Any path search                 NO      
+G017   All shortest path search                        NO      
+G018   Any shortest path search                        NO      
+G019   Counted shortest path search                    NO      
+G020   Counted shortest group search                   NO      
+G030   Path multiset alternation                       NO      
+G031   Path multiset alternation: variable length path operands                        NO      
+G032   Path pattern union                      NO      
+G033   Path pattern union: variable length path operands                       NO      
+G034   Path concatenation                      YES     SQL/PGQ required
+G035   Quantified paths                        NO      
+G036   Quantified edges                        NO      
+G037   Questioned paths                        NO      
+G038   Parenthesized path pattern expression                   NO      
+G039   Simplified path pattern expression: full defaulting                     NO      
+G040   Vertex pattern                  YES     SQL/PGQ required
+G041   Non-local element pattern predicates                    NO      
+G042   Basic full edge patterns                        YES     SQL/PGQ required
+G043   Complete full edge patterns                     NO      
+G044   Basic abbreviated edge patterns                 YES     
+G045   Complete abbreviated edge patterns                      NO      
+G046   Relaxed topological consistency: adjacent vertex patterns                       NO      
+G047   Relaxed topological consistency: concise edge patterns                  NO      
+G048   Parenthesized path pattern: subpath variable declaration                        NO      
+G049   Parenthesized path pattern: path mode prefix                    NO      
+G050   Parenthesized path pattern: WHERE clause                        NO      
+G051   Parenthesized path pattern: non-local predicates                        NO      
+G060   Bounded graph pattern quantifiers                       NO      
+G061   Unbounded graph pattern quantifiers                     NO      
+G070   Label expression: label disjunction                     YES     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                  NO      
+G840   Path-ordered aggregates                 NO      
+G850   SQL/PGQ Information Schema views                        YES     
+G860   GET DIAGNOSTICS enhancements for SQL-property graphs                    NO      
+G900   GRAPH_TABLE                     YES     SQL/PGQ required
+G901   GRAPH_TABLE: ONE ROW PER VERTEX                 NO      
+G902   GRAPH_TABLE: ONE ROW PER STEP                   NO      
+G903   GRAPH_TABLE: explicit ONE ROW PER MATCH keywords                        NO      
+G904   All properties reference                        NO      
+G905   GRAPH_TABLE: optional COLUMNS clause                    NO      
+G906   GRAPH_TABLE: explicit EXPORT ALL                        NO      
+G907   GRAPH_TABLE: EXPORT ALL EXCEPT                  NO      
+G908   GRAPH_TABLE: EXPORT SINGLETONS list                     NO      
+G909   GRAPH_TABLE: explicit EXPORT NO SINGLETONS                      NO      
+G910   GRAPH_TABLE: 'in paths clause'                  NO      
+G920   DDL-based SQL-property graphs                   YES     SQL/PGQ required
+G921   Empty SQL-property graph                        YES     
+G922   Views as element tables                 YES     
+G923   In-line views as element tables                 NO      
+G924   Explicit key clause for element tables                  YES     SQL/PGQ required
+G925   Explicit label and properties clause for element tables                 YES     SQL/PGQ required
+G926   More than one label for vertex tables                   YES     
+G927   More than one label for edge tables                     YES     
+G928   Value expressions as properties and renaming of properties                      YES     
+G929   Labels and properties: EXCEPT list                      NO      
+G940   Multi-sourced/destined edges                    YES     
+G941   Implicit removal of incomplete edges                    YES     
+G950   Alter property graph statement: ADD/DROP element table                  YES     
+G960   Alter element table definition: ADD/DROP LABEL                  YES     
+G970   Alter element table definition: ALTER LABEL                     YES     
+G980   DROP PROPERTY GRAPH: CASCADE drop behavior                      YES     
 R010   Row pattern recognition: FROM clause                    NO      
 R020   Row pattern recognition: WINDOW clause                  NO      
 R030   Row pattern recognition: full aggregate support                 NO      
index 64cb6278409ff14e3c7b0bfb9ffcb2ef5962c9c3..c10fdba2bbb019f66a4cbb2f3ab22e263d212a2b 100644 (file)
@@ -49,6 +49,7 @@ OBJS = \
        portalcmds.o \
        prepare.o \
        proclang.o \
+       propgraphcmds.o \
        publicationcmds.o \
        schemacmds.o \
        seclabel.o \
index c6f58d47be62b39b572618fce4e25d98fc9a585f..74ceb5fe20d4ac6fa0beca1a54e13de315f4acff 100644 (file)
@@ -390,6 +390,7 @@ ExecRenameStmt(RenameStmt *stmt)
                case OBJECT_MATVIEW:
                case OBJECT_INDEX:
                case OBJECT_FOREIGN_TABLE:
+               case OBJECT_PROPGRAPH:
                        return RenameRelation(stmt);
 
                case OBJECT_COLUMN:
@@ -543,6 +544,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
                case OBJECT_TABLE:
                case OBJECT_VIEW:
                case OBJECT_MATVIEW:
+               case OBJECT_PROPGRAPH:
                        address = AlterTableNamespace(stmt,
                                                                                  oldSchemaAddr ? &oldNspOid : NULL);
                        break;
@@ -876,6 +878,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:
@@ -884,11 +887,26 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
                        {
                                ObjectAddress address;
 
-                               address = get_object_address(stmt->objectType,
-                                                                                        stmt->object,
-                                                                                        NULL,
-                                                                                        AccessExclusiveLock,
-                                                                                        false);
+                               if (stmt->relation)
+                               {
+                                       Relation        relation;
+
+                                       address = get_object_address_rv(stmt->objectType,
+                                                                                                       stmt->relation,
+                                                                                                       NIL,
+                                                                                                       &relation,
+                                                                                                       AccessExclusiveLock,
+                                                                                                       false);
+                                       relation_close(relation, NoLock);
+                               }
+                               else
+                               {
+                                       address = get_object_address(stmt->objectType,
+                                                                                                stmt->object,
+                                                                                                NULL,
+                                                                                                AccessExclusiveLock,
+                                                                                                false);
+                               }
 
                                AlterObjectOwner_internal(address.classId, address.objectId,
                                                                                  newowner);
index 92526012d2a935982554e872a2c42573f44ea075..88a2df65c6993ba6c601463a99d28ba0f54668b2 100644 (file)
@@ -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:
index 2898967fa6736a2a816bc0ef7da2e7c5672db835..ff1323c7b821b246093dc72c703fce38170468d3 100644 (file)
@@ -2305,6 +2305,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:
@@ -2389,6 +2390,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:
index ca3f53c62135f200fcceeaa82ce39a91beb8fe0d..90c7e37a42991af191791af87e1216d5da1c26fc 100644 (file)
@@ -37,6 +37,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 (file)
index 0000000..45d2ff1
--- /dev/null
@@ -0,0 +1,1882 @@
+/*-------------------------------------------------------------------------
+ *
+ * propgraphcmds.c
+ *       property graph manipulation
+ *
+ * Portions Copyright (c) 1996-2026, 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/nbtree.h"
+#include "access/table.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_collation_d.h"
+#include "catalog/pg_operator_d.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
+#include "commands/defrem.h"
+#include "commands/propgraphcmds.h"
+#include "commands/tablecmds.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_oper.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_target.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+struct element_info
+{
+       Oid                     elementid;
+       char            kind;
+       Oid                     relid;
+       char       *aliasname;
+       ArrayType  *key;
+
+       char       *srcvertex;
+       Oid                     srcvertexid;
+       Oid                     srcrelid;
+       ArrayType  *srckey;
+       ArrayType  *srcref;
+       ArrayType  *srceqop;
+
+       char       *destvertex;
+       Oid                     destvertexid;
+       Oid                     destrelid;
+       ArrayType  *destkey;
+       ArrayType  *destref;
+       ArrayType  *desteqop;
+
+       List       *labels;
+};
+
+
+static ArrayType *propgraph_element_get_key(ParseState *pstate, const List *keycols, Relation element_rel,
+                                                                                       const char *aliasname, int location);
+static void propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
+                                                                               Relation edge_rel, Relation ref_rel,
+                                                                               const char *aliasname, int location, const char *type,
+                                                                               ArrayType **outkey, ArrayType **outref, ArrayType **outeqop);
+static AttrNumber *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel);
+static ArrayType *array_from_attnums(int numattrs, const AttrNumber *attnums);
+static Oid     insert_element_record(ObjectAddress pgaddress, struct element_info *einfo);
+static Oid     insert_label_record(Oid graphid, Oid peoid, const char *label);
+static void insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties);
+static void insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr);
+static void check_element_properties(Oid peoid);
+static void check_element_label_properties(Oid ellabeloid);
+static void check_all_labels_properties(Oid pgrelid);
+static Oid     get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location);
+static Oid     get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location);
+static Oid     get_element_relid(Oid peid);
+static List *get_graph_label_ids(Oid graphid);
+static List *get_label_element_label_ids(Oid labelid);
+static List *get_element_label_property_names(Oid ellabeloid);
+static List *get_graph_property_ids(Oid graphid);
+
+
+/*
+ * CREATE PROPERTY GRAPH
+ */
+ObjectAddress
+CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt)
+{
+       CreateStmt *cstmt = makeNode(CreateStmt);
+       char            components_persistence;
+       ListCell   *lc;
+       ObjectAddress pgaddress;
+       List       *vertex_infos = NIL;
+       List       *edge_infos = NIL;
+       List       *element_aliases = NIL;
+       List       *element_oids = NIL;
+
+       if (stmt->pgname->relpersistence == RELPERSISTENCE_UNLOGGED)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("property graphs cannot be unlogged because they do not have storage")));
+
+       components_persistence = RELPERSISTENCE_PERMANENT;
+
+       foreach(lc, stmt->vertex_tables)
+       {
+               PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc);
+               struct element_info *vinfo;
+               Relation        rel;
+
+               vinfo = palloc0_object(struct element_info);
+               vinfo->kind = PGEKIND_VERTEX;
+
+               vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+               rel = table_open(vinfo->relid, NoLock);
+
+               if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+                       components_persistence = RELPERSISTENCE_TEMP;
+
+               if (vertex->vtable->alias)
+                       vinfo->aliasname = vertex->vtable->alias->aliasname;
+               else
+                       vinfo->aliasname = vertex->vtable->relname;
+
+               if (list_member(element_aliases, makeString(vinfo->aliasname)))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_TABLE),
+                                        errmsg("alias \"%s\" used more than once as element table", vinfo->aliasname),
+                                        parser_errposition(pstate, vertex->location)));
+
+               vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location);
+
+               vinfo->labels = vertex->labels;
+
+               table_close(rel, NoLock);
+
+               vertex_infos = lappend(vertex_infos, vinfo);
+
+               element_aliases = lappend(element_aliases, makeString(vinfo->aliasname));
+       }
+
+       foreach(lc, stmt->edge_tables)
+       {
+               PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc);
+               struct element_info *einfo;
+               Relation        rel;
+               ListCell   *lc2;
+               Oid                     srcrelid;
+               Oid                     destrelid;
+               Relation        srcrel;
+               Relation        destrel;
+
+               einfo = palloc0_object(struct element_info);
+               einfo->kind = PGEKIND_EDGE;
+
+               einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+               rel = table_open(einfo->relid, NoLock);
+
+               if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+                       components_persistence = RELPERSISTENCE_TEMP;
+
+               if (edge->etable->alias)
+                       einfo->aliasname = edge->etable->alias->aliasname;
+               else
+                       einfo->aliasname = edge->etable->relname;
+
+               if (list_member(element_aliases, makeString(einfo->aliasname)))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_TABLE),
+                                        errmsg("alias \"%s\" used more than once as element table", einfo->aliasname),
+                                        parser_errposition(pstate, edge->location)));
+
+               einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location);
+
+               einfo->srcvertex = edge->esrcvertex;
+               einfo->destvertex = edge->edestvertex;
+
+               srcrelid = 0;
+               destrelid = 0;
+               foreach(lc2, vertex_infos)
+               {
+                       struct element_info *vinfo = lfirst(lc2);
+
+                       if (strcmp(vinfo->aliasname, edge->esrcvertex) == 0)
+                               srcrelid = vinfo->relid;
+
+                       if (strcmp(vinfo->aliasname, edge->edestvertex) == 0)
+                               destrelid = vinfo->relid;
+
+                       if (srcrelid && destrelid)
+                               break;
+               }
+               if (!srcrelid)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("source vertex \"%s\" of edge \"%s\" does not exist",
+                                                       edge->esrcvertex, einfo->aliasname),
+                                        parser_errposition(pstate, edge->location)));
+               if (!destrelid)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("destination vertex \"%s\" of edge \"%s\" does not exist",
+                                                       edge->edestvertex, einfo->aliasname),
+                                        parser_errposition(pstate, edge->location)));
+
+               srcrel = table_open(srcrelid, NoLock);
+               destrel = table_open(destrelid, NoLock);
+
+               propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
+                                                                       einfo->aliasname, edge->location, "SOURCE",
+                                                                       &einfo->srckey, &einfo->srcref, &einfo->srceqop);
+               propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
+                                                                       einfo->aliasname, edge->location, "DESTINATION",
+                                                                       &einfo->destkey, &einfo->destref, &einfo->desteqop);
+
+               einfo->labels = edge->labels;
+
+               table_close(destrel, NoLock);
+               table_close(srcrel, NoLock);
+
+               table_close(rel, NoLock);
+
+               edge_infos = lappend(edge_infos, einfo);
+
+               element_aliases = lappend(element_aliases, makeString(einfo->aliasname));
+       }
+
+       cstmt->relation = stmt->pgname;
+       cstmt->oncommit = ONCOMMIT_NOOP;
+
+       /*
+        * Automatically make it temporary if any component tables are temporary
+        * (see also DefineView()).
+        */
+       if (stmt->pgname->relpersistence == RELPERSISTENCE_PERMANENT
+               && components_persistence == RELPERSISTENCE_TEMP)
+       {
+               cstmt->relation = copyObject(cstmt->relation);
+               cstmt->relation->relpersistence = RELPERSISTENCE_TEMP;
+               ereport(NOTICE,
+                               (errmsg("property graph \"%s\" will be temporary",
+                                               stmt->pgname->relname)));
+       }
+
+       pgaddress = DefineRelation(cstmt, RELKIND_PROPGRAPH, InvalidOid, NULL, NULL);
+
+       foreach(lc, vertex_infos)
+       {
+               struct element_info *vinfo = lfirst(lc);
+               Oid                     peoid;
+
+               peoid = insert_element_record(pgaddress, vinfo);
+               element_oids = lappend_oid(element_oids, peoid);
+       }
+
+       foreach(lc, edge_infos)
+       {
+               struct element_info *einfo = lfirst(lc);
+               Oid                     peoid;
+               ListCell   *lc2;
+
+               /*
+                * Look up the vertices again.  Now the vertices have OIDs assigned,
+                * which we need.
+                */
+               foreach(lc2, vertex_infos)
+               {
+                       struct element_info *vinfo = lfirst(lc2);
+
+                       if (strcmp(vinfo->aliasname, einfo->srcvertex) == 0)
+                       {
+                               einfo->srcvertexid = vinfo->elementid;
+                               einfo->srcrelid = vinfo->relid;
+                       }
+                       if (strcmp(vinfo->aliasname, einfo->destvertex) == 0)
+                       {
+                               einfo->destvertexid = vinfo->elementid;
+                               einfo->destrelid = vinfo->relid;
+                       }
+                       if (einfo->srcvertexid && einfo->destvertexid)
+                               break;
+               }
+               Assert(einfo->srcvertexid);
+               Assert(einfo->destvertexid);
+               Assert(einfo->srcrelid);
+               Assert(einfo->destrelid);
+               peoid = insert_element_record(pgaddress, einfo);
+               element_oids = lappend_oid(element_oids, peoid);
+       }
+
+       CommandCounterIncrement();
+
+       foreach_oid(peoid, element_oids)
+               check_element_properties(peoid);
+       check_all_labels_properties(pgaddress.objectId);
+
+       return pgaddress;
+}
+
+/*
+ * Process the key clause specified for an element.  If key_clause is non-NIL,
+ * then it is a list of column names.  Otherwise, the primary key of the
+ * relation is used.  The return value is an array of column numbers.
+ */
+static ArrayType *
+propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation element_rel, const char *aliasname, int location)
+{
+       ArrayType  *a;
+
+       if (key_clause == NIL)
+       {
+               Oid                     pkidx = RelationGetPrimaryKeyIndex(element_rel, false);
+
+               if (!pkidx)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                       errmsg("no key specified and no suitable primary key exists for definition of element \"%s\"", aliasname),
+                                       parser_errposition(pstate, location));
+               else
+               {
+                       Relation        indexDesc;
+
+                       indexDesc = index_open(pkidx, AccessShareLock);
+                       a = array_from_attnums(indexDesc->rd_index->indkey.dim1, indexDesc->rd_index->indkey.values);
+                       index_close(indexDesc, NoLock);
+               }
+       }
+       else
+       {
+               a = array_from_attnums(list_length(key_clause),
+                                                          array_from_column_list(pstate, key_clause, location, element_rel));
+       }
+
+       return a;
+}
+
+/*
+ * Process the source or destination link of an edge.
+ *
+ * keycols and refcols are column names representing the local and referenced
+ * (vertex) columns.  If they are both NIL, a matching foreign key is looked
+ * up.
+ *
+ * edge_rel and ref_rel are the local and referenced element tables.
+ *
+ * aliasname, location, and type are for error messages.  type is either
+ * "SOURCE" or "DESTINATION".
+ *
+ * The outputs are arrays of column numbers in outkey and outref.
+ */
+static void
+propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols,
+                                                       Relation edge_rel, Relation ref_rel,
+                                                       const char *aliasname, int location, const char *type,
+                                                       ArrayType **outkey, ArrayType **outref, ArrayType **outeqop)
+{
+       int                     nkeys;
+       AttrNumber *keyattnums;
+       AttrNumber *refattnums;
+       Oid                *keyeqops;
+       Datum      *datums;
+
+       Assert((keycols && refcols) || (!keycols && !refcols));
+
+       if (keycols)
+       {
+               if (list_length(keycols) != list_length(refcols))
+                       ereport(ERROR,
+                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                       errmsg("mismatching number of columns in %s vertex definition of edge \"%s\"", type, aliasname),
+                                       parser_errposition(pstate, location));
+
+               nkeys = list_length(keycols);
+               keyattnums = array_from_column_list(pstate, keycols, location, edge_rel);
+               refattnums = array_from_column_list(pstate, refcols, location, ref_rel);
+               keyeqops = palloc_array(Oid, nkeys);
+
+               for (int i = 0; i < nkeys; i++)
+               {
+                       Oid                     keytype;
+                       int32           keytypmod;
+                       Oid                     keycoll;
+                       Oid                     reftype;
+                       int32           reftypmod;
+                       Oid                     refcoll;
+                       Oid                     opc;
+                       Oid                     opf;
+                       StrategyNumber strategy;
+
+                       /*
+                        * Lookup equality operator to be used for edge and vertex key.
+                        * Vertex key is equivalent to primary key and edge key is similar
+                        * to foreign key since edge key references vertex key. Hence
+                        * vertex key is used as left operand and edge key is used as
+                        * right operand. The method used to find the equality operators
+                        * is similar to the method used to find equality operators for
+                        * FK/PK comparison in ATAddForeignKeyConstraint() except that
+                        * opclass of the the vertex key type is used as a starting point.
+                        * Since we need only equality operators we use both BT and HASH
+                        * strategies.
+                        *
+                        * If the required operators do not exist, we can not construct
+                        * quals linking an edge to its adjacent vertexes.
+                        */
+                       get_atttypetypmodcoll(RelationGetRelid(edge_rel), keyattnums[i], &keytype, &keytypmod, &keycoll);
+                       get_atttypetypmodcoll(RelationGetRelid(ref_rel), refattnums[i], &reftype, &reftypmod, &refcoll);
+                       keyeqops[i] = InvalidOid;
+                       strategy = BTEqualStrategyNumber;
+                       opc = GetDefaultOpClass(reftype, BTREE_AM_OID);
+                       if (!OidIsValid(opc))
+                       {
+                               opc = GetDefaultOpClass(reftype, HASH_AM_OID);
+                               strategy = HTEqualStrategyNumber;
+                       }
+                       if (OidIsValid(opc))
+                       {
+                               opf = get_opclass_family(opc);
+                               if (OidIsValid(opf))
+                               {
+                                       keyeqops[i] = get_opfamily_member(opf, reftype, keytype, strategy);
+                                       if (!OidIsValid(keyeqops[i]))
+                                       {
+                                               /* Last resort, implicit cast. */
+                                               if (can_coerce_type(1, &keytype, &reftype, COERCION_IMPLICIT))
+                                                       keyeqops[i] = get_opfamily_member(opf, reftype, reftype, strategy);
+                                       }
+                               }
+                       }
+
+                       if (!OidIsValid(keyeqops[i]))
+                               ereport(ERROR,
+                                               errcode(ERRCODE_SYNTAX_ERROR),
+                                               errmsg("no equality operator exists for %s key comparison of edge \"%s\"",
+                                                          type, aliasname),
+                                               parser_errposition(pstate, location));
+
+                       /*
+                        * If collations of key attribute and referenced attribute are
+                        * different, an edge may end up being adjacent to undesired
+                        * vertexes.  Prohibit such a case.
+                        *
+                        * PK/FK allows different collations as long as they are
+                        * deterministic for backward compatibility. But we can be a bit
+                        * stricter here and follow SQL standard.
+                        */
+                       if (keycoll != refcoll &&
+                               keycoll != DEFAULT_COLLATION_OID && refcoll != DEFAULT_COLLATION_OID &&
+                               OidIsValid(keycoll) && OidIsValid(refcoll))
+                               ereport(ERROR,
+                                               errcode(ERRCODE_SYNTAX_ERROR),
+                                               errmsg("collation mismatch in %s key of edge \"%s\": %s vs. %s",
+                                                          type, aliasname,
+                                                          get_collation_name(keycoll), get_collation_name(refcoll)),
+                                               parser_errposition(pstate, location));
+               }
+       }
+       else
+       {
+               ForeignKeyCacheInfo *fk = NULL;
+
+               foreach_node(ForeignKeyCacheInfo, tmp, RelationGetFKeyList(edge_rel))
+               {
+                       if (tmp->confrelid == RelationGetRelid(ref_rel))
+                       {
+                               if (fk)
+                                       ereport(ERROR,
+                                                       errcode(ERRCODE_SYNTAX_ERROR),
+                                                       errmsg("more than one suitable foreign key exists for %s key of edge \"%s\"", type, aliasname),
+                                                       parser_errposition(pstate, location));
+                               fk = tmp;
+                       }
+               }
+
+               if (!fk)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_SYNTAX_ERROR),
+                                       errmsg("no %s key specified and no suitable foreign key exists for definition of edge \"%s\"", type, aliasname),
+                                       parser_errposition(pstate, location));
+
+               nkeys = fk->nkeys;
+               keyattnums = fk->conkey;
+               refattnums = fk->confkey;
+               keyeqops = fk->conpfeqop;
+       }
+
+       *outkey = array_from_attnums(nkeys, keyattnums);
+       *outref = array_from_attnums(nkeys, refattnums);
+       datums = palloc_array(Datum, nkeys);
+       for (int i = 0; i < nkeys; i++)
+               datums[i] = ObjectIdGetDatum(keyeqops[i]);
+       *outeqop = construct_array_builtin(datums, nkeys, OIDOID);
+}
+
+/*
+ * Convert list of column names in the specified relation into an array of
+ * column numbers.
+ */
+static AttrNumber *
+array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel)
+{
+       int                     numattrs;
+       AttrNumber *attnums;
+       int                     i;
+       ListCell   *lc;
+
+       numattrs = list_length(colnames);
+       attnums = palloc_array(AttrNumber, 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)));
+               attnums[i++] = attnum;
+       }
+
+       for (int j = 0; j < numattrs; j++)
+       {
+               for (int k = j + 1; k < numattrs; k++)
+               {
+                       if (attnums[j] == attnums[k])
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                                errmsg("graph key columns list must not contain duplicates"),
+                                                parser_errposition(pstate, location)));
+               }
+       }
+
+       return attnums;
+}
+
+static ArrayType *
+array_from_attnums(int numattrs, const AttrNumber *attnums)
+{
+       Datum      *attnumsd;
+
+       attnumsd = palloc_array(Datum, numattrs);
+
+       for (int i = 0; i < numattrs; i++)
+               attnumsd[i] = Int16GetDatum(attnums[i]);
+
+       return construct_array_builtin(attnumsd, numattrs, INT2OID);
+}
+
+static void
+array_of_attnums_to_objectaddrs(Oid relid, ArrayType *arr, ObjectAddresses *addrs)
+{
+       Datum      *attnumsd;
+       int                     numattrs;
+
+       deconstruct_array_builtin(arr, INT2OID, &attnumsd, NULL, &numattrs);
+
+       for (int i = 0; i < numattrs; i++)
+       {
+               ObjectAddress referenced;
+
+               ObjectAddressSubSet(referenced, RelationRelationId, relid, DatumGetInt16(attnumsd[i]));
+               add_exact_object_address(&referenced, addrs);
+       }
+}
+
+static void
+array_of_opers_to_objectaddrs(ArrayType *arr, ObjectAddresses *addrs)
+{
+       Datum      *opersd;
+       int                     numopers;
+
+       deconstruct_array_builtin(arr, OIDOID, &opersd, NULL, &numopers);
+
+       for (int i = 0; i < numopers; i++)
+       {
+               ObjectAddress referenced;
+
+               ObjectAddressSet(referenced, OperatorRelationId, DatumGetObjectId(opersd[i]));
+               add_exact_object_address(&referenced, addrs);
+       }
+}
+
+/*
+ * Insert a record for an element into the pg_propgraph_element catalog.  Also
+ * inserts labels and properties into their respective catalogs.
+ */
+static Oid
+insert_element_record(ObjectAddress pgaddress, struct element_info *einfo)
+{
+       Oid                     graphid = pgaddress.objectId;
+       Relation        rel;
+       NameData        aliasname;
+       Oid                     peoid;
+       Datum           values[Natts_pg_propgraph_element] = {0};
+       bool            nulls[Natts_pg_propgraph_element] = {0};
+       HeapTuple       tup;
+       ObjectAddress myself;
+       ObjectAddress referenced;
+       ObjectAddresses *addrs;
+
+       rel = table_open(PropgraphElementRelationId, RowExclusiveLock);
+
+       peoid = GetNewOidWithIndex(rel, PropgraphElementObjectIndexId, Anum_pg_propgraph_element_oid);
+       einfo->elementid = peoid;
+       values[Anum_pg_propgraph_element_oid - 1] = ObjectIdGetDatum(peoid);
+       values[Anum_pg_propgraph_element_pgepgid - 1] = ObjectIdGetDatum(graphid);
+       values[Anum_pg_propgraph_element_pgerelid - 1] = ObjectIdGetDatum(einfo->relid);
+       namestrcpy(&aliasname, einfo->aliasname);
+       values[Anum_pg_propgraph_element_pgealias - 1] = NameGetDatum(&aliasname);
+       values[Anum_pg_propgraph_element_pgekind - 1] = CharGetDatum(einfo->kind);
+       values[Anum_pg_propgraph_element_pgesrcvertexid - 1] = ObjectIdGetDatum(einfo->srcvertexid);
+       values[Anum_pg_propgraph_element_pgedestvertexid - 1] = ObjectIdGetDatum(einfo->destvertexid);
+       values[Anum_pg_propgraph_element_pgekey - 1] = PointerGetDatum(einfo->key);
+
+       if (einfo->srckey)
+               values[Anum_pg_propgraph_element_pgesrckey - 1] = PointerGetDatum(einfo->srckey);
+       else
+               nulls[Anum_pg_propgraph_element_pgesrckey - 1] = true;
+       if (einfo->srcref)
+               values[Anum_pg_propgraph_element_pgesrcref - 1] = PointerGetDatum(einfo->srcref);
+       else
+               nulls[Anum_pg_propgraph_element_pgesrcref - 1] = true;
+       if (einfo->srceqop)
+               values[Anum_pg_propgraph_element_pgesrceqop - 1] = PointerGetDatum(einfo->srceqop);
+       else
+               nulls[Anum_pg_propgraph_element_pgesrceqop - 1] = true;
+       if (einfo->destkey)
+               values[Anum_pg_propgraph_element_pgedestkey - 1] = PointerGetDatum(einfo->destkey);
+       else
+               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;
+       if (einfo->desteqop)
+               values[Anum_pg_propgraph_element_pgedesteqop - 1] = PointerGetDatum(einfo->desteqop);
+       else
+               nulls[Anum_pg_propgraph_element_pgedesteqop - 1] = true;
+
+       tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+       CatalogTupleInsert(rel, tup);
+       heap_freetuple(tup);
+
+       ObjectAddressSet(myself, PropgraphElementRelationId, peoid);
+
+       /* Add dependency on the property graph */
+       recordDependencyOn(&myself, &pgaddress, DEPENDENCY_AUTO);
+
+       addrs = new_object_addresses();
+
+       /* Add dependency on the relation */
+       ObjectAddressSet(referenced, RelationRelationId, einfo->relid);
+       add_exact_object_address(&referenced, addrs);
+       array_of_attnums_to_objectaddrs(einfo->relid, einfo->key, addrs);
+
+       /*
+        * Add dependencies on vertices and equality operators used for key
+        * comparison.
+        */
+       if (einfo->srcvertexid)
+       {
+               ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid);
+               add_exact_object_address(&referenced, addrs);
+               array_of_attnums_to_objectaddrs(einfo->relid, einfo->srckey, addrs);
+               array_of_attnums_to_objectaddrs(einfo->srcrelid, einfo->srcref, addrs);
+               array_of_opers_to_objectaddrs(einfo->srceqop, addrs);
+       }
+       if (einfo->destvertexid)
+       {
+               ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->destvertexid);
+               add_exact_object_address(&referenced, addrs);
+               array_of_attnums_to_objectaddrs(einfo->relid, einfo->destkey, addrs);
+               array_of_attnums_to_objectaddrs(einfo->destrelid, einfo->destref, addrs);
+               array_of_opers_to_objectaddrs(einfo->desteqop, addrs);
+       }
+
+       record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+
+       table_close(rel, NoLock);
+
+       if (einfo->labels)
+       {
+               ListCell   *lc;
+
+               foreach(lc, einfo->labels)
+               {
+                       PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+                       Oid                     ellabeloid;
+
+                       if (lp->label)
+                               ellabeloid = insert_label_record(graphid, peoid, lp->label);
+                       else
+                               ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+                       insert_property_records(graphid, ellabeloid, einfo->relid, lp->properties);
+
+                       CommandCounterIncrement();
+               }
+       }
+       else
+       {
+               Oid                     ellabeloid;
+               PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+               pr->all = true;
+               pr->location = -1;
+
+               ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname);
+               insert_property_records(graphid, ellabeloid, einfo->relid, pr);
+       }
+
+       return peoid;
+}
+
+/*
+ * Insert records for a label into the pg_propgraph_label and
+ * pg_propgraph_element_label catalogs, and register dependencies.
+ *
+ * Returns the OID of the new pg_propgraph_element_label record.
+ */
+static Oid
+insert_label_record(Oid graphid, Oid peoid, const char *label)
+{
+       Oid                     labeloid;
+       Oid                     ellabeloid;
+
+       /*
+        * Insert into pg_propgraph_label if not already existing.
+        */
+       labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(graphid), CStringGetDatum(label));
+       if (!labeloid)
+       {
+               Relation        rel;
+               Datum           values[Natts_pg_propgraph_label] = {0};
+               bool            nulls[Natts_pg_propgraph_label] = {0};
+               NameData        labelname;
+               HeapTuple       tup;
+               ObjectAddress myself;
+               ObjectAddress referenced;
+
+               rel = table_open(PropgraphLabelRelationId, RowExclusiveLock);
+
+               labeloid = GetNewOidWithIndex(rel, PropgraphLabelObjectIndexId, Anum_pg_propgraph_label_oid);
+               values[Anum_pg_propgraph_label_oid - 1] = ObjectIdGetDatum(labeloid);
+               values[Anum_pg_propgraph_label_pglpgid - 1] = ObjectIdGetDatum(graphid);
+               namestrcpy(&labelname, label);
+               values[Anum_pg_propgraph_label_pgllabel - 1] = NameGetDatum(&labelname);
+
+               tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+               CatalogTupleInsert(rel, tup);
+               heap_freetuple(tup);
+
+               ObjectAddressSet(myself, PropgraphLabelRelationId, labeloid);
+
+               ObjectAddressSet(referenced, RelationRelationId, graphid);
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+               table_close(rel, NoLock);
+       }
+
+       /*
+        * Insert into pg_propgraph_element_label
+        */
+       {
+               Relation        rel;
+               Datum           values[Natts_pg_propgraph_element_label] = {0};
+               bool            nulls[Natts_pg_propgraph_element_label] = {0};
+               HeapTuple       tup;
+               ObjectAddress myself;
+               ObjectAddress referenced;
+
+               rel = table_open(PropgraphElementLabelRelationId, RowExclusiveLock);
+
+               ellabeloid = GetNewOidWithIndex(rel, PropgraphElementLabelObjectIndexId, Anum_pg_propgraph_element_label_oid);
+               values[Anum_pg_propgraph_element_label_oid - 1] = ObjectIdGetDatum(ellabeloid);
+               values[Anum_pg_propgraph_element_label_pgellabelid - 1] = ObjectIdGetDatum(labeloid);
+               values[Anum_pg_propgraph_element_label_pgelelid - 1] = ObjectIdGetDatum(peoid);
+
+               tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+               CatalogTupleInsert(rel, tup);
+               heap_freetuple(tup);
+
+               ObjectAddressSet(myself, PropgraphElementLabelRelationId, ellabeloid);
+
+               ObjectAddressSet(referenced, PropgraphLabelRelationId, labeloid);
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+               ObjectAddressSet(referenced, PropgraphElementRelationId, peoid);
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+               table_close(rel, NoLock);
+       }
+
+       return ellabeloid;
+}
+
+/*
+ * Insert records for properties into the pg_propgraph_property catalog.
+ */
+static void
+insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties)
+{
+       List       *proplist = NIL;
+       ParseState *pstate;
+       ParseNamespaceItem *nsitem;
+       List       *tp;
+       Relation        rel;
+       ListCell   *lc;
+
+       if (properties->all)
+       {
+               Relation        attRelation;
+               SysScanDesc scan;
+               ScanKeyData key[1];
+               HeapTuple       attributeTuple;
+
+               attRelation = table_open(AttributeRelationId, RowShareLock);
+               ScanKeyInit(&key[0],
+                                       Anum_pg_attribute_attrelid,
+                                       BTEqualStrategyNumber, F_OIDEQ,
+                                       ObjectIdGetDatum(pgerelid));
+               scan = systable_beginscan(attRelation, AttributeRelidNumIndexId,
+                                                                 true, NULL, 1, key);
+               while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
+               {
+                       Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
+                       ColumnRef  *cr;
+                       ResTarget  *rt;
+
+                       if (att->attnum <= 0 || att->attisdropped)
+                               continue;
+
+                       cr = makeNode(ColumnRef);
+                       rt = makeNode(ResTarget);
+
+                       cr->fields = list_make1(makeString(pstrdup(NameStr(att->attname))));
+                       cr->location = -1;
+
+                       rt->name = pstrdup(NameStr(att->attname));
+                       rt->val = (Node *) cr;
+                       rt->location = -1;
+
+                       proplist = lappend(proplist, rt);
+               }
+               systable_endscan(scan);
+               table_close(attRelation, RowShareLock);
+       }
+       else
+       {
+               proplist = properties->properties;
+
+               foreach(lc, proplist)
+               {
+                       ResTarget  *rt = lfirst_node(ResTarget, lc);
+
+                       if (!rt->name && !IsA(rt->val, ColumnRef))
+                               ereport(ERROR,
+                                               errcode(ERRCODE_SYNTAX_ERROR),
+                                               errmsg("property name required"),
+                                               parser_errposition(NULL, rt->location));
+               }
+       }
+
+       rel = table_open(pgerelid, AccessShareLock);
+
+       pstate = make_parsestate(NULL);
+       nsitem = addRangeTableEntryForRelation(pstate,
+                                                                                  rel,
+                                                                                  AccessShareLock,
+                                                                                  NULL,
+                                                                                  false,
+                                                                                  true);
+       addNSItemToQuery(pstate, nsitem, true, true, true);
+
+       table_close(rel, NoLock);
+
+       tp = transformTargetList(pstate, proplist, EXPR_KIND_PROPGRAPH_PROPERTY);
+       assign_expr_collations(pstate, (Node *) tp);
+
+       foreach(lc, tp)
+       {
+               TargetEntry *te = lfirst_node(TargetEntry, lc);
+
+               insert_property_record(graphid, ellabeloid, pgerelid, te->resname, te->expr);
+       }
+}
+
+/*
+ * Insert records for a property into the pg_propgraph_property and
+ * pg_propgraph_label_property catalogs, and register dependencies.
+ */
+static void
+insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr)
+{
+       Oid                     propoid;
+       Oid                     exprtypid = exprType((const Node *) expr);
+       int32           exprtypmod = exprTypmod((const Node *) expr);
+       Oid                     exprcollation = exprCollation((const Node *) expr);
+
+       /*
+        * Insert into pg_propgraph_property if not already existing.
+        */
+       propoid = GetSysCacheOid2(PROPGRAPHPROPNAME, Anum_pg_propgraph_property_oid, ObjectIdGetDatum(graphid), CStringGetDatum(propname));
+       if (!OidIsValid(propoid))
+       {
+               Relation        rel;
+               NameData        propnamedata;
+               Datum           values[Natts_pg_propgraph_property] = {0};
+               bool            nulls[Natts_pg_propgraph_property] = {0};
+               HeapTuple       tup;
+               ObjectAddress myself;
+               ObjectAddress referenced;
+
+               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(exprtypid);
+               values[Anum_pg_propgraph_property_pgptypmod - 1] = Int32GetDatum(exprtypmod);
+               values[Anum_pg_propgraph_property_pgpcollation - 1] = ObjectIdGetDatum(exprcollation);
+
+               tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+               CatalogTupleInsert(rel, tup);
+               heap_freetuple(tup);
+
+               ObjectAddressSet(myself, PropgraphPropertyRelationId, propoid);
+
+               ObjectAddressSet(referenced, RelationRelationId, graphid);
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+               ObjectAddressSet(referenced, TypeRelationId, exprtypid);
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+               if (OidIsValid(exprcollation) && exprcollation != DEFAULT_COLLATION_OID)
+               {
+                       ObjectAddressSet(referenced, CollationRelationId, exprcollation);
+                       recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+               }
+
+               table_close(rel, NoLock);
+       }
+       else
+       {
+               HeapTuple       pgptup = SearchSysCache1(PROPGRAPHPROPOID, ObjectIdGetDatum(propoid));
+               Form_pg_propgraph_property pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
+               Oid                     proptypid = pgpform->pgptypid;
+               int32           proptypmod = pgpform->pgptypmod;
+               Oid                     propcollation = pgpform->pgpcollation;
+
+               ReleaseSysCache(pgptup);
+
+               /*
+                * Check that in the graph, all properties with the same name have the
+                * same type (independent of which label they are on).  (See SQL/PGQ
+                * subclause "Consistency check of a tabular property graph
+                * descriptor".)
+                */
+               if (proptypid != exprtypid || proptypmod != exprtypmod)
+               {
+                       ereport(ERROR,
+                                       errcode(ERRCODE_SYNTAX_ERROR),
+                                       errmsg("property \"%s\" data type mismatch: %s vs. %s",
+                                                  propname, format_type_with_typemod(proptypid, proptypmod), format_type_with_typemod(exprtypid, exprtypmod)),
+                                       errdetail("In a property graph, a property of the same name has to have the same data type in each label."));
+               }
+
+               /* Similarly for collation */
+               if (propcollation != exprcollation)
+               {
+                       ereport(ERROR,
+                                       errcode(ERRCODE_SYNTAX_ERROR),
+                                       errmsg("property \"%s\" collation mismatch: %s vs. %s",
+                                                  propname, get_collation_name(propcollation), get_collation_name(exprcollation)),
+                                       errdetail("In a property graph, a property of the same name has to have the same collation in each label."));
+               }
+       }
+
+       /*
+        * Insert into pg_propgraph_label_property
+        */
+       {
+               Relation        rel;
+               Datum           values[Natts_pg_propgraph_label_property] = {0};
+               bool            nulls[Natts_pg_propgraph_label_property] = {0};
+               Oid                     plpoid;
+               HeapTuple       tup;
+               ObjectAddress myself;
+               ObjectAddress referenced;
+
+               rel = table_open(PropgraphLabelPropertyRelationId, RowExclusiveLock);
+
+               plpoid = GetNewOidWithIndex(rel, PropgraphLabelPropertyObjectIndexId, Anum_pg_propgraph_label_property_oid);
+               values[Anum_pg_propgraph_label_property_oid - 1] = ObjectIdGetDatum(plpoid);
+               values[Anum_pg_propgraph_label_property_plppropid - 1] = ObjectIdGetDatum(propoid);
+               values[Anum_pg_propgraph_label_property_plpellabelid - 1] = ObjectIdGetDatum(ellabeloid);
+               values[Anum_pg_propgraph_label_property_plpexpr - 1] = CStringGetTextDatum(nodeToString(expr));
+
+               tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+               CatalogTupleInsert(rel, tup);
+               heap_freetuple(tup);
+
+               ObjectAddressSet(myself, PropgraphLabelPropertyRelationId, plpoid);
+
+               ObjectAddressSet(referenced, PropgraphPropertyRelationId, propoid);
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+               ObjectAddressSet(referenced, PropgraphElementLabelRelationId, ellabeloid);
+               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+               recordDependencyOnSingleRelExpr(&myself, (Node *) copyObject(expr), pgerelid, DEPENDENCY_NORMAL, DEPENDENCY_NORMAL, false);
+
+               table_close(rel, NoLock);
+       }
+}
+
+/*
+ * Check that for the given graph element, all properties with the same name
+ * have the same expression for each label.  (See SQL/PGQ subclause "Creation
+ * of an element table descriptor".)
+ *
+ * We check this after all the catalog records are already inserted.  This
+ * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER
+ * PROPERTY GRAPH.  We pass in the element OID so that ALTER PROPERTY GRAPH
+ * only has to check the element it has just operated on.  CREATE PROPERTY
+ * GROUP checks all elements it has created.
+ */
+static void
+check_element_properties(Oid peoid)
+{
+       Relation        rel1;
+       ScanKeyData key1[1];
+       SysScanDesc scan1;
+       HeapTuple       tuple1;
+       List       *propoids = NIL;
+       List       *propexprs = NIL;
+
+       rel1 = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+       ScanKeyInit(&key1[0],
+                               Anum_pg_propgraph_element_label_pgelelid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(peoid));
+
+       scan1 = systable_beginscan(rel1, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, key1);
+       while (HeapTupleIsValid(tuple1 = systable_getnext(scan1)))
+       {
+               Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple1);
+               Relation        rel2;
+               ScanKeyData key2[1];
+               SysScanDesc scan2;
+               HeapTuple       tuple2;
+
+               rel2 = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+               ScanKeyInit(&key2[0],
+                                       Anum_pg_propgraph_label_property_plpellabelid,
+                                       BTEqualStrategyNumber, F_OIDEQ,
+                                       ObjectIdGetDatum(ellabel->oid));
+
+               scan2 = systable_beginscan(rel2, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key2);
+               while (HeapTupleIsValid(tuple2 = systable_getnext(scan2)))
+               {
+                       Form_pg_propgraph_label_property lprop = (Form_pg_propgraph_label_property) GETSTRUCT(tuple2);
+                       Oid                     propoid;
+                       Datum           datum;
+                       bool            isnull;
+                       char       *propexpr;
+                       ListCell   *lc1,
+                                          *lc2;
+                       bool            found;
+
+                       propoid = lprop->plppropid;
+                       datum = heap_getattr(tuple2, Anum_pg_propgraph_label_property_plpexpr, RelationGetDescr(rel2), &isnull);
+                       Assert(!isnull);
+                       propexpr = TextDatumGetCString(datum);
+
+                       found = false;
+                       forboth(lc1, propoids, lc2, propexprs)
+                       {
+                               if (propoid == lfirst_oid(lc1))
+                               {
+                                       Node       *na,
+                                                          *nb;
+
+                                       na = stringToNode(propexpr);
+                                       nb = stringToNode(lfirst(lc2));
+
+                                       found = true;
+
+                                       if (!equal(na, nb))
+                                       {
+                                               HeapTuple       tuple3;
+                                               Form_pg_propgraph_element elform;
+                                               List       *dpcontext;
+                                               char       *dpa,
+                                                                  *dpb;
+
+                                               tuple3 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peoid));
+                                               if (!tuple3)
+                                                       elog(ERROR, "cache lookup failed for property graph element %u", peoid);
+                                               elform = (Form_pg_propgraph_element) GETSTRUCT(tuple3);
+                                               dpcontext = deparse_context_for(get_rel_name(elform->pgerelid), elform->pgerelid);
+
+                                               dpa = deparse_expression(na, dpcontext, false, false);
+                                               dpb = deparse_expression(nb, dpcontext, false, false);
+
+                                               /*
+                                                * show in sorted order to keep output independent of
+                                                * index order
+                                                */
+                                               if (strcmp(dpa, dpb) > 0)
+                                               {
+                                                       char       *tmp;
+
+                                                       tmp = dpa;
+                                                       dpa = dpb;
+                                                       dpb = tmp;
+                                               }
+
+                                               ereport(ERROR,
+                                                               errcode(ERRCODE_SYNTAX_ERROR),
+                                                               errmsg("element \"%s\" property \"%s\" expression mismatch: %s vs. %s",
+                                                                          NameStr(elform->pgealias), get_propgraph_property_name(propoid), dpa, dpb),
+                                                               errdetail("In a property graph element, a property of the same name has to have the same expression in each label."));
+
+                                               ReleaseSysCache(tuple3);
+                                       }
+
+                                       break;
+                               }
+                       }
+
+                       if (!found)
+                       {
+                               propoids = lappend_oid(propoids, propoid);
+                               propexprs = lappend(propexprs, propexpr);
+                       }
+               }
+               systable_endscan(scan2);
+               table_close(rel2, AccessShareLock);
+       }
+
+       systable_endscan(scan1);
+       table_close(rel1, AccessShareLock);
+}
+
+/*
+ * Check that for the given element label, all labels of the same name in the
+ * graph have the same number and names of properties (independent of which
+ * element they are on).  (See SQL/PGQ subclause "Consistency check of a
+ * tabular property graph descriptor".)
+ *
+ * We check this after all the catalog records are already inserted.  This
+ * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER
+ * PROPERTY GRAPH.  We pass in the element label OID so that some variants of
+ * ALTER PROPERTY GRAPH only have to check the element label it has just
+ * operated on.  CREATE PROPERTY GRAPH and other ALTER PROPERTY GRAPH variants
+ * check all labels.
+ */
+static void
+check_element_label_properties(Oid ellabeloid)
+{
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       tuple;
+       Oid                     labelid = InvalidOid;
+       Oid                     ref_ellabeloid = InvalidOid;
+       List       *myprops,
+                          *refprops;
+       List       *diff1,
+                          *diff2;
+
+       rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+
+       /*
+        * Get element label info
+        */
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_element_label_oid,
+                               BTEqualStrategyNumber,
+                               F_OIDEQ, ObjectIdGetDatum(ellabeloid));
+       scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key);
+       if (HeapTupleIsValid(tuple = systable_getnext(scan)))
+       {
+               Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+               labelid = ellabel->pgellabelid;
+       }
+       systable_endscan(scan);
+       if (!labelid)
+               elog(ERROR, "element label %u not found", ellabeloid);
+
+       /*
+        * Find a reference element label to fetch label properties.  The
+        * reference element label has to have the label OID as the one being
+        * checked but be distinct from the one being checked.
+        */
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_element_label_pgellabelid,
+                               BTEqualStrategyNumber,
+                               F_OIDEQ, ObjectIdGetDatum(labelid));
+       scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key);
+       while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+       {
+               Form_pg_propgraph_element_label otherellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple);
+
+               if (otherellabel->oid != ellabeloid)
+               {
+                       ref_ellabeloid = otherellabel->oid;
+                       break;
+               }
+       }
+       systable_endscan(scan);
+
+       table_close(rel, AccessShareLock);
+
+       /*
+        * If there is not previous definition of this label, then we are done.
+        */
+       if (!ref_ellabeloid)
+               return;
+
+       /*
+        * Now check number and names.
+        *
+        * XXX We could provide more detail in the error messages, but that would
+        * probably only be useful for some ALTER commands, because otherwise it's
+        * not really clear which label definition is the wrong one, and so you'd
+        * have to construct a rather verbose report to be of any use.  Let's keep
+        * it simple for now.
+        */
+
+       myprops = get_element_label_property_names(ellabeloid);
+       refprops = get_element_label_property_names(ref_ellabeloid);
+
+       if (list_length(refprops) != list_length(myprops))
+               ereport(ERROR,
+                               errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                               errmsg("mismatching number of properties in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+
+       diff1 = list_difference(myprops, refprops);
+       diff2 = list_difference(refprops, myprops);
+
+       if (diff1 || diff2)
+               ereport(ERROR,
+                               errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                               errmsg("mismatching properties names in definition of label \"%s\"", get_propgraph_label_name(labelid)));
+}
+
+/*
+ * As above, but check all labels of a graph.
+ */
+static void
+check_all_labels_properties(Oid pgrelid)
+{
+       foreach_oid(labeloid, get_graph_label_ids(pgrelid))
+       {
+               foreach_oid(ellabeloid, get_label_element_label_ids(labeloid))
+               {
+                       check_element_label_properties(ellabeloid);
+               }
+       }
+}
+
+/*
+ * ALTER PROPERTY GRAPH
+ */
+ObjectAddress
+AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt)
+{
+       Oid                     pgrelid;
+       ListCell   *lc;
+       ObjectAddress pgaddress;
+
+       pgrelid = RangeVarGetRelidExtended(stmt->pgname,
+                                                                          ShareRowExclusiveLock,
+                                                                          stmt->missing_ok ? RVR_MISSING_OK : 0,
+                                                                          RangeVarCallbackOwnsRelation,
+                                                                          NULL);
+       if (pgrelid == InvalidOid)
+       {
+               ereport(NOTICE,
+                               (errmsg("relation \"%s\" does not exist, skipping",
+                                               stmt->pgname->relname)));
+               return InvalidObjectAddress;
+       }
+
+       ObjectAddressSet(pgaddress, RelationRelationId, pgrelid);
+
+       foreach(lc, stmt->add_vertex_tables)
+       {
+               PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc);
+               struct element_info *vinfo;
+               Relation        rel;
+               Oid                     peoid;
+
+               vinfo = palloc0_object(struct element_info);
+               vinfo->kind = PGEKIND_VERTEX;
+
+               vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+               rel = table_open(vinfo->relid, NoLock);
+
+               if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("cannot add temporary element table to non-temporary property graph"),
+                                        errdetail("Table \"%s\" is a temporary table.", get_rel_name(vinfo->relid)),
+                                        parser_errposition(pstate, vertex->vtable->location)));
+
+               if (vertex->vtable->alias)
+                       vinfo->aliasname = vertex->vtable->alias->aliasname;
+               else
+                       vinfo->aliasname = vertex->vtable->relname;
+
+               vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location);
+
+               vinfo->labels = vertex->labels;
+
+               table_close(rel, NoLock);
+
+               if (SearchSysCacheExists2(PROPGRAPHELALIAS,
+                                                                 ObjectIdGetDatum(pgrelid),
+                                                                 CStringGetDatum(vinfo->aliasname)))
+                       ereport(ERROR,
+                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                       errmsg("alias \"%s\" already exists in property graph \"%s\"",
+                                                  vinfo->aliasname, stmt->pgname->relname),
+                                       parser_errposition(pstate, vertex->vtable->location));
+
+               peoid = insert_element_record(pgaddress, vinfo);
+
+               CommandCounterIncrement();
+               check_element_properties(peoid);
+               check_all_labels_properties(pgrelid);
+       }
+
+       foreach(lc, stmt->add_edge_tables)
+       {
+               PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc);
+               struct element_info *einfo;
+               Relation        rel;
+               Relation        srcrel;
+               Relation        destrel;
+               Oid                     peoid;
+
+               einfo = palloc0_object(struct element_info);
+               einfo->kind = PGEKIND_EDGE;
+
+               einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL);
+
+               rel = table_open(einfo->relid, NoLock);
+
+               if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("cannot add temporary element table to non-temporary property graph"),
+                                        errdetail("Table \"%s\" is a temporary table.", get_rel_name(einfo->relid)),
+                                        parser_errposition(pstate, edge->etable->location)));
+
+               if (edge->etable->alias)
+                       einfo->aliasname = edge->etable->alias->aliasname;
+               else
+                       einfo->aliasname = edge->etable->relname;
+
+               einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location);
+
+               einfo->srcvertexid = get_vertex_oid(pstate, pgrelid, edge->esrcvertex, edge->location);
+               einfo->destvertexid = get_vertex_oid(pstate, pgrelid, edge->edestvertex, edge->location);
+
+               einfo->srcrelid = get_element_relid(einfo->srcvertexid);
+               einfo->destrelid = get_element_relid(einfo->destvertexid);
+
+               srcrel = table_open(einfo->srcrelid, AccessShareLock);
+               destrel = table_open(einfo->destrelid, AccessShareLock);
+
+               propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel,
+                                                                       einfo->aliasname, edge->location, "SOURCE",
+                                                                       &einfo->srckey, &einfo->srcref, &einfo->srceqop);
+               propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel,
+                                                                       einfo->aliasname, edge->location, "DESTINATION",
+                                                                       &einfo->destkey, &einfo->destref, &einfo->desteqop);
+
+               einfo->labels = edge->labels;
+
+               table_close(destrel, NoLock);
+               table_close(srcrel, NoLock);
+
+               table_close(rel, NoLock);
+
+               if (SearchSysCacheExists2(PROPGRAPHELALIAS,
+                                                                 ObjectIdGetDatum(pgrelid),
+                                                                 CStringGetDatum(einfo->aliasname)))
+                       ereport(ERROR,
+                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                       errmsg("alias \"%s\" already exists in property graph \"%s\"",
+                                                  einfo->aliasname, stmt->pgname->relname),
+                                       parser_errposition(pstate, edge->etable->location));
+
+               peoid = insert_element_record(pgaddress, einfo);
+
+               CommandCounterIncrement();
+               check_element_properties(peoid);
+               check_all_labels_properties(pgrelid);
+       }
+
+       foreach(lc, stmt->drop_vertex_tables)
+       {
+               char       *alias = strVal(lfirst(lc));
+               Oid                     peoid;
+               ObjectAddress obj;
+
+               peoid = get_vertex_oid(pstate, pgrelid, alias, -1);
+               ObjectAddressSet(obj, PropgraphElementRelationId, peoid);
+               performDeletion(&obj, stmt->drop_behavior, 0);
+       }
+
+       foreach(lc, stmt->drop_edge_tables)
+       {
+               char       *alias = strVal(lfirst(lc));
+               Oid                     peoid;
+               ObjectAddress obj;
+
+               peoid = get_edge_oid(pstate, pgrelid, alias, -1);
+               ObjectAddressSet(obj, PropgraphElementRelationId, peoid);
+               performDeletion(&obj, stmt->drop_behavior, 0);
+       }
+
+       /* Remove any orphaned pg_propgraph_label entries */
+       if (stmt->drop_vertex_tables || stmt->drop_edge_tables)
+       {
+               foreach_oid(labeloid, get_graph_label_ids(pgrelid))
+               {
+                       if (!get_label_element_label_ids(labeloid))
+                       {
+                               ObjectAddress obj;
+
+                               ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid);
+                               performDeletion(&obj, stmt->drop_behavior, 0);
+                       }
+               }
+       }
+
+       foreach(lc, stmt->add_labels)
+       {
+               PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc);
+               Oid                     peoid;
+               Oid                     pgerelid;
+               Oid                     ellabeloid;
+
+               Assert(lp->label);
+
+               if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+                       peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+               else
+                       peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+               pgerelid = get_element_relid(peoid);
+
+               ellabeloid = insert_label_record(pgrelid, peoid, lp->label);
+               insert_property_records(pgrelid, ellabeloid, pgerelid, lp->properties);
+
+               CommandCounterIncrement();
+               check_element_properties(peoid);
+               check_element_label_properties(ellabeloid);
+       }
+
+       if (stmt->drop_label)
+       {
+               Oid                     peoid;
+               Oid                     labeloid;
+               Oid                     ellabeloid;
+               ObjectAddress obj;
+
+               if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+                       peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+               else
+                       peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+               labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+                                                                  Anum_pg_propgraph_label_oid,
+                                                                  ObjectIdGetDatum(pgrelid),
+                                                                  CStringGetDatum(stmt->drop_label));
+               if (!labeloid)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                       errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+                                                  get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
+                                       parser_errposition(pstate, -1));
+
+               ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+                                                                        Anum_pg_propgraph_element_label_oid,
+                                                                        ObjectIdGetDatum(peoid),
+                                                                        ObjectIdGetDatum(labeloid));
+
+               if (!ellabeloid)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                       errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+                                                  get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label),
+                                       parser_errposition(pstate, -1));
+
+               ObjectAddressSet(obj, PropgraphElementLabelRelationId, ellabeloid);
+               performDeletion(&obj, stmt->drop_behavior, 0);
+
+               /* Remove any orphaned pg_propgraph_label entries */
+               if (!get_label_element_label_ids(labeloid))
+               {
+                       ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid);
+                       performDeletion(&obj, stmt->drop_behavior, 0);
+               }
+       }
+
+       if (stmt->add_properties)
+       {
+               Oid                     peoid;
+               Oid                     pgerelid;
+               Oid                     labeloid;
+               Oid                     ellabeloid;
+
+               if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+                       peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+               else
+                       peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+               labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+                                                                  Anum_pg_propgraph_label_oid,
+                                                                  ObjectIdGetDatum(pgrelid),
+                                                                  CStringGetDatum(stmt->alter_label));
+               if (!labeloid)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                       errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+                                                  get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+                                       parser_errposition(pstate, -1));
+
+               ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+                                                                        Anum_pg_propgraph_element_label_oid,
+                                                                        ObjectIdGetDatum(peoid),
+                                                                        ObjectIdGetDatum(labeloid));
+               if (!ellabeloid)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                       errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+                                                  get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+                                       parser_errposition(pstate, -1));
+
+               pgerelid = get_element_relid(peoid);
+
+               insert_property_records(pgrelid, ellabeloid, pgerelid, stmt->add_properties);
+
+               CommandCounterIncrement();
+               check_element_properties(peoid);
+               check_element_label_properties(ellabeloid);
+       }
+
+       if (stmt->drop_properties)
+       {
+               Oid                     peoid;
+               Oid                     labeloid;
+               Oid                     ellabeloid;
+               ObjectAddress obj;
+
+               if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX)
+                       peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1);
+               else
+                       peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1);
+
+               labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME,
+                                                                  Anum_pg_propgraph_label_oid,
+                                                                  ObjectIdGetDatum(pgrelid),
+                                                                  CStringGetDatum(stmt->alter_label));
+               if (!labeloid)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                       errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+                                                  get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+                                       parser_errposition(pstate, -1));
+
+               ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+                                                                        Anum_pg_propgraph_element_label_oid,
+                                                                        ObjectIdGetDatum(peoid),
+                                                                        ObjectIdGetDatum(labeloid));
+
+               if (!ellabeloid)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                       errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"",
+                                                  get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label),
+                                       parser_errposition(pstate, -1));
+
+               foreach(lc, stmt->drop_properties)
+               {
+                       char       *propname = strVal(lfirst(lc));
+                       Oid                     propoid;
+                       Oid                     plpoid;
+
+                       propoid = GetSysCacheOid2(PROPGRAPHPROPNAME,
+                                                                         Anum_pg_propgraph_property_oid,
+                                                                         ObjectIdGetDatum(pgrelid),
+                                                                         CStringGetDatum(propname));
+                       if (!propoid)
+                               ereport(ERROR,
+                                               errcode(ERRCODE_UNDEFINED_OBJECT),
+                                               errmsg("property graph \"%s\" element \"%s\" label \"%s\" has no property \"%s\"",
+                                                          get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label, propname),
+                                               parser_errposition(pstate, -1));
+
+                       plpoid = GetSysCacheOid2(PROPGRAPHLABELPROP, Anum_pg_propgraph_label_property_oid, ObjectIdGetDatum(ellabeloid), ObjectIdGetDatum(propoid));
+
+                       ObjectAddressSet(obj, PropgraphLabelPropertyRelationId, plpoid);
+                       performDeletion(&obj, stmt->drop_behavior, 0);
+               }
+
+               check_element_label_properties(ellabeloid);
+       }
+
+       /* Remove any orphaned pg_propgraph_property entries */
+       if (stmt->drop_properties || stmt->drop_vertex_tables || stmt->drop_edge_tables)
+       {
+               foreach_oid(propoid, get_graph_property_ids(pgrelid))
+               {
+                       Relation        rel;
+                       SysScanDesc scan;
+                       ScanKeyData key[1];
+
+                       rel = table_open(PropgraphLabelPropertyRelationId, RowShareLock);
+                       ScanKeyInit(&key[0],
+                                               Anum_pg_propgraph_label_property_plppropid,
+                                               BTEqualStrategyNumber, F_OIDEQ,
+                                               ObjectIdGetDatum(propoid));
+                       /* XXX no suitable index */
+                       scan = systable_beginscan(rel, InvalidOid, true, NULL, 1, key);
+                       if (!systable_getnext(scan))
+                       {
+                               ObjectAddress obj;
+
+                               ObjectAddressSet(obj, PropgraphPropertyRelationId, propoid);
+                               performDeletion(&obj, stmt->drop_behavior, 0);
+                       }
+
+                       systable_endscan(scan);
+                       table_close(rel, RowShareLock);
+               }
+       }
+
+       /*
+        * Invalidate relcache entry of the property graph so that the queries in
+        * the cached plans referencing the property graph will be rewritten
+        * considering changes to the propert graph.
+        */
+       CacheInvalidateRelcacheByRelid(pgrelid);
+
+       return pgaddress;
+}
+
+/*
+ * Get OID of vertex from graph OID and element alias.  Element must be a
+ * vertex, otherwise error.
+ */
+static Oid
+get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location)
+{
+       HeapTuple       tuple;
+       Oid                     peoid;
+
+       tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias));
+       if (!tuple)
+               ereport(ERROR,
+                               errcode(ERRCODE_UNDEFINED_OBJECT),
+                               errmsg("property graph \"%s\" has no element with alias \"%s\"",
+                                          get_rel_name(pgrelid), alias),
+                               parser_errposition(pstate, location));
+
+       if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_VERTEX)
+               ereport(ERROR,
+                               errcode(ERRCODE_SYNTAX_ERROR),
+                               errmsg("element \"%s\" of property graph \"%s\" is not a vertex",
+                                          alias, get_rel_name(pgrelid)),
+                               parser_errposition(pstate, location));
+
+       peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid;
+
+       ReleaseSysCache(tuple);
+
+       return peoid;
+}
+
+/*
+ * Get OID of edge from graph OID and element alias.  Element must be an edge,
+ * otherwise error.
+ */
+static Oid
+get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location)
+{
+       HeapTuple       tuple;
+       Oid                     peoid;
+
+       tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias));
+       if (!tuple)
+               ereport(ERROR,
+                               errcode(ERRCODE_UNDEFINED_OBJECT),
+                               errmsg("property graph \"%s\" has no element with alias \"%s\"",
+                                          get_rel_name(pgrelid), alias),
+                               parser_errposition(pstate, location));
+
+       if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_EDGE)
+               ereport(ERROR,
+                               errcode(ERRCODE_SYNTAX_ERROR),
+                               errmsg("element \"%s\" of property graph \"%s\" is not an edge",
+                                          alias, get_rel_name(pgrelid)),
+                               parser_errposition(pstate, location));
+
+       peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid;
+
+       ReleaseSysCache(tuple);
+
+       return peoid;
+}
+
+/*
+ * Get the element table relation OID from the OID of the element.
+ */
+static Oid
+get_element_relid(Oid peid)
+{
+       HeapTuple       tuple;
+       Oid                     pgerelid;
+
+       tuple = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peid));
+       if (!tuple)
+               elog(ERROR, "cache lookup failed for property graph element %u", peid);
+
+       pgerelid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgerelid;
+
+       ReleaseSysCache(tuple);
+
+       return pgerelid;
+}
+
+/*
+ * Get a list of all label OIDs of a graph.
+ */
+static List *
+get_graph_label_ids(Oid graphid)
+{
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       tuple;
+       List       *result = NIL;
+
+       rel = table_open(PropgraphLabelRelationId, AccessShareLock);
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_label_pglpgid,
+                               BTEqualStrategyNumber,
+                               F_OIDEQ, ObjectIdGetDatum(graphid));
+       scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, true, NULL, 1, key);
+       while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+       {
+               result = lappend_oid(result, ((Form_pg_propgraph_label) GETSTRUCT(tuple))->oid);
+       }
+       systable_endscan(scan);
+       table_close(rel, AccessShareLock);
+
+       return result;
+}
+
+/*
+ * Get a list of all element label OIDs for a label.
+ */
+static List *
+get_label_element_label_ids(Oid labelid)
+{
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       tuple;
+       List       *result = NIL;
+
+       rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_element_label_pgellabelid,
+                               BTEqualStrategyNumber,
+                               F_OIDEQ, ObjectIdGetDatum(labelid));
+       scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key);
+       while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+       {
+               result = lappend_oid(result, ((Form_pg_propgraph_element_label) GETSTRUCT(tuple))->oid);
+       }
+       systable_endscan(scan);
+       table_close(rel, AccessShareLock);
+
+       return result;
+}
+
+/*
+ * Get the names of properties associated with the given element label OID.
+ *
+ * The result is a list of String nodes (so we can use list functions to
+ * detect differences).
+ */
+static List *
+get_element_label_property_names(Oid ellabeloid)
+{
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       tuple;
+       List       *result = NIL;
+
+       rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_label_property_plpellabelid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(ellabeloid));
+
+       scan = systable_beginscan(rel, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key);
+
+       while ((tuple = systable_getnext(scan)))
+       {
+               Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple);
+
+               result = lappend(result, makeString(get_propgraph_property_name(plpform->plppropid)));
+       }
+
+       systable_endscan(scan);
+       table_close(rel, AccessShareLock);
+
+       return result;
+}
+
+/*
+ * Get a list of all property OIDs of a graph.
+ */
+static List *
+get_graph_property_ids(Oid graphid)
+{
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       tuple;
+       List       *result = NIL;
+
+       rel = table_open(PropgraphPropertyRelationId, AccessShareLock);
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_property_pgppgid,
+                               BTEqualStrategyNumber,
+                               F_OIDEQ, ObjectIdGetDatum(graphid));
+       scan = systable_beginscan(rel, PropgraphPropertyNameIndexId, true, NULL, 1, key);
+       while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+       {
+               result = lappend_oid(result, ((Form_pg_propgraph_property) GETSTRUCT(tuple))->oid);
+       }
+       systable_endscan(scan);
+       table_close(rel, AccessShareLock);
+
+       return result;
+}
index 5b80396723c06f767861d2f8540422f83faa4f67..77542d04200affba26263ccc30506802d6d2d184 100644 (file)
@@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
                case OBJECT_OPFAMILY:
                case OBJECT_PARAMETER_ACL:
                case OBJECT_POLICY:
+               case OBJECT_PROPGRAPH:
                case OBJECT_PUBLICATION_NAMESPACE:
                case OBJECT_PUBLICATION_REL:
                case OBJECT_RULE:
index 991f3b85df734d30c15a98aa94b8e4b8794277a3..8118f7aa1effe588ef70510e47f067611998bdf4 100644 (file)
@@ -308,6 +308,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}
 };
 
@@ -1550,7 +1556,7 @@ DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
 /*
  * RemoveRelations
  *             Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
- *             DROP MATERIALIZED VIEW, DROP FOREIGN TABLE
+ *             DROP MATERIALIZED VIEW, DROP FOREIGN TABLE, DROP PROPERTY GRAPH
  */
 void
 RemoveRelations(DropStmt *drop)
@@ -1614,6 +1620,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);
@@ -4219,7 +4229,7 @@ RenameConstraint(RenameStmt *stmt)
 }
 
 /*
- * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
+ * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE/PROPERTY GRAPH
  * RENAME
  */
 ObjectAddress
@@ -16329,6 +16339,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:
@@ -19897,6 +19908,11 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
                                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                                 errmsg("\"%s\" is not a composite type", rv->relname)));
 
+       if (reltype == OBJECT_PROPGRAPH && relkind != RELKIND_PROPGRAPH)
+               ereport(ERROR,
+                               (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                errmsg("\"%s\" is not a property graph", rv->relname)));
+
        if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
                relkind != RELKIND_PARTITIONED_INDEX
                && !IsA(stmt, RenameStmt))
index 0b63548699371dbd835be3ffec7ecc1f64c8c873..4015aef1b1fd060c363cd79096f798700b91bb54 100644 (file)
@@ -599,11 +599,11 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos,
 
                        /*
                         * Only relation RTEs and subquery RTEs that were once relation
-                        * RTEs (views) have their perminfoindex set.
+                        * RTEs (views, property graphs) have their perminfoindex set.
                         */
                        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 */
@@ -1163,6 +1163,12 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
                                        break;
                        }
                        break;
+               case RELKIND_PROPGRAPH:
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                        errmsg("cannot change property graph \"%s\"",
+                                                       RelationGetRelationName(resultRel))));
+                       break;
                default:
                        ereport(ERROR,
                                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1227,6 +1233,13 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
                                                 errmsg("cannot lock rows in foreign table \"%s\"",
                                                                RelationGetRelationName(rel))));
                        break;
+               case RELKIND_PROPGRAPH:
+                       /* Should not get here; rewriter should have expanded the graph */
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                        errmsg_internal("cannot lock rows in property graph \"%s\"",
+                                                                        RelationGetRelationName(rel))));
+                       break;
                default:
                        ereport(ERROR,
                                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
index 199ed27995fc55fb6298fbe79ffbf1adf028ff36..6a850349cf750a54a870b3c365b07fc652b5564d 100644 (file)
@@ -284,6 +284,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 */
@@ -536,6 +539,8 @@ exprTypmod(const Node *expr)
                        return exprTypmod((Node *) ((const ReturningExpr *) expr)->retexpr);
                case T_PlaceHolderVar:
                        return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+               case T_GraphPropertyRef:
+                       return ((const GraphPropertyRef *) expr)->typmod;
                default:
                        break;
        }
@@ -1058,6 +1063,9 @@ exprCollation(const Node *expr)
                case T_PlaceHolderVar:
                        coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
                        break;
+               case T_GraphPropertyRef:
+                       coll = ((const GraphPropertyRef *) expr)->collation;
+                       break;
                default:
                        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
                        coll = InvalidOid;      /* keep compiler quiet */
@@ -2127,6 +2135,7 @@ expression_tree_walker_impl(Node *node,
                case T_RangeTblRef:
                case T_SortGroupClause:
                case T_CTESearchClause:
+               case T_GraphPropertyRef:
                case T_MergeSupportFunc:
                        /* primitive node types with no expression subnodes */
                        break;
@@ -2667,6 +2676,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));
@@ -2860,6 +2889,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:
@@ -3912,6 +3947,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:
@@ -4546,6 +4585,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;
@@ -4704,6 +4755,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));
index 40990143927e75e614b71b11de0cc3bcc35f9a4c..953c5797c5d649234b934eb85fefed65249aa237 100644 (file)
@@ -565,6 +565,15 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
                        /* we re-use these RELATION fields, too: */
                        WRITE_OID_FIELD(relid);
                        break;
+               case RTE_GRAPH_TABLE:
+                       WRITE_NODE_FIELD(graph_pattern);
+                       WRITE_NODE_FIELD(graph_table_columns);
+                       /* we re-use these RELATION fields, too: */
+                       WRITE_OID_FIELD(relid);
+                       WRITE_CHAR_FIELD(relkind);
+                       WRITE_INT_FIELD(rellockmode);
+                       WRITE_UINT_FIELD(perminfoindex);
+                       break;
                case RTE_RESULT:
                        /* no extra fields */
                        break;
index 12f2b95d87cb1017e2779961514701477634d90f..17da5543c52f1f8f0556dfa38ce2871ed5da9725 100644 (file)
@@ -304,6 +304,10 @@ print_rt(const List *rtable)
                                printf("%d\t%s\t[group]",
                                           i, rte->eref->aliasname);
                                break;
+                       case RTE_GRAPH_TABLE:
+                               printf("%d\t%s\t[graph table]",
+                                          i, rte->eref->aliasname);
+                               break;
                        default:
                                printf("%d\t%s\t[unknown rtekind]",
                                           i, rte->eref->aliasname);
index 981ab9c34ef191616b57314fef0a233953253089..b6b2ce6c792aa9e0790405b57c03dcd4e9644754 100644 (file)
@@ -423,6 +423,15 @@ _readRangeTblEntry(void)
                        /* we re-use these RELATION fields, too: */
                        READ_OID_FIELD(relid);
                        break;
+               case RTE_GRAPH_TABLE:
+                       READ_NODE_FIELD(graph_pattern);
+                       READ_NODE_FIELD(graph_table_columns);
+                       /* we re-use these RELATION fields, too: */
+                       READ_OID_FIELD(relid);
+                       READ_CHAR_FIELD(relkind);
+                       READ_INT_FIELD(rellockmode);
+                       READ_UINT_FIELD(perminfoindex);
+                       break;
                case RTE_RESULT:
                        /* no extra fields */
                        break;
index 5eceb3218283a3d53d521f135506e5c11c10dee9..c26f48edfa01070d1a6b82af8d97943a71cc0f2d 100644 (file)
@@ -787,6 +787,16 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
                case RTE_RESULT:
                        /* RESULT RTEs, in themselves, are no problem. */
                        break;
+
+               case RTE_GRAPH_TABLE:
+
+                       /*
+                        * Shouldn't happen since these are replaced by subquery RTEs when
+                        * rewriting queries.
+                        */
+                       Assert(false);
+                       return;
+
                case RTE_GROUP:
                        /* Shouldn't happen; we're only considering baserels here. */
                        Assert(false);
index b2beb0a0d68eddcf1524a4092e2064c729078094..0e77114efc8984c73fc3c6ec3247eb1cf192a633 100644 (file)
@@ -1631,6 +1631,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
                                case RTE_GROUP:
                                        /* these can't contain any lateral references */
                                        break;
+                               case RTE_GRAPH_TABLE:
+                                       /* shouldn't happen here */
+                                       Assert(false);
+                                       break;
                        }
                }
        }
@@ -2696,6 +2700,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;
                                }
                        }
                }
index 8c0fe28d63f5917eb977cffeb08c74d8f6a26f2d..8b5a4af6bf2a34b4a5d69cc6f49eccecda85a879 100644 (file)
@@ -23,6 +23,7 @@ OBJS = \
        parse_enr.o \
        parse_expr.o \
        parse_func.o \
+       parse_graphtable.o \
        parse_jsontable.o \
        parse_merge.o \
        parse_node.o \
index 3f669a6eb26fb204cb5e4047e956e769110c73fb..ad31dee26865d0cc56fd7f41e86564bf8bd49fed 100644 (file)
@@ -79,9 +79,6 @@ static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
 static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
 static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                                                                           bool isTopLevel, List **targetlist);
-static void constructSetOpTargetlist(ParseState *pstate, SetOperationStmt *op,
-                                                                        const List *ltargetlist, const List *rtargetlist,
-                                                                        List **targetlist, const char *context, bool recursive);
 static void determineRecursiveColTypes(ParseState *pstate,
                                                                           Node *larg, List *nrtargetlist);
 static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
@@ -2271,7 +2268,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
  * given SetOperationStmt node.  context is a string for error messages
  * ("UNION" etc.).  recursive is true if it is a recursive union.
  */
-static void
+void
 constructSetOpTargetlist(ParseState *pstate, SetOperationStmt *op,
                                                 const List *ltargetlist, const List *rtargetlist,
                                                 List **targetlist, const char *context, bool recursive)
index f01f5734fe9383dffa0529ad4d16673e17fc9318..c25842496039ce414ec8c4a9b7cf81bd68debbb3 100644 (file)
@@ -295,6 +295,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
@@ -685,6 +686,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.
  * They must be listed first so that their numeric codes do not depend on
@@ -727,18 +758,18 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
        DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
-       DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC
+       DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC DESTINATION
        DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
        DOUBLE_P DROP
 
-       EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENFORCED ENUM_P ERROR_P
-       ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
-       EXPRESSION EXTENSION EXTERNAL EXTRACT
+       EACH EDGE ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENFORCED ENUM_P
+       ERROR_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS
+       EXPLAIN EXPRESSION EXTENSION EXTERNAL EXTRACT
 
        FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
        FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
-       GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS
+       GENERATED GLOBAL GRANT GRANTED GRAPH GRAPH_TABLE GREATEST GROUP_P GROUPING GROUPS
 
        HANDLER HAVING HEADER_P HOLD HOUR_P
 
@@ -759,7 +790,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE MERGE_ACTION METHOD
        MINUTE_P MINVALUE MODE MONTH_P MOVE
 
-       NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO
+       NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NFC NFD NFKC NFKD NO NODE
        NONE NORMALIZE NORMALIZED
        NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
        NULLS_P NUMERIC
@@ -771,12 +802,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        PARALLEL PARAMETER PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PATH
        PERIOD PLACING PLAN PLANS POLICY
        POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
-       PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
+       PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PROPERTIES PROPERTY PUBLICATION
 
        QUOTE QUOTES
 
        RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-       REFRESH REINDEX RELATIVE_P RELEASE RENAME REPACK REPEATABLE REPLACE REPLICA
+       REFRESH REINDEX RELATIONSHIP RELATIVE_P RELEASE RENAME REPACK REPEATABLE REPLACE REPLICA
        RESET RESPECT_P RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
        ROUTINE ROUTINES ROW ROWS RULE
 
@@ -796,7 +827,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 VIRTUAL VOLATILE
+       VERBOSE VERSION_P VERTEX VIEW VIEWS VIRTUAL VOLATILE
 
        WAIT WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -894,7 +925,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc      UNBOUNDED NESTED /* ideally would have same precedence as IDENT */
 %nonassoc      IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
                        SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT PATH
-%left          Op OPERATOR             /* multi-character ops and user-defined operators */
+%left          Op OPERATOR RIGHT_ARROW '|'     /* multi-character ops and user-defined operators */
 %left          '+' '-'
 %left          '*' '/' '%'
 %left          '^'
@@ -1021,6 +1052,7 @@ stmt:
                        | AlterOperatorStmt
                        | AlterTypeStmt
                        | AlterPolicyStmt
+                       | AlterPropGraphStmt
                        | AlterSeqStmt
                        | AlterSystemStmt
                        | AlterTableStmt
@@ -1060,6 +1092,7 @@ stmt:
                        | AlterOpFamilyStmt
                        | CreatePolicyStmt
                        | CreatePLangStmt
+                       | CreatePropGraphStmt
                        | CreateSchemaStmt
                        | CreateSeqStmt
                        | CreateStmt
@@ -7202,6 +7235,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; }
@@ -8090,6 +8124,15 @@ privilege_target:
                                        n->objs = $2;
                                        $$ = n;
                                }
+                       | PROPERTY GRAPH qualified_name_list
+                               {
+                                       PrivTarget *n = palloc_object(PrivTarget);
+
+                                       n->targtype = ACL_TARGET_OBJECT;
+                                       n->objtype = OBJECT_PROPGRAPH;
+                                       n->objs = $3;
+                                       $$ = n;
+                               }
                        | SCHEMA name_list
                                {
                                        PrivTarget *n = palloc_object(PrivTarget);
@@ -9419,6 +9462,370 @@ 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
+                       /*
+                        * SQL standard also allows "PROPERTIES ARE ALL COLUMNS", but that
+                        * would require making ARE a keyword, which seems a bit much for
+                        * such a marginal use.  Could be added later if needed.
+                        */
+                               {
+                                       PropGraphProperties *pr = makeNode(PropGraphProperties);
+
+                                       pr->all = true;
+                                       pr->location = @1;
+
+                                       $$ = (Node *) pr;
+                               }
+                       | PROPERTIES '(' labeled_expr_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 '(' labeled_expr_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
@@ -9715,6 +10122,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);
@@ -10340,6 +10757,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);
@@ -10683,6 +11120,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);
@@ -13958,6 +14404,17 @@ table_ref:     relation_expr opt_alias_clause
                                        n->alias = $3;
                                        $$ = (Node *) n;
                                }
+                       | GRAPH_TABLE '(' qualified_name MATCH graph_pattern COLUMNS '(' labeled_expr_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);
@@ -15309,6 +15766,10 @@ a_expr:                c_expr                                                                  { $$ = $1; }
                                { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
                        | a_expr NOT_EQUALS a_expr
                                { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
+                       | a_expr RIGHT_ARROW a_expr
+                               { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
+                       | a_expr '|' a_expr
+                               { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); }
 
                        | a_expr qual_Op a_expr                         %prec Op
                                { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
@@ -15789,6 +16250,10 @@ b_expr:                c_expr
                                { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
                        | b_expr NOT_EQUALS b_expr
                                { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
+                       | b_expr RIGHT_ARROW b_expr
+                               { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); }
+                       | b_expr '|' b_expr
+                               { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "|", $1, $3, @2); }
                        | b_expr qual_Op b_expr                         %prec Op
                                { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
                        | qual_Op b_expr                                        %prec Op
@@ -16984,6 +17449,8 @@ MathOp:          '+'                                                                    { $$ = "+"; }
                        | LESS_EQUALS                                                   { $$ = "<="; }
                        | GREATER_EQUALS                                                { $$ = ">="; }
                        | NOT_EQUALS                                                    { $$ = "<>"; }
+                       | RIGHT_ARROW                                                   { $$ = "->"; }
+                       | '|'                                                                   { $$ = "|"; }
                ;
 
 qual_Op:       Op
@@ -17564,6 +18031,214 @@ 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;
+
+                                       $$ = (Node *) gep;
+                               }
+               ;
+
+path_primary:
+                       '(' opt_colid opt_is_label_expression where_clause ')'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = VERTEX_PATTERN;
+                                       gep->variable = $2;
+                                       gep->labelexpr = $3;
+                                       gep->whereClause = $4;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       /* full edge pointing left: <-[ xxx ]- */
+                       | '<' '-' '[' opt_colid opt_is_label_expression where_clause ']' '-'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_LEFT;
+                                       gep->variable = $4;
+                                       gep->labelexpr = $5;
+                                       gep->whereClause = $6;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       /* full edge pointing right: -[ xxx ]-> */
+                       | '-' '[' opt_colid opt_is_label_expression where_clause ']' '-' '>'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_RIGHT;
+                                       gep->variable = $3;
+                                       gep->labelexpr = $4;
+                                       gep->whereClause = $5;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       | '-' '[' opt_colid opt_is_label_expression where_clause ']' RIGHT_ARROW
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_RIGHT;
+                                       gep->variable = $3;
+                                       gep->labelexpr = $4;
+                                       gep->whereClause = $5;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       /* full edge any direction: -[ xxx ]- */
+                       | '-' '[' opt_colid opt_is_label_expression where_clause ']' '-'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_ANY;
+                                       gep->variable = $3;
+                                       gep->labelexpr = $4;
+                                       gep->whereClause = $5;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       /* abbreviated edge patterns */
+                       | '<' '-'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_LEFT;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       | '-' '>'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_RIGHT;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       | RIGHT_ARROW
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_RIGHT;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       | '-'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = EDGE_PATTERN_ANY;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+                       | '(' path_pattern_expression where_clause ')'
+                               {
+                                       GraphElementPattern *gep = makeNode(GraphElementPattern);
+
+                                       gep->kind = PAREN_EXPR;
+                                       gep->subexpr = $2;
+                                       gep->whereClause = $3;
+                                       gep->location = @1;
+
+                                       $$ = (Node *) gep;
+                               }
+               ;
+
+opt_colid:
+                       ColId                   { $$ = $1; }
+                       | /*EMPTY*/             { $$ = NULL; }
+               ;
+
+opt_is_label_expression:
+                       IS label_expression             { $$ = $2; }
+                       | /*EMPTY*/                             { $$ = NULL; }
+               ;
+
+/*
+ * graph pattern quantifier
+ */
+
+opt_graph_pattern_quantifier:
+                       '{' Iconst '}'                                  { $$ = list_make2_int($2, $2); }
+                       | '{' ',' Iconst '}'                    { $$ = list_make2_int(0, $3); }
+                       | '{' Iconst ',' Iconst '}'             { $$ = list_make2_int($2, $4); }
+                       | /*EMPTY*/                                             { $$ = NULL; }
+               ;
+
+/*
+ * label expression
+ */
+
+label_expression:
+                       label_term
+                       | label_disjunction
+               ;
+
+label_disjunction:
+                       label_expression '|' label_term
+                               { $$ = makeOrExpr($1, $3, @2); }
+               ;
+
+label_term:
+                       name
+                               { $$ = makeColumnRef($1, NIL, @1, yyscanner); }
+               ;
+
+
 /*****************************************************************************
  *
  *     target list for SELECT
@@ -18085,6 +18760,7 @@ unreserved_keyword:
                        | DELIMITERS
                        | DEPENDS
                        | DEPTH
+                       | DESTINATION
                        | DETACH
                        | DICTIONARY
                        | DISABLE_P
@@ -18094,6 +18770,7 @@ unreserved_keyword:
                        | DOUBLE_P
                        | DROP
                        | EACH
+                       | EDGE
                        | EMPTY_P
                        | ENABLE_P
                        | ENCODING
@@ -18124,6 +18801,7 @@ unreserved_keyword:
                        | GENERATED
                        | GLOBAL
                        | GRANTED
+                       | GRAPH
                        | GROUPS
                        | HANDLER
                        | HEADER_P
@@ -18190,6 +18868,7 @@ unreserved_keyword:
                        | NFKC
                        | NFKD
                        | NO
+                       | NODE
                        | NORMALIZED
                        | NOTHING
                        | NOTIFY
@@ -18234,6 +18913,8 @@ unreserved_keyword:
                        | PROCEDURE
                        | PROCEDURES
                        | PROGRAM
+                       | PROPERTIES
+                       | PROPERTY
                        | PUBLICATION
                        | QUOTE
                        | QUOTES
@@ -18245,6 +18926,7 @@ unreserved_keyword:
                        | REFERENCING
                        | REFRESH
                        | REINDEX
+                       | RELATIONSHIP
                        | RELATIVE_P
                        | RELEASE
                        | RENAME
@@ -18337,6 +19019,7 @@ unreserved_keyword:
                        | VALUE_P
                        | VARYING
                        | VERSION_P
+                       | VERTEX
                        | VIEW
                        | VIEWS
                        | VIRTUAL
@@ -18377,6 +19060,7 @@ col_name_keyword:
                        | EXISTS
                        | EXTRACT
                        | FLOAT_P
+                       | GRAPH_TABLE
                        | GREATEST
                        | GROUPING
                        | INOUT
@@ -18668,6 +19352,7 @@ bare_label_keyword:
                        | DEPENDS
                        | DEPTH
                        | DESC
+                       | DESTINATION
                        | DETACH
                        | DICTIONARY
                        | DISABLE_P
@@ -18679,6 +19364,7 @@ bare_label_keyword:
                        | DOUBLE_P
                        | DROP
                        | EACH
+                       | EDGE
                        | ELSE
                        | EMPTY_P
                        | ENABLE_P
@@ -18717,6 +19403,8 @@ bare_label_keyword:
                        | GENERATED
                        | GLOBAL
                        | GRANTED
+                       | GRAPH
+                       | GRAPH_TABLE
                        | GREATEST
                        | GROUPING
                        | GROUPS
@@ -18813,6 +19501,7 @@ bare_label_keyword:
                        | NFKC
                        | NFKD
                        | NO
+                       | NODE
                        | NONE
                        | NORMALIZE
                        | NORMALIZED
@@ -18870,6 +19559,8 @@ bare_label_keyword:
                        | PROCEDURE
                        | PROCEDURES
                        | PROGRAM
+                       | PROPERTIES
+                       | PROPERTY
                        | PUBLICATION
                        | QUOTE
                        | QUOTES
@@ -18883,6 +19574,7 @@ bare_label_keyword:
                        | REFERENCING
                        | REFRESH
                        | REINDEX
+                       | RELATIONSHIP
                        | RELATIVE_P
                        | RELEASE
                        | RENAME
@@ -18999,6 +19691,7 @@ bare_label_keyword:
                        | VARIADIC
                        | VERBOSE
                        | VERSION_P
+                       | VERTEX
                        | VIEW
                        | VIEWS
                        | VIRTUAL
index 924ee87a453e65c6ba52f05a24125eef623c5936..86c09b29ec2c797c87e91618de0bb01f162c2779 100644 (file)
@@ -10,6 +10,7 @@ backend_sources += files(
   'parse_enr.c',
   'parse_expr.c',
   'parse_func.c',
+  'parse_graphtable.c',
   'parse_jsontable.c',
   'parse_merge.c',
   'parse_node.c',
index 33fd2cccae5f8e4324449efc5c8ef1deec71dc4a..6076e9373c19759a74e603623717d51528555008 100644 (file)
@@ -585,6 +585,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
                        errkind = true;
                        break;
 
+               case EXPR_KIND_PROPGRAPH_PROPERTY:
+                       if (isAgg)
+                               err = _("aggregate functions are not allowed in property definition expressions");
+                       else
+                               err = _("grouping operations are not allowed in property definition expressions");
+
+                       break;
+
                        /*
                         * There is intentionally no default: case here, so that the
                         * compiler will warn if we add a new ParseExprKind without
@@ -1024,6 +1032,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
                case EXPR_KIND_CYCLE_MARK:
                        errkind = true;
                        break;
+               case EXPR_KIND_PROPGRAPH_PROPERTY:
+                       err = _("window functions are not allowed in property definition expressions");
+                       break;
 
                        /*
                         * There is intentionally no default: case here, so that the
index 06b65d4a605208e32ca3b739783da3a17d97eb53..967eea44f1c7cfb16c68885e794ec03ea5c6f079 100644 (file)
@@ -17,6 +17,7 @@
 
 #include "access/htup_details.h"
 #include "access/nbtree.h"
+#include "access/relation.h"
 #include "access/table.h"
 #include "access/tsmapi.h"
 #include "catalog/catalog.h"
@@ -35,6 +36,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"
@@ -65,6 +67,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,
@@ -898,6 +902,126 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
                                                                                  tf, rtf->alias, is_lateral, true);
 }
 
+/*
+ * Similar to parserOpenTable() but for property graphs.
+ */
+static Relation
+parserOpenPropGraph(ParseState *pstate, const RangeVar *relation, LOCKMODE lockmode)
+{
+       Relation        rel;
+       ParseCallbackState pcbstate;
+
+       setup_parser_errposition_callback(&pcbstate, pstate, relation->location);
+
+       rel = relation_openrv(relation, lockmode);
+
+       /*
+        * In parserOpenTable(), the relkind check is done inside table_openrv*.
+        * We do it here since we don't have anything like propgraph_open.
+        */
+       if (rel->rd_rel->relkind != RELKIND_PROPGRAPH)
+               ereport(ERROR,
+                               errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("\"%s\" is not a property graph",
+                                          RelationGetRelationName(rel)));
+
+       cancel_parser_errposition_callback(&pcbstate);
+       return rel;
+}
+
+/*
+ * transformRangeGraphTable -- transform a GRAPH_TABLE clause
+ */
+static ParseNamespaceItem *
+transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt)
+{
+       Relation        rel;
+       Oid                     graphid;
+       GraphTableParseState *gpstate = palloc0_object(GraphTableParseState);
+       Node       *gp;
+       List       *columns = NIL;
+       List       *colnames = NIL;
+       ListCell   *lc;
+       int                     resno = 0;
+       bool            saved_hasSublinks;
+
+       rel = parserOpenPropGraph(pstate, rgt->graph_name, AccessShareLock);
+
+       graphid = RelationGetRelid(rel);
+
+       gpstate->graphid = graphid;
+
+       /*
+        * The syntax does not allow nested GRAPH_TABLE and this function
+        * prohibits subquery within GRAPH_TABLE. There should be only one
+        * GRAPH_TABLE being transformed at a time.
+        */
+       Assert(!pstate->p_graph_table_pstate);
+       pstate->p_graph_table_pstate = gpstate;
+
+       Assert(!pstate->p_lateral_active);
+       pstate->p_lateral_active = true;
+
+       saved_hasSublinks = pstate->p_hasSubLinks;
+       pstate->p_hasSubLinks = false;
+
+       gp = transformGraphPattern(pstate, rgt->graph_pattern);
+
+       /*
+        * Construct a targetlist representing the COLUMNS specified in the
+        * GRAPH_TABLE. This uses previously constructed list of element pattern
+        * variables in the GraphTableParseState.
+        */
+       foreach(lc, rgt->columns)
+       {
+               ResTarget  *rt = lfirst_node(ResTarget, lc);
+               Node       *colexpr;
+               TargetEntry *te;
+               char       *colname;
+
+               colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET);
+
+               if (rt->name)
+                       colname = rt->name;
+               else
+               {
+                       if (IsA(colexpr, GraphPropertyRef))
+                               colname = get_propgraph_property_name(castNode(GraphPropertyRef, colexpr)->propid);
+                       else
+                       {
+                               ereport(ERROR,
+                                               errcode(ERRCODE_SYNTAX_ERROR),
+                                               errmsg("complex graph table column must specify an explicit column name"),
+                                               parser_errposition(pstate, rt->location));
+                               colname = NULL;
+                       }
+               }
+
+               colnames = lappend(colnames, makeString(colname));
+
+               te = makeTargetEntry((Expr *) colexpr, ++resno, colname, false);
+               columns = lappend(columns, te);
+       }
+
+       table_close(rel, NoLock);
+
+       pstate->p_graph_table_pstate = NULL;
+       pstate->p_lateral_active = false;
+
+       /*
+        * If we support subqueries within GRAPH_TABLE, those need to be
+        * propagated to the queries resulting from rewriting graph table RTE. We
+        * don't do that right now, hence prohibit it for now.
+        */
+       if (pstate->p_hasSubLinks)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("subqueries within GRAPH_TABLE reference are not supported")));
+       pstate->p_hasSubLinks = saved_hasSublinks;
+
+       return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true);
+}
+
 /*
  * transformRangeTableSample --- transform a TABLESAMPLE clause
  *
@@ -1121,6 +1245,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) */
index ba7df2a7789cb7cf3923796eb3b4a9c3c8f2ce9e..de4b20cd6af746416afd57f3619a6853d8b26c26 100644 (file)
@@ -546,6 +546,7 @@ assign_collations_walker(Node *node, assign_collations_context *context)
                case T_CaseTestExpr:
                case T_SetToDefault:
                case T_CurrentOfExpr:
+               case T_GraphPropertyRef:
 
                        /*
                         * General case for childless expression nodes.  These should
index 96991cae764dd43e351fe18284b5651b6974caeb..474caffad48f3e95afb3137177c0508ffa9e96f5 100644 (file)
@@ -29,6 +29,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"
@@ -577,6 +578,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
                case EXPR_KIND_COPY_WHERE:
                case EXPR_KIND_GENERATED_COLUMN:
                case EXPR_KIND_CYCLE_MARK:
+               case EXPR_KIND_PROPGRAPH_PROPERTY:
                        /* okay */
                        break;
 
@@ -824,6 +826,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
                        break;
        }
 
+       /* Try it as a graph table property reference. */
+       if (node == NULL)
+               node = transformGraphTablePropertyRef(pstate, cref);
+
        /*
         * Now give the PostParseColumnRefHook, if any, a chance.  We pass the
         * translation-so-far so that it can throw an error if it wishes in the
@@ -1871,6 +1877,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
                case EXPR_KIND_GENERATED_COLUMN:
                        err = _("cannot use subquery in column generation expression");
                        break;
+               case EXPR_KIND_PROPGRAPH_PROPERTY:
+                       err = _("cannot use subquery in property definition expression");
+                       break;
 
                        /*
                         * There is intentionally no default: case here, so that the
@@ -3230,6 +3239,8 @@ ParseExprKindName(ParseExprKind exprKind)
                        return "GENERATED AS";
                case EXPR_KIND_CYCLE_MARK:
                        return "CYCLE";
+               case EXPR_KIND_PROPGRAPH_PROPERTY:
+                       return "property definition expression";
 
                        /*
                         * There is intentionally no default: case here, so that the
index 24f6745923b175dffdafa6d7dc739f2595a2a4e6..8dbd41a3548717cf4daeeae40302b7d80eb19a28 100644 (file)
@@ -2783,6 +2783,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
                case EXPR_KIND_CYCLE_MARK:
                        errkind = true;
                        break;
+               case EXPR_KIND_PROPGRAPH_PROPERTY:
+                       err = _("set-returning functions are not allowed in property definition expressions");
+                       break;
 
                        /*
                         * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
new file mode 100644 (file)
index 0000000..bf805b4
--- /dev/null
@@ -0,0 +1,309 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_graphtable.c
+ *       parsing of GRAPH_TABLE
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/parser/parse_graphtable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_property.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_graphtable.h"
+#include "parser/parse_node.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/relcache.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Return human-readable name of the type of graph element pattern in
+ * GRAPH_TABLE clause, usually for error message purpose.
+ */
+static const char *
+get_gep_kind_name(GraphElementPatternKind gepkind)
+{
+       switch (gepkind)
+       {
+               case VERTEX_PATTERN:
+                       return "vertex";
+               case EDGE_PATTERN_LEFT:
+                       return "edge pointing left";
+               case EDGE_PATTERN_RIGHT:
+                       return "edge pointing right";
+               case EDGE_PATTERN_ANY:
+                       return "edge pointing any direction";
+               case PAREN_EXPR:
+                       return "nested path pattern";
+       }
+
+       /*
+        * When a GraphElementPattern is constructed by the parser, it will set a
+        * value from the GraphElementPatternKind enum. But we may get here if the
+        * GraphElementPatternKind value stored in a catalog is corrupted.
+        */
+       return "unknown";
+}
+
+/*
+ * Transform a property reference.
+ *
+ * A property reference is parsed as a ColumnRef of the form:
+ * <variable>.<property>. If <variable> is one of the variables bound to an
+ * element pattern in the graph pattern and <property> can be resolved as a
+ * property of the property graph, then we return a GraphPropertyRef node
+ * representing the property reference. If the <variable> exists in the graph
+ * pattern but <property> does not exist in the property graph, we raise an
+ * error. However, if <variable> does not exist in the graph pattern, we return
+ * NULL to let the caller handle it as some other kind of ColumnRef. The
+ * variables bound to the element patterns in the graph pattern are expected to
+ * be collected in the GraphTableParseState.
+ */
+Node *
+transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
+{
+       GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
+
+       if (!gpstate)
+               return NULL;
+
+       if (list_length(cref->fields) == 2)
+       {
+               Node       *field1 = linitial(cref->fields);
+               Node       *field2 = lsecond(cref->fields);
+               char       *elvarname;
+               char       *propname;
+
+               if (IsA(field1, A_Star) || IsA(field2, A_Star))
+               {
+                       if (pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)
+                               ereport(ERROR,
+                                               errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                               errmsg("\"*\" is not supported here"),
+                                               parser_errposition(pstate, cref->location));
+                       else
+                               ereport(ERROR,
+                                               errcode(ERRCODE_SYNTAX_ERROR),
+                                               errmsg("\"*\" not allowed here"),
+                                               parser_errposition(pstate, cref->location));
+               }
+
+               elvarname = strVal(field1);
+               propname = strVal(field2);
+
+               if (list_member(gpstate->variables, field1))
+               {
+                       GraphPropertyRef *gpr = makeNode(GraphPropertyRef);
+                       HeapTuple       pgptup;
+                       Form_pg_propgraph_property pgpform;
+
+                       pgptup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
+                       if (!HeapTupleIsValid(pgptup))
+                               ereport(ERROR,
+                                               errcode(ERRCODE_SYNTAX_ERROR),
+                                               errmsg("property \"%s\" does not exist", propname));
+                       pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
+
+                       gpr->location = cref->location;
+                       gpr->elvarname = elvarname;
+                       gpr->propid = pgpform->oid;
+                       gpr->typeId = pgpform->pgptypid;
+                       gpr->typmod = pgpform->pgptypmod;
+                       gpr->collation = pgpform->pgpcollation;
+
+                       ReleaseSysCache(pgptup);
+
+                       return (Node *) gpr;
+               }
+       }
+
+       return NULL;
+}
+
+/*
+ * Transform a label expression.
+ *
+ * A label expression is parsed as either a ColumnRef with a single field or a
+ * label expression like label disjunction. The single field in the ColumnRef is
+ * treated as a label name and transformed to a GraphLabelRef node. The label
+ * expression is recursively transformed into an expression tree containg
+ * GraphLabelRef nodes corresponding to the names of the labels appearing in the
+ * expression. If any label name cannot be resolved to a label in the property
+ * graph, an error is raised.
+ */
+static Node *
+transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
+{
+       Node       *result;
+
+       if (labelexpr == NULL)
+               return NULL;
+
+       check_stack_depth();
+
+       switch (nodeTag(labelexpr))
+       {
+               case T_ColumnRef:
+                       {
+                               ColumnRef  *cref = (ColumnRef *) labelexpr;
+                               const char *labelname;
+                               Oid                     labelid;
+                               GraphLabelRef *lref;
+
+                               Assert(list_length(cref->fields) == 1);
+                               labelname = strVal(linitial(cref->fields));
+
+                               labelid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(labelname));
+                               if (!labelid)
+                                       ereport(ERROR,
+                                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                                       errmsg("label \"%s\" does not exist in property graph \"%s\"", labelname, get_rel_name(gpstate->graphid)));
+
+                               lref = makeNode(GraphLabelRef);
+                               lref->labelid = labelid;
+                               lref->location = cref->location;
+
+                               result = (Node *) lref;
+                               break;
+                       }
+
+               case T_BoolExpr:
+                       {
+                               BoolExpr   *be = (BoolExpr *) labelexpr;
+                               ListCell   *lc;
+                               List       *args = NIL;
+
+                               foreach(lc, be->args)
+                               {
+                                       Node       *arg = (Node *) lfirst(lc);
+
+                                       arg = transformLabelExpr(gpstate, arg);
+                                       args = lappend(args, arg);
+                               }
+
+                               result = (Node *) makeBoolExpr(be->boolop, args, be->location);
+                               break;
+                       }
+
+               default:
+                       /* should not reach here */
+                       elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
+                       result = NULL;          /* keep compiler quiet */
+                       break;
+       }
+
+       return result;
+}
+
+/*
+ * Transform a GraphElementPattern.
+ *
+ * Transform the label expression and the where clause in the element pattern
+ * given by GraphElementPattern. The variable name in the GraphElementPattern is
+ * added to the list of variables in the GraphTableParseState which is used to
+ * resolve property references in this element pattern or elsewhere in the
+ * GRAPH_TABLE.
+ */
+static Node *
+transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep)
+{
+       GraphTableParseState *gpstate = pstate->p_graph_table_pstate;
+
+       if (gep->kind != VERTEX_PATTERN && !IS_EDGE_PATTERN(gep->kind))
+               ereport(ERROR,
+                               errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                               errmsg("unsupported element pattern kind: \"%s\"", get_gep_kind_name(gep->kind)));
+
+       if (gep->quantifier)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("element pattern quantifier is not supported")));
+
+       if (gep->variable)
+               gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable)));
+
+       gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr);
+
+       gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE);
+       assign_expr_collations(pstate, gep->whereClause);
+
+       return (Node *) gep;
+}
+
+/*
+ * Transform a path term (list of GraphElementPattern's).
+ */
+static Node *
+transformPathTerm(ParseState *pstate, List *path_term)
+{
+       List       *result = NIL;
+
+       foreach_node(GraphElementPattern, gep, path_term)
+               result = lappend(result,
+                                                transformGraphElementPattern(pstate, gep));
+
+       return (Node *) result;
+}
+
+/*
+ * Transform a path pattern list (list of path terms).
+ */
+static Node *
+transformPathPatternList(ParseState *pstate, List *path_pattern)
+{
+       List       *result = NIL;
+
+       /* Grammar doesn't allow empty path pattern list */
+       Assert(list_length(path_pattern) > 0);
+
+       /*
+        * We do not support multiple path patterns in one GRAPH_TABLE clause
+        * right now. But we may do so in future.
+        */
+       if (list_length(path_pattern) != 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("multiple path patterns in one GRAPH_TABLE clause not supported")));
+
+       foreach_node(List, path_term, path_pattern)
+               result = lappend(result, transformPathTerm(pstate, path_term));
+
+       return (Node *) result;
+}
+
+/*
+ * Transform a GraphPattern.
+ *
+ * A GraphPattern consists of a list of one or more path patterns and an
+ * optional where clause. Transform them. We use the previously constructure
+ * list of variables in the GraphTableParseState to resolve property references
+ * in the WHERE clause.
+ */
+Node *
+transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern)
+{
+       List       *path_pattern_list = castNode(List,
+                                                                                        transformPathPatternList(pstate, graph_pattern->path_pattern_list));
+
+       graph_pattern->path_pattern_list = path_pattern_list;
+       graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_WHERE);
+       assign_expr_collations(pstate, graph_pattern->whereClause);
+
+       return (Node *) graph_pattern;
+}
index 9c415e166eef7f65101c7202d6a953e1c02c1f5d..ffd1fdab7a0962acdf0d7f08f5122f38bf656787 100644 (file)
@@ -2131,6 +2131,99 @@ 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;
+       rte->rellockmode = AccessShareLock;
+
+       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.
@@ -3029,6 +3122,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);
@@ -3413,10 +3507,11 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
                case RTE_VALUES:
                case RTE_CTE:
                case RTE_GROUP:
+               case RTE_GRAPH_TABLE:
 
                        /*
-                        * Subselect, Table Functions, Values, CTE, GROUP RTEs never have
-                        * dropped columns
+                        * Subselect, Table Functions, Values, CTE, GROUP RTEs, Property
+                        * graph references never have dropped columns
                         */
                        result = false;
                        break;
index f57c4d4108054a7695470066d8efb6b525b93e94..541fef5f18385977dfe55a8d30bacaf869c417a0 100644 (file)
@@ -359,6 +359,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)
@@ -1584,6 +1588,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
                case RTE_RELATION:
                case RTE_VALUES:
                case RTE_NAMEDTUPLESTORE:
+               case RTE_GRAPH_TABLE:
                case RTE_RESULT:
 
                        /*
index 6c162f48342ef7ea2bc5964ba6f8bd8422c5fa27..ee6c34cc14b012004450f0cd230e7f8d863af01d 100644 (file)
@@ -348,6 +348,8 @@ less_equals         "<="
 greater_equals ">="
 less_greater   "<>"
 not_equals             "!="
+/* Note there is no need for left_arrow, since "<-" is not a single operator. */
+right_arrow            "->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -359,7 +361,7 @@ not_equals          "!="
  * If you change either set, adjust the character lists appearing in the
  * rule for "operator"!
  */
-self                   [,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self                   [,()\[\].;\:\|\+\-\*\/\%\^\<\>\=]
 op_chars               [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
 operator               {op_chars}+
 
@@ -853,6 +855,11 @@ other                      .
                                        return NOT_EQUALS;
                                }
 
+{right_arrow}  {
+                                       SET_YYLLOC();
+                                       return RIGHT_ARROW;
+                               }
+
 {self}                 {
                                        SET_YYLLOC();
                                        return yytext[0];
@@ -930,7 +937,7 @@ other                       .
                                                 * that the "self" rule would have.
                                                 */
                                                if (nchars == 1 &&
-                                                       strchr(",()[].;:+-*/%^<>=", yytext[0]))
+                                                       strchr(",()[].;:|+-*/%^<>=", yytext[0]))
                                                        return yytext[0];
                                                /*
                                                 * Likewise, if what we have left is two chars, and
@@ -950,6 +957,8 @@ other                       .
                                                                return NOT_EQUALS;
                                                        if (yytext[0] == '!' && yytext[1] == '=')
                                                                return NOT_EQUALS;
+                                                       if (yytext[0] == '-' && yytext[1] == '>')
+                                                               return RIGHT_ARROW;
                                                }
                                        }
 
index 4680752e6a7f8aedbfeb8f506ff531c72b561f30..09070047b7e06efbbbb8b248d02f48cc7d37cfc5 100644 (file)
@@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = \
        rewriteDefine.o \
+       rewriteGraphTable.o \
        rewriteHandler.o \
        rewriteManip.o \
        rewriteRemove.o \
index 2b288d6b61c0d5df899847abf7961254f2f22ff8..4387d80d93ca26848ec6e9f0cac1b91384f725dd 100644 (file)
@@ -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 (file)
index 0000000..bbf3316
--- /dev/null
@@ -0,0 +1,1318 @@
+/*-------------------------------------------------------------------------
+ *
+ * rewriteGraphTable.c
+ *             Support for rewriting GRAPH_TABLE clauses.
+ *
+ * Portions Copyright (c) 1996-2026, 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/genam.h"
+#include "access/table.h"
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_func.h"
+#include "parser/parse_node.h"
+#include "parser/parse_oper.h"
+#include "parser/parse_relation.h"
+#include "parser/parsetree.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_graphtable.h"
+#include "rewrite/rewriteGraphTable.h"
+#include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Represents one path factor in a path.
+ *
+ * In a non-cyclic path, one path factor corresponds to one element pattern.
+ *
+ * In a cyclic path, one path factor corresponds to all the element patterns with
+ * the same variable name.
+ */
+struct path_factor
+{
+       GraphElementPatternKind kind;
+       const char *variable;
+       Node       *labelexpr;
+       Node       *whereClause;
+       int                     factorpos;              /* Position of this path factor in the list of
+                                                                * path factors representing a given path
+                                                                * pattern. */
+       List       *labeloids;          /* OIDs of all the labels referenced in
+                                                                * labelexpr. */
+       /* Links to adjacent vertex path factors if this is an edge path factor. */
+       struct path_factor *src_pf;
+       struct path_factor *dest_pf;
+};
+
+/*
+ * Represents one property graph element (vertex or edge) in the path.
+ *
+ * Label expression in an element pattern resolves into a set of elements. We
+ * create one path_element object for each of those elements.
+ */
+struct path_element
+{
+       /* Path factor from which this element is derived. */
+       struct path_factor *path_factor;
+       Oid                     elemoid;
+       Oid                     reloid;
+       /* Source and destination vertex elements for an edge element. */
+       Oid                     srcvertexid;
+       Oid                     destvertexid;
+       /* Source and destination conditions for an edge element. */
+       List       *src_quals;
+       List       *dest_quals;
+};
+
+static Node *replace_property_refs(Oid propgraphid, Node *node, const List *mappings);
+static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum, AttrNumber catalog_eqop_attnum);
+static List *generate_queries_for_path_pattern(RangeTblEntry *rte, List *element_patterns);
+static Query *generate_query_for_graph_path(RangeTblEntry *rte, List *path);
+static Node *generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist);
+static List *generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_pattern_lists, int elempos);
+static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte);
+static Query *generate_union_from_pathqueries(List **pathqueries);
+static List *get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf);
+static bool is_property_associated_with_label(Oid labeloid, Oid propoid);
+static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex);
+
+/*
+ * Convert GRAPH_TABLE clause into a subquery using relational
+ * operators.
+ */
+Query *
+rewriteGraphTable(Query *parsetree, int rt_index)
+{
+       RangeTblEntry *rte;
+       Query      *graph_table_query;
+       List       *path_pattern;
+       List       *pathqueries = NIL;
+
+       rte = rt_fetch(rt_index, parsetree->rtable);
+
+       Assert(list_length(rte->graph_pattern->path_pattern_list) == 1);
+
+       path_pattern = linitial(rte->graph_pattern->path_pattern_list);
+       pathqueries = generate_queries_for_path_pattern(rte, path_pattern);
+       graph_table_query = generate_union_from_pathqueries(&pathqueries);
+
+       AcquireRewriteLocks(graph_table_query, true, false);
+
+       rte->rtekind = RTE_SUBQUERY;
+       rte->subquery = graph_table_query;
+       rte->lateral = true;
+
+       /*
+        * Reset no longer applicable fields, to appease
+        * WRITE_READ_PARSE_PLAN_TREES.
+        */
+       rte->graph_pattern = NULL;
+       rte->graph_table_columns = NIL;
+
+       return parsetree;
+}
+
+/*
+ * Generate queries representing the given path pattern applied to the given
+ * property graph.
+ *
+ * A path pattern consists of one or more element patterns. Each of the element
+ * patterns may be satisfied by multiple elements. A path satisfying the given
+ * path pattern consists of one element from each element pattern.  There can be
+ * as many paths as the number of combinations of the elements.  A path pattern
+ * in itself is a K-partite graph where K = number of element patterns in the
+ * path pattern. The possible paths are computed by performing a DFS in this
+ * graph. The DFS is implemented as recursion. Each of these paths is converted
+ * into a query connecting all the elements in that path. Set of these queries is
+ * returned.
+ *
+ * Between every two vertex elements in the path there is an edge element that
+ * connects them.  An edge connects two vertexes identified by the source and
+ * destination keys respectively. The connection between an edge and its
+ * adjacent vertex is naturally computed as an equi-join between edge and vertex
+ * table on their respective keys. Hence the query representing one path
+ * consists of JOINs between edge and vertex tables.
+ *
+ * generate_queries_for_path_pattern() starts the recursion but actual work is
+ * done by generate_queries_for_path_pattern_recurse().
+ * generate_query_for_graph_path() constructs a query for a given path.
+ *
+ * A path pattern may result into no path if any of the element pattern yields no
+ * elements or edge patterns yield no edges connecting adjacent vertex patterns.
+ * In such a case a dummy query which returns no result is returned
+ * (generate_query_for_empty_path_pattern()).
+ *
+ * 'path_pattern' is given path pattern to be applied on the property graph in
+ * the GRAPH_TABLE clause represented by given 'rte'.
+ */
+static List *
+generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
+{
+       List       *pathqueries = NIL;
+       List       *path_elem_lists = NIL;
+       int                     factorpos = 0;
+       List       *path_factors = NIL;
+       struct path_factor *prev_pf = NULL;
+
+       Assert(list_length(path_pattern) > 0);
+
+       /*
+        * Create a list of path factors representing the given path pattern
+        * linking edge path factors to their adjacent vertex path factors.
+        *
+        * While doing that merge element patterns with the same variable name
+        * into a single path_factor.
+        */
+       foreach_node(GraphElementPattern, gep, path_pattern)
+       {
+               struct path_factor *pf = NULL;
+
+               /*
+                * Unsupported conditions should have been caught by the parser
+                * itself. We have corresponding Asserts here to document the
+                * assumptions in this code.
+                */
+               Assert(gep->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(gep->kind));
+               Assert(!gep->quantifier);
+
+               foreach_ptr(struct path_factor, other, path_factors)
+               {
+                       if (gep->variable && other->variable &&
+                               strcmp(gep->variable, other->variable) == 0)
+                       {
+                               if (other->kind != gep->kind)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                                                        errmsg("element patterns with same variable name \"%s\" but different element pattern types",
+                                                                       gep->variable)));
+
+                               /*
+                                * If both the element patterns have label expressions, they
+                                * need to be conjuncted, which is not supported right now.
+                                *
+                                * However, an empty label expression means all labels.
+                                * Conjunction of any label expression with all labels is the
+                                * expression itself. Hence if only one of the two element
+                                * patterns has a label expression use that expression.
+                                */
+                               if (!other->labelexpr)
+                                       other->labelexpr = gep->labelexpr;
+                               else if (gep->labelexpr && !equal(other->labelexpr, gep->labelexpr))
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                        errmsg("element patterns with same variable name \"%s\" but different label expressions are not supported",
+                                                                       gep->variable)));
+
+                               /*
+                                * If two element patterns have the same variable name, they
+                                * represent the same set of graph elements and hence are
+                                * constrained by conditions from both the element patterns.
+                                */
+                               if (!other->whereClause)
+                                       other->whereClause = gep->whereClause;
+                               else if (gep->whereClause)
+                                       other->whereClause = (Node *) makeBoolExpr(AND_EXPR,
+                                                                                                                          list_make2(other->whereClause, gep->whereClause),
+                                                                                                                          -1);
+                               pf = other;
+                               break;
+                       }
+               }
+
+               if (!pf)
+               {
+                       {
+                               pf = palloc0_object(struct path_factor);
+                               pf->factorpos = factorpos++;
+                               pf->kind = gep->kind;
+                               pf->labelexpr = gep->labelexpr;
+                               pf->variable = gep->variable;
+                               pf->whereClause = gep->whereClause;
+
+                               path_factors = lappend(path_factors, pf);
+                       }
+               }
+
+               /*
+                * Setup links to the previous path factor in the path.
+                *
+                * If the previous path factor represents an edge, this path factor
+                * represents an adjacent vertex; the source vertex for an edge
+                * pointing left or the destination vertex for an edge pointing right.
+                * If this path factor represents an edge, the previous path factor
+                * represents an adjacent vertex; source vertex for an edge pointing
+                * right or the destination vertex for an edge pointing left.
+                *
+                * Edge pointing in any direction is treated similar to that pointing
+                * in right direction here.  When constructing a query in
+                * generate_query_for_graph_path(), we will try links in both the
+                * directions.
+                *
+                * If multiple edge patterns share the same variable name, they
+                * constrain the adjacent vertex patterns since an edge can connect
+                * only one pair of vertexes. These adjacent vertex patterns need to
+                * be merged even though they have different variables. Such element
+                * patterns form a walk of graph where vertex and edges are repeated.
+                * For example, in (a)-[b]->(c)<-[b]-(d), (a) and (d) represent the
+                * same vertex element. This is slighly harder to implement and
+                * probably less useful. Hence not supported for now.
+                */
+               if (prev_pf)
+               {
+                       if (prev_pf->kind == EDGE_PATTERN_RIGHT || prev_pf->kind == EDGE_PATTERN_ANY)
+                       {
+                               Assert(!IS_EDGE_PATTERN(pf->kind));
+                               if (prev_pf->dest_pf && prev_pf->dest_pf != pf)
+                                       ereport(ERROR,
+                                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                                       errmsg("an edge cannot connect more than two vertexes even in a cyclic pattern"));
+                               prev_pf->dest_pf = pf;
+                       }
+                       else if (prev_pf->kind == EDGE_PATTERN_LEFT)
+                       {
+                               Assert(!IS_EDGE_PATTERN(pf->kind));
+                               if (prev_pf->src_pf && prev_pf->src_pf != pf)
+                                       ereport(ERROR,
+                                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                                       errmsg("an edge cannot connect more than two vertexes even in a cyclic pattern"));
+                               prev_pf->src_pf = pf;
+                       }
+
+                       if (pf->kind == EDGE_PATTERN_RIGHT || pf->kind == EDGE_PATTERN_ANY)
+                       {
+                               Assert(!IS_EDGE_PATTERN(prev_pf->kind));
+                               if (pf->src_pf && pf->src_pf != prev_pf)
+                                       ereport(ERROR,
+                                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                                       errmsg("an edge cannot connect more than two vertexes even in a cyclic pattern"));
+                               pf->src_pf = prev_pf;
+                       }
+                       else if (pf->kind == EDGE_PATTERN_LEFT)
+                       {
+                               Assert(!IS_EDGE_PATTERN(prev_pf->kind));
+                               if (pf->dest_pf && pf->dest_pf != prev_pf)
+                                       ereport(ERROR,
+                                                       errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                                       errmsg("an edge cannot connect more than two vertexes even in a cyclic pattern"));
+                               pf->dest_pf = prev_pf;
+                       }
+               }
+
+               prev_pf = pf;
+       }
+
+       /*
+        * Collect list of elements for each path factor. Do this after all the
+        * edge links are setup correctly.
+        */
+       foreach_ptr(struct path_factor, pf, path_factors)
+               path_elem_lists = lappend(path_elem_lists,
+                                                                 get_path_elements_for_path_factor(rte->relid, pf));
+
+       pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
+                                                                                                                       NIL, path_elem_lists, 0);
+       if (!pathqueries)
+               pathqueries = list_make1(generate_query_for_empty_path_pattern(rte));
+
+       return pathqueries;
+}
+
+/*
+ * Recursive workhorse function of generate_queries_for_path_pattern().
+ *
+ * `elempos` is the position of the next element being added in the path being
+ * built.
+ */
+static List *
+generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_elem_lists, int elempos)
+{
+       List       *path_elems = list_nth_node(List, path_elem_lists, elempos);
+
+       foreach_ptr(struct path_element, pe, path_elems)
+       {
+               /* Update current path being built with current element. */
+               cur_path = lappend(cur_path, pe);
+
+               /*
+                * If this is the last element in the path, generate query for the
+                * completed path. Else recurse processing the next element.
+                */
+               if (list_length(path_elem_lists) == list_length(cur_path))
+               {
+                       Query      *pathquery = generate_query_for_graph_path(rte, cur_path);
+
+                       Assert(elempos == list_length(path_elem_lists) - 1);
+                       if (pathquery)
+                               pathqueries = lappend(pathqueries, pathquery);
+               }
+               else
+                       pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries,
+                                                                                                                                       cur_path,
+                                                                                                                                       path_elem_lists,
+                                                                                                                                       elempos + 1);
+               /* Make way for the next element at the same position. */
+               cur_path = list_delete_last(cur_path);
+       }
+
+       return pathqueries;
+}
+
+/*
+ * Construct a query representing given graph path.
+ *
+ * The query contains:
+ *
+ * 1. targetlist corresponding to the COLUMNS clause of GRAPH_TABLE clause
+ *
+ * 2. quals corresponding to the WHERE clause of individual elements, WHERE
+ * clause in GRAPH_TABLE clause and quals representing edge-vertex links.
+ *
+ * 3. fromlist containing all elements in the path
+ *
+ * The collations of property expressions are obtained from the catalog. The
+ * collations of expressions in COLUMNS and WHERE clauses are assigned before
+ * rewriting the graph table.  The collations of the edge-vertex link quals are
+ * assigned when crafting those quals. Thus everything in the query that requires
+ * collation assignment has been taken care of already. No separate collation
+ * assignment is required in this function.
+ *
+ * More details in the prologue of generate_queries_for_path_pattern().
+ */
+static Query *
+generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
+{
+       Query      *path_query = makeNode(Query);
+       List       *fromlist = NIL;
+       List       *qual_exprs = NIL;
+       List       *vars;
+
+       path_query->commandType = CMD_SELECT;
+
+       foreach_ptr(struct path_element, pe, graph_path)
+       {
+               struct path_factor *pf = pe->path_factor;
+               RangeTblRef *rtr;
+               Relation        rel;
+               ParseNamespaceItem *pni;
+
+               Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
+
+               /* Add conditions representing edge connnections. */
+               if (IS_EDGE_PATTERN(pf->kind))
+               {
+                       struct path_element *src_pe;
+                       struct path_element *dest_pe;
+                       Expr       *edge_qual = NULL;
+
+                       Assert(pf->src_pf && pf->dest_pf);
+                       src_pe = list_nth(graph_path, pf->src_pf->factorpos);
+                       dest_pe = list_nth(graph_path, pf->dest_pf->factorpos);
+
+                       /* Make sure that the links of adjacent vertices are correct. */
+                       Assert(pf->src_pf == src_pe->path_factor &&
+                                  pf->dest_pf == dest_pe->path_factor);
+
+                       if (src_pe->elemoid == pe->srcvertexid &&
+                               dest_pe->elemoid == pe->destvertexid)
+                               edge_qual = makeBoolExpr(AND_EXPR,
+                                                                                list_concat(copyObject(pe->src_quals),
+                                                                                                        copyObject(pe->dest_quals)),
+                                                                                -1);
+
+                       /*
+                        * An edge pattern in any direction matches edges in both
+                        * directions, try swapping source and destination. When the
+                        * source and destination is the same vertex table, quals
+                        * corresponding to either direction may get satisfied. Hence OR
+                        * the quals corresponding to both the directions.
+                        */
+                       if (pf->kind == EDGE_PATTERN_ANY &&
+                               dest_pe->elemoid == pe->srcvertexid &&
+                               src_pe->elemoid == pe->destvertexid)
+                       {
+                               List       *src_quals = copyObject(pe->dest_quals);
+                               List       *dest_quals = copyObject(pe->src_quals);
+                               Expr       *rev_edge_qual;
+
+                               /* Swap the source and destination varnos in the quals. */
+                               ChangeVarNodes((Node *) dest_quals, pe->path_factor->src_pf->factorpos + 1,
+                                                          pe->path_factor->dest_pf->factorpos + 1, 0);
+                               ChangeVarNodes((Node *) src_quals, pe->path_factor->dest_pf->factorpos + 1,
+                                                          pe->path_factor->src_pf->factorpos + 1, 0);
+
+                               rev_edge_qual = makeBoolExpr(AND_EXPR, list_concat(src_quals, dest_quals), -1);
+                               if (edge_qual)
+                                       edge_qual = makeBoolExpr(OR_EXPR, list_make2(edge_qual, rev_edge_qual), -1);
+                               else
+                                       edge_qual = rev_edge_qual;
+                       }
+
+                       /*
+                        * If the given edge element does not connect the adjacent vertex
+                        * elements in this path, the path is broken. Abandon this path as
+                        * it won't return any rows.
+                        */
+                       if (edge_qual == NULL)
+                               return NULL;
+
+                       qual_exprs = lappend(qual_exprs, edge_qual);
+               }
+               else
+                       Assert(!pe->src_quals && !pe->dest_quals);
+
+               /*
+                * Create RangeTblEntry for this element table.
+                *
+                * SQL/PGQ standard (Ref. Section 11.19, Access rule 2 and General
+                * rule 4) does not specify whose access privileges to use when
+                * accessing the element tables: property graph owner's or current
+                * user's. It is safer to use current user's privileges so as not to
+                * make property graphs as a hole for unpriviledged data access. This
+                * is inline with the views being security_invoker by default.
+                */
+               rel = table_open(pe->reloid, AccessShareLock);
+               pni = addRangeTableEntryForRelation(make_parsestate(NULL), rel, AccessShareLock,
+                                                                                       NULL, true, false);
+               table_close(rel, NoLock);
+               path_query->rtable = lappend(path_query->rtable, pni->p_rte);
+               path_query->rteperminfos = lappend(path_query->rteperminfos, pni->p_perminfo);
+               pni->p_rte->perminfoindex = list_length(path_query->rteperminfos);
+               rtr = makeNode(RangeTblRef);
+               rtr->rtindex = list_length(path_query->rtable);
+               fromlist = lappend(fromlist, rtr);
+
+               /*
+                * Make sure that the assumption mentioned in create_pe_for_element()
+                * holds true; that the elements' RangeTblEntrys are added in the
+                * order in which their respective path factors appear in the list of
+                * path factors representing the path pattern.
+                */
+               Assert(pf->factorpos + 1 == rtr->rtindex);
+
+               if (pf->whereClause)
+               {
+                       Node       *tr;
+
+                       tr = replace_property_refs(rte->relid, pf->whereClause, list_make1(pe));
+
+                       qual_exprs = lappend(qual_exprs, tr);
+               }
+       }
+
+       if (rte->graph_pattern->whereClause)
+       {
+               Node       *path_quals = replace_property_refs(rte->relid,
+                                                                                                          (Node *) rte->graph_pattern->whereClause,
+                                                                                                          graph_path);
+
+               qual_exprs = lappend(qual_exprs, path_quals);
+       }
+
+       path_query->jointree = makeFromExpr(fromlist,
+                                                                               qual_exprs ? (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1) : NULL);
+
+       /* Construct query targetlist from COLUMNS specification of GRAPH_TABLE. */
+       path_query->targetList = castNode(List,
+                                                                         replace_property_refs(rte->relid,
+                                                                                                                       (Node *) rte->graph_table_columns,
+                                                                                                                       graph_path));
+
+       /*
+        * Mark the columns being accessed in the path query as requiring SELECT
+        * privilege. Any lateral columns should have been handled when the
+        * corresponding ColumnRefs were transformed. Ignore those here.
+        */
+       vars = pull_vars_of_level((Node *) list_make2(qual_exprs, path_query->targetList), 0);
+       foreach_node(Var, var, vars)
+       {
+               RTEPermissionInfo *perminfo = getRTEPermissionInfo(path_query->rteperminfos,
+                                                                                                                  rt_fetch(var->varno, path_query->rtable));
+
+               /* Must offset the attnum to fit in a bitmapset */
+               perminfo->selectedCols = bms_add_member(perminfo->selectedCols,
+                                                                                               var->varattno - FirstLowInvalidHeapAttributeNumber);
+       }
+
+       return path_query;
+}
+
+/*
+ * Construct a query which would not return any rows.
+ *
+ * More details in the prologue of generate_queries_for_path_pattern().
+ */
+static Query *
+generate_query_for_empty_path_pattern(RangeTblEntry *rte)
+{
+       Query      *query = makeNode(Query);
+
+       query->commandType = CMD_SELECT;
+       query->rtable = NIL;
+       query->rteperminfos = NIL;
+       query->jointree = makeFromExpr(NIL, (Node *) makeBoolConst(false, false));
+
+       /*
+        * Even though no rows are returned, the result still projects the same
+        * columns as projected by GRAPH_TABLE clause. Do this by constructing a
+        * target list full of NULL values.
+        */
+       foreach_node(TargetEntry, te, rte->graph_table_columns)
+       {
+               Node       *nte = (Node *) te->expr;
+
+               te->expr = (Expr *) makeNullConst(exprType(nte), exprTypmod(nte), exprCollation(nte));
+               query->targetList = lappend(query->targetList, te);
+       }
+
+       return query;
+}
+
+/*
+ * Construct a query which is UNION of given path queries.
+ *
+ * The UNION query derives collations of its targetlist entries from the
+ * corresponding targetlist entries of the path queries. The targetlists of path
+ * queries being UNION'ed already have collations assigned.  No separate
+ * collation assignment required in this function.
+ *
+ * The function destroys given pathqueries list while constructing
+ * SetOperationStmt recursively. Hence the function always returns with
+ * `pathqueries` set to NIL.
+ */
+static Query *
+generate_union_from_pathqueries(List **pathqueries)
+{
+       List       *rtable = NIL;
+       Query      *sampleQuery = linitial_node(Query, *pathqueries);
+       SetOperationStmt *sostmt;
+       Query      *union_query;
+       int                     resno;
+       ListCell   *lctl,
+                          *lct,
+                          *lcm,
+                          *lcc;
+
+       Assert(list_length(*pathqueries) > 0);
+
+       /* If there's only one pathquery, no need to construct a UNION query. */
+       if (list_length(*pathqueries) == 1)
+       {
+               *pathqueries = NIL;
+               return sampleQuery;
+       }
+
+       sostmt = castNode(SetOperationStmt,
+                                         generate_setop_from_pathqueries(*pathqueries, &rtable, NULL));
+
+       /* Encapsulate the set operation statement into a Query. */
+       union_query = makeNode(Query);
+       union_query->commandType = CMD_SELECT;
+       union_query->rtable = rtable;
+       union_query->setOperations = (Node *) sostmt;
+       union_query->rteperminfos = NIL;
+       union_query->jointree = makeFromExpr(NIL, NULL);
+
+       /*
+        * Generate dummy targetlist for outer query using column names from one
+        * of the queries and common datatypes/collations of topmost set
+        * operation.  It shouldn't matter which query. Also it shouldn't matter
+        * which RT index is used as varno in the target list entries, as long as
+        * it corresponds to a real RT entry; else funny things may happen when
+        * the tree is mashed by rule rewriting. So we use 1 since there's always
+        * one RT entry at least.
+        */
+       Assert(rt_fetch(1, rtable));
+       union_query->targetList = NULL;
+       resno = 1;
+       forfour(lct, sostmt->colTypes,
+                       lcm, sostmt->colTypmods,
+                       lcc, sostmt->colCollations,
+                       lctl, sampleQuery->targetList)
+       {
+               Oid                     colType = lfirst_oid(lct);
+               int32           colTypmod = lfirst_int(lcm);
+               Oid                     colCollation = lfirst_oid(lcc);
+               TargetEntry *sample_tle = (TargetEntry *) lfirst(lctl);
+               char       *colName;
+               TargetEntry *tle;
+               Var                *var;
+
+               Assert(!sample_tle->resjunk);
+               colName = pstrdup(sample_tle->resname);
+               var = makeVar(1, sample_tle->resno, colType, colTypmod, colCollation, 0);
+               var->location = exprLocation((Node *) sample_tle->expr);
+               tle = makeTargetEntry((Expr *) var, (AttrNumber) resno++, colName, false);
+               union_query->targetList = lappend(union_query->targetList, tle);
+       }
+
+       *pathqueries = NIL;
+       return union_query;
+}
+
+/*
+ * Construct a query which is UNION of all the given path queries.
+ *
+ * The function destroys given pathqueries list while constructing
+ * SetOperationStmt recursively.
+ */
+static Node *
+generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist)
+{
+       SetOperationStmt *sostmt;
+       Query      *lquery;
+       Node       *rarg;
+       RangeTblRef *lrtr = makeNode(RangeTblRef);
+       List       *rtargetlist;
+       ParseNamespaceItem *pni;
+
+       /* Recursion termination condition. */
+       if (list_length(pathqueries) == 0)
+       {
+               *targetlist = NIL;
+               return NULL;
+       }
+
+       lquery = linitial_node(Query, pathqueries);
+
+       pni = addRangeTableEntryForSubquery(make_parsestate(NULL), lquery, NULL,
+                                                                               false, false);
+       *rtable = lappend(*rtable, pni->p_rte);
+       lrtr->rtindex = list_length(*rtable);
+       rarg = generate_setop_from_pathqueries(list_delete_first(pathqueries), rtable, &rtargetlist);
+       if (rarg == NULL)
+       {
+               /*
+                * No further path queries in the list. Convert the last query into a
+                * RangeTblRef as expected by SetOperationStmt. Extract a list of the
+                * non-junk TLEs for upper-level processing.
+                */
+               if (targetlist)
+               {
+                       *targetlist = NIL;
+                       foreach_node(TargetEntry, tle, lquery->targetList)
+                       {
+                               if (!tle->resjunk)
+                                       *targetlist = lappend(*targetlist, tle);
+                       }
+               }
+               return (Node *) lrtr;
+       }
+
+       sostmt = makeNode(SetOperationStmt);
+       sostmt->op = SETOP_UNION;
+       sostmt->all = true;
+       sostmt->larg = (Node *) lrtr;
+       sostmt->rarg = rarg;
+       constructSetOpTargetlist(NULL, sostmt, lquery->targetList, rtargetlist, targetlist, "UNION", false);
+
+       return (Node *) sostmt;
+}
+
+/*
+ * Construct a path_element object for the graph element given by `elemoid`
+ * statisfied by the path factor `pf`.
+ *
+ * If the type of graph element does not fit the element pattern kind, the
+ * function returns NULL.
+ */
+static struct path_element *
+create_pe_for_element(struct path_factor *pf, Oid elemoid)
+{
+       HeapTuple       eletup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elemoid));
+       Form_pg_propgraph_element pgeform;
+       struct path_element *pe;
+
+       if (!eletup)
+               elog(ERROR, "cache lookup failed for property graph element %u", elemoid);
+       pgeform = ((Form_pg_propgraph_element) GETSTRUCT(eletup));
+
+       if ((pgeform->pgekind == PGEKIND_VERTEX && pf->kind != VERTEX_PATTERN) ||
+               (pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(pf->kind)))
+       {
+               ReleaseSysCache(eletup);
+               return NULL;
+       }
+
+       pe = palloc0_object(struct path_element);
+       pe->path_factor = pf;
+       pe->elemoid = elemoid;
+       pe->reloid = pgeform->pgerelid;
+
+       /*
+        * When a path is converted into a query
+        * (generate_query_for_graph_path()), a RangeTblEntry will be created for
+        * every element in the path.  Fixing rtindexes of RangeTblEntrys here
+        * makes it possible to craft elements' qual expressions only once while
+        * we have access to the catalog entry. Otherwise they need to be crafted
+        * as many times as the number of paths a given element appears in,
+        * fetching catalog entry again each time.  Hence we simply assume
+        * RangeTblEntrys will be created in the same order in which the
+        * corresponding path factors appear in the list of path factors
+        * representing a path pattern. That way their rtindexes will be same as
+        * path_factor::factorpos + 1.
+        */
+       if (IS_EDGE_PATTERN(pf->kind))
+       {
+               pe->srcvertexid = pgeform->pgesrcvertexid;
+               pe->destvertexid = pgeform->pgedestvertexid;
+               Assert(pf->src_pf && pf->dest_pf);
+
+               pe->src_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->src_pf->factorpos + 1,
+                                                                                                        pe->srcvertexid,
+                                                                                                        Anum_pg_propgraph_element_pgesrckey,
+                                                                                                        Anum_pg_propgraph_element_pgesrcref,
+                                                                                                        Anum_pg_propgraph_element_pgesrceqop);
+               pe->dest_quals = build_edge_vertex_link_quals(eletup, pf->factorpos + 1, pf->dest_pf->factorpos + 1,
+                                                                                                         pe->destvertexid,
+                                                                                                         Anum_pg_propgraph_element_pgedestkey,
+                                                                                                         Anum_pg_propgraph_element_pgedestref,
+                                                                                                         Anum_pg_propgraph_element_pgedesteqop);
+       }
+
+       ReleaseSysCache(eletup);
+
+       return pe;
+}
+
+/*
+ * Returns the list of OIDs of graph labels which the given label expression
+ * resolves to in the given property graph.
+ */
+static List *
+get_labels_for_expr(Oid propgraphid, Node *labelexpr)
+{
+       List       *label_oids;
+
+       if (!labelexpr)
+       {
+               Relation        rel;
+               SysScanDesc scan;
+               ScanKeyData key[1];
+               HeapTuple       tup;
+
+               /*
+                * According to section 9.2 "Contextual inference of a set of labels"
+                * subclause 2.a.ii of SQL/PGQ standard, element pattern which does
+                * not have a label expression is considered to have label expression
+                * equivalent to '%|!%' which is set of all labels.
+                */
+               label_oids = NIL;
+               rel = table_open(PropgraphLabelRelationId, AccessShareLock);
+               ScanKeyInit(&key[0],
+                                       Anum_pg_propgraph_label_pglpgid,
+                                       BTEqualStrategyNumber,
+                                       F_OIDEQ, ObjectIdGetDatum(propgraphid));
+               scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId,
+                                                                 true, NULL, 1, key);
+               while (HeapTupleIsValid(tup = systable_getnext(scan)))
+               {
+                       Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup);
+
+                       label_oids = lappend_oid(label_oids, label->oid);
+               }
+               systable_endscan(scan);
+               table_close(rel, AccessShareLock);
+       }
+       else if (IsA(labelexpr, GraphLabelRef))
+       {
+               GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr);
+
+               label_oids = list_make1_oid(glr->labelid);
+       }
+       else if (IsA(labelexpr, BoolExpr))
+       {
+               BoolExpr   *be = castNode(BoolExpr, labelexpr);
+               List       *label_exprs = be->args;
+
+               label_oids = NIL;
+               foreach_node(GraphLabelRef, glr, label_exprs)
+                       label_oids = lappend_oid(label_oids, glr->labelid);
+       }
+       else
+       {
+               /*
+                * should not reach here since gram.y will not generate a label
+                * expression with other node types.
+                */
+               elog(ERROR, "unsupported label expression node: %d", (int) nodeTag(labelexpr));
+       }
+
+       return label_oids;
+}
+
+/*
+ * Return a list of all the graph elements that satisfy the graph element pattern
+ * represented by the given path_factor `pf`.
+ *
+ * First we find all the graph labels that satisfy the label expression in path
+ * factor. Each label is associated with one or more graph elements.  A union of
+ * all such elements satisfies the element pattern. We create one path_element
+ * object representing every element whose graph element kind qualifies the
+ * element pattern kind. A list of all such path_element objects is returned.
+ *
+ * Note that we need to report an error for an explicitly specified label which
+ * is not associated with any graph element of the required kind. So we have to
+ * treat each label separately. Without that requirement we could have collected
+ * all the unique elements first and then created path_element objects for them
+ * to simplify the code.
+ */
+static List *
+get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
+{
+       List       *label_oids = get_labels_for_expr(propgraphid, pf->labelexpr);
+       List       *elem_oids_seen = NIL;
+       List       *pf_elem_oids = NIL;
+       List       *path_elements = NIL;
+       List       *unresolved_labels = NIL;
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       tup;
+
+       /*
+        * A property graph element can be either a vertex or an edge. Other types
+        * of path factors like nested path pattern need to be handled separately
+        * when supported.
+        */
+       Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
+
+       rel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+       foreach_oid(labeloid, label_oids)
+       {
+               bool            found = false;
+
+               ScanKeyInit(&key[0],
+                                       Anum_pg_propgraph_element_label_pgellabelid,
+                                       BTEqualStrategyNumber,
+                                       F_OIDEQ, ObjectIdGetDatum(labeloid));
+               scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true,
+                                                                 NULL, 1, key);
+               while (HeapTupleIsValid(tup = systable_getnext(scan)))
+               {
+                       Form_pg_propgraph_element_label label_elem = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+                       Oid                     elem_oid = label_elem->pgelelid;
+
+                       if (!list_member_oid(elem_oids_seen, elem_oid))
+                       {
+                               /*
+                                * Create path_element object if the new element qualifies the
+                                * element pattern kind.
+                                */
+                               struct path_element *pe = create_pe_for_element(pf, elem_oid);
+
+                               if (pe)
+                               {
+                                       path_elements = lappend(path_elements, pe);
+
+                                       /* Remember qualified elements. */
+                                       pf_elem_oids = lappend_oid(pf_elem_oids, elem_oid);
+                                       found = true;
+                               }
+
+                               /*
+                                * Rememeber qualified and unqualified elements processed so
+                                * far to avoid processing already processed elements again.
+                                */
+                               elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
+                       }
+                       else if (list_member_oid(pf_elem_oids, elem_oid))
+                       {
+                               /*
+                                * The graph element is known to qualify the given element
+                                * pattern. Flag that the current label has at least one
+                                * qualified element associated with it.
+                                */
+                               found = true;
+                       }
+               }
+
+               if (!found)
+               {
+                       /*
+                        * We did not find any qualified element associated with this
+                        * label. The label or its properties can not be associated with
+                        * the given element pattern. Throw an error if the label was
+                        * explicitly specified in the element pattern. Otherwise remember
+                        * it for later use.
+                        */
+                       if (!pf->labelexpr)
+                               unresolved_labels = lappend_oid(unresolved_labels, labeloid);
+                       else
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                                                errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"",
+                                                               pf->kind == VERTEX_PATTERN ? "vertex" : "edge",
+                                                               get_propgraph_label_name(labeloid),
+                                                               get_rel_name(propgraphid))));
+               }
+
+               systable_endscan(scan);
+       }
+       table_close(rel, AccessShareLock);
+
+       /*
+        * Remove the labels which were not explicitly mentioned in the label
+        * expression but do not have any qualified elements associated with them.
+        * Properties associated with such labels may not be referenced. See
+        * replace_property_refs_mutator() for more details.
+        */
+       pf->labeloids = list_difference_oid(label_oids, unresolved_labels);
+
+       return path_elements;
+}
+
+/*
+ * Mutating property references into table variables
+ */
+
+struct replace_property_refs_context
+{
+       Oid                     propgraphid;
+       const List *mappings;
+};
+
+static Node *
+replace_property_refs_mutator(Node *node, struct replace_property_refs_context *context)
+{
+       if (node == NULL)
+               return NULL;
+       if (IsA(node, Var))
+       {
+               Var                *var = (Var *) node;
+               Var                *newvar = copyObject(var);
+
+               /*
+                * If it's already a Var, then it was a lateral reference.  Since we
+                * are in a subquery after the rewrite, we have to increase the level
+                * by one.
+                */
+               newvar->varlevelsup++;
+
+               return (Node *) newvar;
+       }
+       else if (IsA(node, GraphPropertyRef))
+       {
+               GraphPropertyRef *gpr = (GraphPropertyRef *) node;
+               Node       *n = NULL;
+               struct path_element *found_mapping = NULL;
+               struct path_factor *mapping_factor = NULL;
+               List       *unrelated_labels = NIL;
+
+               foreach_ptr(struct path_element, m, context->mappings)
+               {
+                       if (m->path_factor->variable && strcmp(gpr->elvarname, m->path_factor->variable) == 0)
+                       {
+                               found_mapping = m;
+                               break;
+                       }
+               }
+
+               /*
+                * transformGraphTablePropertyRef() would not create a
+                * GraphPropertyRef for a variable which is not present in the graph
+                * path pattern.
+                */
+               Assert(found_mapping);
+
+               mapping_factor = found_mapping->path_factor;
+
+               /*
+                * Find property definition for given element through any of the
+                * associated labels qualifying the given element pattern.
+                */
+               foreach_oid(labeloid, mapping_factor->labeloids)
+               {
+                       Oid                     elem_labelid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL,
+                                                                                                          Anum_pg_propgraph_element_label_oid,
+                                                                                                          ObjectIdGetDatum(found_mapping->elemoid),
+                                                                                                          ObjectIdGetDatum(labeloid));
+
+                       if (OidIsValid(elem_labelid))
+                       {
+                               HeapTuple       tup = SearchSysCache2(PROPGRAPHLABELPROP, ObjectIdGetDatum(elem_labelid),
+                                                                                                 ObjectIdGetDatum(gpr->propid));
+
+                               if (!tup)
+                               {
+                                       /*
+                                        * The label is associated with the given element but it
+                                        * is not associated with the required property. Check
+                                        * next label.
+                                        */
+                                       continue;
+                               }
+
+                               n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
+                                                                                                                                                       tup, Anum_pg_propgraph_label_property_plpexpr)));
+                               ChangeVarNodes(n, 1, mapping_factor->factorpos + 1, 0);
+
+                               ReleaseSysCache(tup);
+                       }
+                       else
+                       {
+                               /*
+                                * Label is not associated with the element but it may be
+                                * associated with the property through some other element.
+                                * Save it for later use.
+                                */
+                               unrelated_labels = lappend_oid(unrelated_labels, labeloid);
+                       }
+               }
+
+               /* See if we can resolve the property in some other way. */
+               if (!n)
+               {
+                       bool            prop_associated = false;
+
+                       foreach_oid(loid, unrelated_labels)
+                       {
+                               if (is_property_associated_with_label(loid, gpr->propid))
+                               {
+                                       prop_associated = true;
+                                       break;
+                               }
+                       }
+
+                       if (prop_associated)
+                       {
+                               /*
+                                * The property is associated with at least one of the labels
+                                * that satisfy given element pattern. If it's associated with
+                                * the given element (through some other label), use
+                                * correspondig value expression. Otherwise NULL. Ref. SQL/PGQ
+                                * standard section 6.5 Property Reference, General Rule 2.b.
+                                */
+                               n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
+                                                                                         mapping_factor->factorpos + 1);
+
+                               if (!n)
+                                       n = (Node *) makeNullConst(gpr->typeId, gpr->typmod, gpr->collation);
+                       }
+
+               }
+
+               if (!n)
+                       ereport(ERROR,
+                                       errcode(ERRCODE_UNDEFINED_OBJECT),
+                                       errmsg("property \"%s\" for element variable \"%s\" not found",
+                                                  get_propgraph_property_name(gpr->propid), mapping_factor->variable));
+
+               return n;
+       }
+
+       return expression_tree_mutator(node, replace_property_refs_mutator, context);
+}
+
+static Node *
+replace_property_refs(Oid propgraphid, Node *node, const List *mappings)
+{
+       struct replace_property_refs_context context;
+
+       context.mappings = mappings;
+       context.propgraphid = propgraphid;
+
+       return expression_tree_mutator(node, replace_property_refs_mutator, &context);
+}
+
+/*
+ * Build join qualification expressions between edge and vertex tables.
+ */
+static List *
+build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, Oid refid, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum, AttrNumber catalog_eqop_attnum)
+{
+       List       *quals = NIL;
+       Form_pg_propgraph_element pgeform;
+       Datum           datum;
+       Datum      *d1,
+                          *d2,
+                          *d3;
+       int                     n1,
+                               n2,
+                               n3;
+       ParseState *pstate = make_parsestate(NULL);
+       Oid                     refrelid = GetSysCacheOid1(PROPGRAPHELOID, Anum_pg_propgraph_element_pgerelid, ObjectIdGetDatum(refid));
+
+       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);
+
+       datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_eqop_attnum);
+       deconstruct_array_builtin(DatumGetArrayTypeP(datum), OIDOID, &d3, NULL, &n3);
+
+       if (n1 != n2)
+               elog(ERROR, "array size key (%d) vs ref (%d) mismatch for element ID %u", catalog_key_attnum, catalog_ref_attnum, pgeform->oid);
+       if (n1 != n3)
+               elog(ERROR, "array size key (%d) vs operator (%d) mismatch for element ID %u", catalog_key_attnum, catalog_eqop_attnum, pgeform->oid);
+
+       for (int i = 0; i < n1; i++)
+       {
+               AttrNumber      keyattn = DatumGetInt16(d1[i]);
+               AttrNumber      refattn = DatumGetInt16(d2[i]);
+               Oid                     eqop = DatumGetObjectId(d3[i]);
+               Var                *keyvar;
+               Var                *refvar;
+               Oid                     atttypid;
+               int32           atttypmod;
+               Oid                     attcoll;
+               HeapTuple       tup;
+               Form_pg_operator opform;
+               List       *args;
+               Oid                     actual_arg_types[2];
+               Oid                     declared_arg_types[2];
+               OpExpr     *linkqual;
+
+               get_atttypetypmodcoll(pgeform->pgerelid, keyattn, &atttypid, &atttypmod, &attcoll);
+               keyvar = makeVar(edgerti, keyattn, atttypid, atttypmod, attcoll, 0);
+               get_atttypetypmodcoll(refrelid, refattn, &atttypid, &atttypmod, &attcoll);
+               refvar = makeVar(refrti, refattn, atttypid, atttypmod, attcoll, 0);
+
+               tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(eqop));
+               if (!HeapTupleIsValid(tup))
+                       elog(ERROR, "cache lookup failed for operator %u", eqop);
+               opform = (Form_pg_operator) GETSTRUCT(tup);
+               /* An equality operator is a binary operator returning boolean result. */
+               Assert(opform->oprkind == 'b'
+                          && RegProcedureIsValid(opform->oprcode)
+                          && opform->oprresult == BOOLOID
+                          && !get_func_retset(opform->oprcode));
+
+               /*
+                * Prepare operands and cast them to the types required by the
+                * equality operator. Similar to PK/FK quals, referenced vertex key is
+                * used as left operand and referencing edge key is used as right
+                * operand.
+                */
+               args = list_make2(refvar, keyvar);
+               actual_arg_types[0] = exprType((Node *) refvar);
+               actual_arg_types[1] = exprType((Node *) keyvar);
+               declared_arg_types[0] = opform->oprleft;
+               declared_arg_types[1] = opform->oprright;
+               make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
+
+               linkqual = makeNode(OpExpr);
+               linkqual->opno = opform->oid;
+               linkqual->opfuncid = opform->oprcode;
+               linkqual->opresulttype = opform->oprresult;
+               linkqual->opretset = false;
+               /* opcollid and inputcollid will be set by parse_collate.c */
+               linkqual->args = args;
+               linkqual->location = -1;
+
+               ReleaseSysCache(tup);
+               quals = lappend(quals, linkqual);
+       }
+
+       assign_expr_collations(pstate, (Node *) quals);
+
+       return quals;
+}
+
+/*
+ * Check if the given property is associated with the given label.
+ *
+ * A label projects the same set of properties through every element it is
+ * associated with. Find any of the elements and return true if that element is
+ * associated with the given property. False otherwise.
+ */
+static bool
+is_property_associated_with_label(Oid labeloid, Oid propoid)
+{
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       tup;
+       bool            associated = false;
+
+       rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_element_label_pgellabelid,
+                               BTEqualStrategyNumber,
+                               F_OIDEQ, ObjectIdGetDatum(labeloid));
+       scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId,
+                                                         true, NULL, 1, key);
+
+       if (HeapTupleIsValid(tup = systable_getnext(scan)))
+       {
+               Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+
+               associated = SearchSysCacheExists2(PROPGRAPHLABELPROP,
+                                                                                  ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
+       }
+       systable_endscan(scan);
+       table_close(rel, RowShareLock);
+
+       return associated;
+}
+
+/*
+ * If given element has the given property associated with it, through any of
+ * the associated labels, return value expression of the property. Otherwise
+ * NULL.
+ */
+static Node *
+get_element_property_expr(Oid elemoid, Oid propoid, int rtindex)
+{
+       Relation        rel;
+       SysScanDesc scan;
+       ScanKeyData key[1];
+       HeapTuple       labeltup;
+       Node       *n = NULL;
+
+       rel = table_open(PropgraphElementLabelRelationId, RowShareLock);
+       ScanKeyInit(&key[0],
+                               Anum_pg_propgraph_element_label_pgelelid,
+                               BTEqualStrategyNumber,
+                               F_OIDEQ, ObjectIdGetDatum(elemoid));
+       scan = systable_beginscan(rel, PropgraphElementLabelElementLabelIndexId,
+                                                         true, NULL, 1, key);
+
+       while (HeapTupleIsValid(labeltup = systable_getnext(scan)))
+       {
+               Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(labeltup);
+
+               HeapTuple       proptup = SearchSysCache2(PROPGRAPHLABELPROP,
+                                                                                         ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid));
+
+               if (!proptup)
+                       continue;
+               n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP,
+                                                                                                                                       proptup, Anum_pg_propgraph_label_property_plpexpr)));
+               ChangeVarNodes(n, 1, rtindex, 0);
+
+               ReleaseSysCache(proptup);
+               break;
+       }
+       systable_endscan(scan);
+       table_close(rel, RowShareLock);
+
+       return n;
+}
index f98062668d6dcb28b2192dc590c5658c2d7fc396..e33fd81d73512c04bf391ff807acaabde5dcc334 100644 (file)
@@ -36,6 +36,7 @@
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteDefine.h"
+#include "rewrite/rewriteGraphTable.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
 #include "rewrite/rewriteSearchCycle.h"
@@ -173,6 +174,7 @@ AcquireRewriteLocks(Query *parsetree,
                switch (rte->rtekind)
                {
                        case RTE_RELATION:
+                       case RTE_GRAPH_TABLE:
 
                                /*
                                 * Grab the appropriate lock type for the relation, and do not
@@ -2045,6 +2047,16 @@ fireRIRrules(Query *parsetree, List *activeRIRs)
 
                rte = rt_fetch(rt_index, parsetree->rtable);
 
+               /*
+                * Convert GRAPH_TABLE clause into a subquery using relational
+                * operators.  (This will change the rtekind to subquery, so it must
+                * be done before the subquery handling below.)
+                */
+               if (rte->rtekind == RTE_GRAPH_TABLE)
+               {
+                       parsetree = rewriteGraphTable(parsetree, rt_index);
+               }
+
                /*
                 * A subquery RTE can't have associated rules, so there's nothing to
                 * do to this level of the query, but we must recurse into the
index b4651a641318cc12640cdca0faa62024d98569ad..2b609bfc824b1ab4e70e529172b6b7bd4ca1805f 100644 (file)
@@ -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"
@@ -149,6 +150,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:
@@ -179,6 +181,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:
@@ -1739,6 +1742,14 @@ ProcessUtilitySlow(ParseState *pstate,
                                commandCollected = true;
                                break;
 
+                       case T_CreatePropGraphStmt:
+                               address = CreatePropGraph(pstate, (CreatePropGraphStmt *) parsetree);
+                               break;
+
+                       case T_AlterPropGraphStmt:
+                               address = AlterPropGraph(pstate, (AlterPropGraphStmt *) parsetree);
+                               break;
+
                        case T_CreateTransformStmt:
                                address = CreateTransform((CreateTransformStmt *) parsetree);
                                break;
@@ -2008,6 +2019,7 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel)
                case OBJECT_VIEW:
                case OBJECT_MATVIEW:
                case OBJECT_FOREIGN_TABLE:
+               case OBJECT_PROPGRAPH:
                        RemoveRelations(stmt);
                        break;
                default:
@@ -2290,6 +2302,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;
@@ -2566,6 +2581,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;
@@ -2950,6 +2968,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;
@@ -3651,6 +3677,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;
index fc2e9f6686ace44202bcd726b5ef9c3a54c6201f..7ab91fbb06dac5b970bb0dd09730b625d9d3adf0 100644 (file)
@@ -890,6 +890,10 @@ acldefault(ObjectType objtype, Oid ownerId)
                        world_default = ACL_NO_RIGHTS;
                        owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
                        break;
+               case OBJECT_PROPGRAPH:
+                       world_default = ACL_NO_RIGHTS;
+                       owner_default = ACL_ALL_RIGHTS_PROPGRAPH;
+                       break;
                default:
                        elog(ERROR, "unrecognized object type: %d", (int) objtype);
                        world_default = ACL_NO_RIGHTS;  /* keep compiler quiet */
index 6298a37f88eeb7029d191fa65b7b79391fca1d04..153a92fd3eaf71b168a25562db0a1ee7aa50ac5b 100644 (file)
 #include "catalog/pg_operator.h"
 #include "catalog/pg_partitioned_table.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_element.h"
+#include "catalog/pg_propgraph_element_label.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_label_property.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -361,6 +366,9 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
                                                                        bool attrsOnly, bool keysOnly,
                                                                        bool showTblSpc, bool inherits,
                                                                        int prettyFlags, bool missing_ok);
+static void make_propgraphdef_elements(StringInfo buf, Oid pgrelid, char pgekind);
+static void make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid);
+static void make_propgraphdef_properties(StringInfo buf, Oid ellabelid, Oid elrelid);
 static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only,
                                                                                 bool missing_ok);
 static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags,
@@ -1601,6 +1609,325 @@ 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),
+                                                        quote_identifier(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, false, 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, false, buf);
+                               appendStringInfo(buf, ") REFERENCES %s (", quote_identifier(NameStr(pgeform2->pgealias)));
+                               decompile_column_index_array(srcref, pgeform2->pgerelid, false, buf);
+                               appendStringInfoString(buf, ")");
+                       }
+                       else
+                               appendStringInfo(buf, " %s ", quote_identifier(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, false, buf);
+                               appendStringInfo(buf, ") REFERENCES %s (", quote_identifier(NameStr(pgeform2->pgealias)));
+                               decompile_column_index_array(destref, pgeform2->pgerelid, false, buf);
+                               appendStringInfoString(buf, ")");
+                       }
+                       else
+                               appendStringInfo(buf, " %s", quote_identifier(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 appended to buf.
+ */
+static void
+make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid)
+{
+       Relation        pglrel;
+       ScanKeyData scankey[1];
+       SysScanDesc scan;
+       int                     count;
+       HeapTuple       tup;
+
+       pglrel = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+
+       ScanKeyInit(&scankey[0],
+                               Anum_pg_propgraph_element_label_pgelelid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(elid));
+
+       count = 0;
+       scan = systable_beginscan(pglrel, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, scankey);
+       while ((tup = systable_getnext(scan)))
+       {
+               count++;
+       }
+       systable_endscan(scan);
+
+       scan = systable_beginscan(pglrel, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, scankey);
+
+       while ((tup = systable_getnext(scan)))
+       {
+               Form_pg_propgraph_element_label pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+               const char *labelname;
+
+               labelname = get_propgraph_label_name(pgelform->pgellabelid);
+
+               if (strcmp(labelname, elalias) == 0)
+               {
+                       /* If the default label is the only label, don't print anything. */
+                       if (count != 1)
+                               appendStringInfo(buf, " DEFAULT LABEL");
+               }
+               else
+                       appendStringInfo(buf, " LABEL %s", quote_identifier(labelname));
+
+               make_propgraphdef_properties(buf, pgelform->oid, elrelid);
+       }
+
+       systable_endscan(scan);
+
+       table_close(pglrel, AccessShareLock);
+}
+
+/*
+ * Helper function for make_propgraphdef_properties(): Sort (propname, expr)
+ * pairs by name.
+ */
+static int
+propdata_by_name_cmp(const ListCell *a, const ListCell *b)
+{
+       List       *la = lfirst_node(List, a);
+       List       *lb = lfirst_node(List, b);
+       char       *pna = strVal(linitial(la));
+       char       *pnb = strVal(linitial(lb));
+
+       return strcmp(pna, pnb);
+}
+
+/*
+ * Generates element table properties clause (PROPERTIES (...) or NO
+ * PROPERTIES).  Pass in label OID and element table OID.  Result is appended
+ * to buf.
+ */
+static void
+make_propgraphdef_properties(StringInfo buf, Oid ellabelid, Oid elrelid)
+{
+       Relation        plprel;
+       ScanKeyData scankey[1];
+       SysScanDesc scan;
+       HeapTuple       tup;
+       List       *outlist = NIL;
+
+       plprel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock);
+
+       ScanKeyInit(&scankey[0],
+                               Anum_pg_propgraph_label_property_plpellabelid,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(ellabelid));
+
+       /*
+        * We want to output the properties in a deterministic order.  So we first
+        * read all the data, then sort, then print it.
+        */
+       scan = systable_beginscan(plprel, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, scankey);
+
+       while ((tup = systable_getnext(scan)))
+       {
+               Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tup);
+               Datum           exprDatum;
+               bool            isnull;
+               char       *tmp;
+               Node       *expr;
+               char       *propname;
+
+               exprDatum = heap_getattr(tup, Anum_pg_propgraph_label_property_plpexpr, RelationGetDescr(plprel), &isnull);
+               Assert(!isnull);
+               tmp = TextDatumGetCString(exprDatum);
+               expr = stringToNode(tmp);
+               pfree(tmp);
+
+               propname = get_propgraph_property_name(plpform->plppropid);
+
+               outlist = lappend(outlist, list_make2(makeString(propname), expr));
+       }
+
+       systable_endscan(scan);
+       table_close(plprel, AccessShareLock);
+
+       list_sort(outlist, propdata_by_name_cmp);
+
+       if (outlist)
+       {
+               List       *context;
+               ListCell   *lc;
+               bool            first = true;
+
+               context = deparse_context_for(get_relation_name(elrelid), elrelid);
+
+               appendStringInfo(buf, " PROPERTIES (");
+
+               foreach(lc, outlist)
+               {
+                       List       *data = lfirst_node(List, lc);
+                       char       *propname = strVal(linitial(data));
+                       Node       *expr = lsecond(data);
+
+                       if (first)
+                               first = false;
+                       else
+                               appendStringInfo(buf, ", ");
+
+                       if (IsA(expr, Var) && strcmp(propname, get_attname(elrelid, castNode(Var, expr)->varattno, false)) == 0)
+                               appendStringInfo(buf, "%s", quote_identifier(propname));
+                       else
+                               appendStringInfo(buf, "%s AS %s",
+                                                                deparse_expression_pretty(expr, context, false, false, 0, 0),
+                                                                quote_identifier(propname));
+               }
+
+               appendStringInfo(buf, ")");
+       }
+       else
+               appendStringInfo(buf, " NO PROPERTIES");
+}
+
 /*
  * pg_get_statisticsobjdef
  *             Get the definition of an extended statistics object
@@ -7610,6 +7937,171 @@ get_utility_query_def(Query *query, deparse_context *context)
        }
 }
 
+
+/*
+ * Parse back a graph label expression
+ */
+static void
+get_graph_label_expr(Node *label_expr, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+
+       check_stack_depth();
+
+       switch (nodeTag(label_expr))
+       {
+               case T_GraphLabelRef:
+                       {
+                               GraphLabelRef *lref = (GraphLabelRef *) label_expr;
+
+                               appendStringInfoString(buf, quote_identifier(get_propgraph_label_name(lref->labelid)));
+                               break;
+                       }
+
+               case T_BoolExpr:
+                       {
+                               BoolExpr   *be = (BoolExpr *) label_expr;
+                               ListCell   *lc;
+                               bool            first = true;
+
+                               Assert(be->boolop == OR_EXPR);
+
+                               foreach(lc, be->args)
+                               {
+                                       if (!first)
+                                       {
+                                               if (be->boolop == OR_EXPR)
+                                                       appendStringInfoString(buf, "|");
+                                       }
+                                       else
+                                               first = false;
+                                       get_graph_label_expr(lfirst(lc), context);
+                               }
+
+                               break;
+                       }
+
+               default:
+                       elog(ERROR, "unrecognized node type: %d", (int) nodeTag(label_expr));
+                       break;
+       }
+}
+
+/*
+ * Parse back a path pattern expression
+ */
+static void
+get_path_pattern_expr_def(List *path_pattern_expr, deparse_context *context)
+{
+       StringInfo      buf = context->buf;
+       ListCell   *lc;
+
+       foreach(lc, path_pattern_expr)
+       {
+               GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc);
+               const char *sep = "";
+
+               switch (gep->kind)
+               {
+                       case VERTEX_PATTERN:
+                               appendStringInfoString(buf, "(");
+                               break;
+                       case EDGE_PATTERN_LEFT:
+                               appendStringInfoString(buf, "<-[");
+                               break;
+                       case EDGE_PATTERN_RIGHT:
+                       case EDGE_PATTERN_ANY:
+                               appendStringInfoString(buf, "-[");
+                               break;
+                       case PAREN_EXPR:
+                               appendStringInfoString(buf, "(");
+                               break;
+               }
+
+               if (gep->variable)
+               {
+                       appendStringInfoString(buf, quote_identifier(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.
  *
@@ -8220,6 +8712,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:
 
                        /*
@@ -10664,6 +11157,14 @@ get_rule_expr(Node *node, deparse_context *context,
                        get_tablefunc((TableFunc *) node, context, showimplicit);
                        break;
 
+               case T_GraphPropertyRef:
+                       {
+                               GraphPropertyRef *gpr = (GraphPropertyRef *) node;
+
+                               appendStringInfo(buf, "%s.%s", quote_identifier(gpr->elvarname), quote_identifier(get_propgraph_property_name(gpr->propid)));
+                               break;
+                       }
+
                default:
                        elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
                        break;
@@ -12546,6 +13047,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, '(');
index f10948483b9161020d3afc4c88aa0d9c287f9f99..768b11e3b820a7fc7d6137b6b5b1db72e44d189e 100644 (file)
@@ -34,6 +34,8 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_propgraph_label.h"
+#include "catalog/pg_propgraph_property.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_statistic.h"
@@ -3934,3 +3936,39 @@ get_subscription_name(Oid subid, bool missing_ok)
 
        return subname;
 }
+
+char *
+get_propgraph_label_name(Oid labeloid)
+{
+       HeapTuple       tuple;
+       char       *labelname;
+
+       tuple = SearchSysCache1(PROPGRAPHLABELOID, labeloid);
+       if (!tuple)
+       {
+               elog(ERROR, "cache lookup failed for label %u", labeloid);
+               return NULL;
+       }
+       labelname = pstrdup(NameStr(((Form_pg_propgraph_label) GETSTRUCT(tuple))->pgllabel));
+       ReleaseSysCache(tuple);
+
+       return labelname;
+}
+
+char *
+get_propgraph_property_name(Oid propoid)
+{
+       HeapTuple       tuple;
+       char       *propname;
+
+       tuple = SearchSysCache1(PROPGRAPHPROPOID, propoid);
+       if (!tuple)
+       {
+               elog(ERROR, "cache lookup failed for property %u", propoid);
+               return NULL;
+       }
+       propname = pstrdup(NameStr(((Form_pg_propgraph_property) GETSTRUCT(tuple))->pgpname));
+       ReleaseSysCache(tuple);
+
+       return propname;
+}
index 812e2265734c0a153d572f9b4cc757664b394c10..182c16e9b9a1a0a8110e70c0ead211ffbb8262b3 100644 (file)
@@ -2014,7 +2014,11 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
                                break;
 
                        case RTE_SUBQUERY:
-                               /* If this was a view, must lock/unlock the view */
+
+                               /*
+                                * If this was a view or a property graph, must lock/unlock
+                                * it.
+                                */
                                if (OidIsValid(rte->relid))
                                {
                                        if (acquire)
index 349b47c8e29dfe27c20bddaa98b672361d68a2d6..d1431c5c24c0590e095ca5c7ed4b48b5630045dc 100644 (file)
@@ -497,7 +497,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 */
index 5bc77fed9747e39fe6098d8422b6245d00152fbf..dfb1f603a43e9ce4e7b6cae0b95978107133b0d0 100644 (file)
@@ -510,6 +510,9 @@ do { \
                /* UPDATE */
                CONVERT_PRIV('w', "UPDATE");
        }
+       else if (strcmp(type, "PROPERTY GRAPH") == 0 ||
+                        strcmp(type, "PROPERTY GRAPHS") == 0)
+               CONVERT_PRIV('r', "SELECT");
        else if (strcmp(type, "FUNCTION") == 0 ||
                         strcmp(type, "FUNCTIONS") == 0)
                CONVERT_PRIV('X', "EXECUTE");
index df8a69d3b79d8979b0c548be77245b665d15beb6..271a2c3e481ec8f99abe83929380ea983897e2b5 100644 (file)
@@ -3853,6 +3853,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 ||
index 137161aa5e05935ef6c40fa4e8dbd679d2d8b87f..b41a3ae3db4bf882fd30ebce8e4b046790ef9f05 100644 (file)
@@ -1851,10 +1851,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,
@@ -3034,6 +3034,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;
@@ -7484,7 +7487,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);
@@ -16987,8 +16991,20 @@ dumpTable(Archive *fout, const TableInfo *tbinfo)
        namecopy = pg_strdup(fmtId(tbinfo->dobj.name));
        if (tbinfo->dobj.dump & DUMP_COMPONENT_ACL)
        {
-               const char *objtype =
-                       (tbinfo->relkind == RELKIND_SEQUENCE) ? "SEQUENCE" : "TABLE";
+               const char *objtype;
+
+               switch (tbinfo->relkind)
+               {
+                       case RELKIND_SEQUENCE:
+                               objtype = "SEQUENCE";
+                               break;
+                       case RELKIND_PROPGRAPH:
+                               objtype = "PROPERTY GRAPH";
+                               break;
+                       default:
+                               objtype = "TABLE";
+                               break;
+               }
 
                tableAclDumpId =
                        dumpACL(fout, tbinfo->dobj.dumpId, InvalidDumpId,
@@ -17234,8 +17250,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);
@@ -17261,6 +17275,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);
+
+               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;
@@ -17336,8 +17391,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);
@@ -18081,6 +18134,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,
@@ -20414,6 +20469,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");
 
index 6d1d38128fcf77d8f55a0026a2f580545654dd5d..051a3d8ea3db198fcbb07bb2aea8f0e278289859 100644 (file)
@@ -3131,6 +3131,18 @@ 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;',
@@ -4508,6 +4520,22 @@ my %tests = (
                },
        },
 
+       'GRANT SELECT ON PROPERTY GRAPH propgraph' => {
+               create_order => 21,
+               create_sql =>
+                 'GRANT SELECT ON PROPERTY GRAPH dump_test.propgraph TO regress_dump_test_role;',
+               regexp => qr/^
+                       \QGRANT ALL ON PROPERTY GRAPH dump_test.propgraph TO regress_dump_test_role;\E
+                       /xm,
+               like =>
+                 { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+               unlike => {
+                       exclude_dump_test_schema => 1,
+                       no_privs => 1,
+                       only_dump_measurement => 1,
+               },
+       },
+
        'GRANT EXECUTE ON FUNCTION pg_sleep() TO regress_dump_test_role' => {
                create_order => 16,
                create_sql => 'GRANT EXECUTE ON FUNCTION pg_sleep(float8)
index e6365d823ce2538d76eb0ad95e59209174d1c401..37e2241edf664ae54d88456b6bfd503e017fc760 100644 (file)
@@ -1056,7 +1056,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':
                                {
@@ -1190,6 +1190,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':
index 211d8f3b1ecdb7d5c0a4a131f82c32e6455346ae..eafb33143d99e9187724475eb4c00ce219ed23e4 100644 (file)
@@ -24,6 +24,7 @@
 #include "catalog/pg_constraint_d.h"
 #include "catalog/pg_default_acl_d.h"
 #include "catalog/pg_proc_d.h"
+#include "catalog/pg_propgraph_element_d.h"
 #include "catalog/pg_publication_d.h"
 #include "catalog/pg_statistic_ext_d.h"
 #include "catalog/pg_subscription_d.h"
@@ -1068,6 +1069,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"
                                          "  ",
@@ -1078,6 +1080,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"));
 
@@ -1169,6 +1172,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)
@@ -1909,6 +1913,78 @@ describeOneTableDetails(const char *schemaname,
                goto error_return;              /* not an error, just return early */
        }
 
+       /*
+        * If it's a property graph, deal with it here separately.
+        */
+       if (tableinfo.relkind == RELKIND_PROPGRAPH)
+       {
+               printQueryOpt myopt = pset.popt;
+               char       *footers[3] = {NULL, NULL, NULL};
+
+               printfPQExpBuffer(&buf,
+                                                 "SELECT e.pgealias AS \"%s\","
+                                                 "\n     pg_catalog.quote_ident(n.nspname) || '.' ||"
+                                                 "\n          pg_catalog.quote_ident(c.relname) AS \"%s\","
+                                                 "\n     case e.pgekind when " CppAsString2(PGEKIND_VERTEX) " then 'vertex'"
+                                                 "\n                    when " CppAsString2(PGEKIND_EDGE) " then 'edge' end AS \"%s\","
+                                                 "\n     s.pgealias as \"%s\","
+                                                 "\n     d.pgealias as \"%s\""
+                                                 "\n FROM pg_propgraph_element e"
+                                                 "\n      INNER JOIN pg_class c ON c.oid = e.pgerelid"
+                                                 "\n      INNER JOIN pg_namespace n ON c.relnamespace = n.oid"
+                                                 "\n      LEFT JOIN pg_propgraph_element s ON e.pgesrcvertexid = s.oid"
+                                                 "\n      LEFT JOIN pg_propgraph_element d ON e.pgedestvertexid = d.oid"
+                                                 "\n WHERE e.pgepgid = '%s'"
+                                                 "\n ORDER BY e.pgealias",
+                                                 gettext_noop("Element Alias"),
+                                                 gettext_noop("Element Table"),
+                                                 gettext_noop("Element Kind"),
+                                                 gettext_noop("Source Vertex Alias"),
+                                                 gettext_noop("Destination Vertex Alias"),
+                                                 oid);
+
+               res = PSQLexec(buf.data);
+               if (!res)
+                       goto error_return;
+
+               printfPQExpBuffer(&title, _("Property Graph \"%s.%s\""),
+                                                 schemaname, relationname);
+
+               /* Add property graph definition in verbose mode */
+               if (verbose)
+               {
+                       PGresult   *result;
+
+                       printfPQExpBuffer(&buf,
+                                                         "SELECT pg_catalog.pg_get_propgraphdef('%s'::pg_catalog.oid);",
+                                                         oid);
+                       result = PSQLexec(buf.data);
+
+                       if (result)
+                       {
+                               if (PQntuples(result) > 0)
+                               {
+                                       footers[0] = pg_strdup(_("Property graph definition:"));
+                                       footers[1] = pg_strdup(PQgetvalue(result, 0, 0));
+                               }
+                               PQclear(result);
+                       }
+               }
+
+               myopt.footers = footers;
+               myopt.topt.default_footer = false;
+               myopt.title = title.data;
+               myopt.translate_header = true;
+
+               printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+               free(footers[0]);
+               free(footers[1]);
+
+               retval = true;
+               goto error_return;              /* not an error, just return early */
+       }
+
        /* Identify whether we should print collation, nullable, default vals */
        if (tableinfo.relkind == RELKIND_RELATION ||
                tableinfo.relkind == RELKIND_VIEW ||
@@ -4093,6 +4169,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
@@ -4104,6 +4181,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;
 
        int                     ntypes;
        PQExpBufferData buf;
@@ -4114,10 +4192,10 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
        /* Count the number of explicitly-requested relation types */
        ntypes = showTables + showIndexes + showViews + showMatViews +
-               showSeq + showForeign;
-       /* If none, we default to \dtvmsE (but see also command.c) */
+               showSeq + showForeign + showPropGraphs;
+       /* If none, we default to \dtvmsEG (but see also command.c) */
        if (ntypes == 0)
-               showTables = showViews = showMatViews = showSeq = showForeign = true;
+               showTables = showViews = showMatViews = showSeq = showForeign = showPropGraphs = true;
 
        initPQExpBuffer(&buf);
 
@@ -4134,6 +4212,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"),
@@ -4147,6 +4226,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;
@@ -4234,6 +4314,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");
@@ -4289,6 +4371,9 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
                        else if (showForeign)
                                pg_log_error("Did not find any foreign tables named \"%s\".",
                                                         pattern);
+                       else if (showPropGraphs)
+                               pg_log_error("Did not find any property graphs named \"%s\".",
+                                                        pattern);
                        else                            /* should not get here */
                                pg_log_error_internal("Did not find any ??? named \"%s\".",
                                                                          pattern);
@@ -4309,6 +4394,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
                                pg_log_error("Did not find any sequences.");
                        else if (showForeign)
                                pg_log_error("Did not find any foreign tables.");
+                       else if (showPropGraphs)
+                               pg_log_error("Did not find any property graphs.");
                        else                            /* should not get here */
                                pg_log_error_internal("Did not find any ??? relations.");
                }
@@ -4323,6 +4410,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
                        (showMatViews) ? _("List of materialized views") :
                        (showSeq) ? _("List of sequences") :
                        (showForeign) ? _("List of foreign tables") :
+                       (showPropGraphs) ? _("List of property graphs") :
                        "List of ???";          /* should not get here */
                myopt.translate_header = true;
                myopt.translate_columns = translate_columns;
index bcfdbbba571015db911ccf938a37f0c2354f6534..5e0d8f3aae1fd1f21840ffbf5ffc46eda36943a4 100644 (file)
@@ -219,8 +219,8 @@ slashUsage(unsigned short int pager)
 
        HELP0("Informational\n");
        HELP0("  (options: S = show system objects, x = expanded mode, + = additional detail)\n");
-       HELP0("  \\d[Sx+]                list tables, views, and sequences\n");
-       HELP0("  \\d[S+]   NAME          describe table, view, sequence, or index\n");
+       HELP0("  \\d[Sx+]                list tables, views, sequences, and property graphs\n");
+       HELP0("  \\d[S+]   NAME          describe table, view, sequence, index, or property graph\n");
        HELP0("  \\da[Sx]  [PATTERN]     list aggregates\n");
        HELP0("  \\dA[x+]  [PATTERN]     list access methods\n");
        HELP0("  \\dAc[x+] [AMPTRN [TYPEPTRN]]  list operator classes\n");
@@ -246,6 +246,7 @@ slashUsage(unsigned short int pager)
        HELP0("  \\dFp[x+] [PATTERN]     list text search parsers\n");
        HELP0("  \\dFt[x+] [PATTERN]     list text search templates\n");
        HELP0("  \\dg[Sx+] [PATTERN]     list roles\n");
+       HELP0("  \\dG[Sx+] [PATTERN]     list property graphs\n");
        HELP0("  \\di[Sx+] [PATTERN]     list indexes\n");
        HELP0("  \\dl[x+]                list large objects, same as \\lo_list\n");
        HELP0("  \\dL[Sx+] [PATTERN]     list procedural languages\n");
index 199fc64ddf5f3a77a64dce411c5e3bc547903d7e..5bdbf1530a20ae668f40cea45ec04374689dd920 100644 (file)
@@ -815,6 +815,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 = {
@@ -1336,6 +1344,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},
@@ -2739,6 +2748,20 @@ match_previous_words(int pattern_id,
        else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK"))
                COMPLETE_WITH("(");
 
+       /* ALTER PROPERTY GRAPH */
+       else if (Matches("ALTER", "PROPERTY", "GRAPH"))
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
+       else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny))
+               COMPLETE_WITH("ADD", "ALTER", "DROP", "OWNER TO", "RENAME TO", "SET SCHEMA");
+       else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD|ALTER|DROP"))
+               COMPLETE_WITH("VERTEX", "EDGE");
+       else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD|DROP", "VERTEX|EDGE"))
+               COMPLETE_WITH("TABLES");
+       else if (HeadMatches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD") && TailMatches("EDGE"))
+               COMPLETE_WITH("TABLES");
+       else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ALTER", "VERTEX|EDGE"))
+               COMPLETE_WITH("TABLE");
+
        /* ALTER RULE <name>, add ON */
        else if (Matches("ALTER", "RULE", MatchAny))
                COMPLETE_WITH("ON");
@@ -3275,7 +3298,7 @@ match_previous_words(int pattern_id,
                                          "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",
@@ -3313,6 +3336,8 @@ match_previous_words(int pattern_id,
        }
        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"))
@@ -3672,6 +3697,25 @@ match_previous_words(int pattern_id,
        else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING"))
                COMPLETE_WITH("(");
 
+/* CREATE PROPERTY GRAPH */
+       else if (Matches("CREATE", "PROPERTY"))
+               COMPLETE_WITH("GRAPH");
+       else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny))
+               COMPLETE_WITH("VERTEX");
+       else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE"))
+               COMPLETE_WITH("TABLES");
+       else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES"))
+               COMPLETE_WITH("(");
+       else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "("))
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
+       else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(*)"))
+               COMPLETE_WITH("EDGE");
+       else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP"))
+               COMPLETE_WITH("TABLES");
+       else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP", "TABLES"))
+               COMPLETE_WITH("(");
+       else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP", "TABLES", "("))
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE PUBLICATION */
        else if (Matches("CREATE", "PUBLICATION", MatchAny))
@@ -4403,6 +4447,12 @@ match_previous_words(int pattern_id,
        else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny))
                COMPLETE_WITH("CASCADE", "RESTRICT");
 
+       /* DROP PROPERTY GRAPH */
+       else if (Matches("DROP", "PROPERTY"))
+               COMPLETE_WITH("GRAPH");
+       else if (Matches("DROP", "PROPERTY", "GRAPH"))
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs);
+
        /* DROP RULE */
        else if (Matches("DROP", "RULE", MatchAny))
                COMPLETE_WITH("ON");
@@ -4647,6 +4697,7 @@ match_previous_words(int pattern_id,
                                                                                        "LARGE OBJECT",
                                                                                        "PARAMETER",
                                                                                        "PROCEDURE",
+                                                                                       "PROPERTY GRAPH",
                                                                                        "ROUTINE",
                                                                                        "SCHEMA",
                                                                                        "SEQUENCE",
@@ -4805,6 +4856,14 @@ match_previous_words(int pattern_id,
                        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");
@@ -5170,8 +5229,10 @@ match_previous_words(int pattern_id,
                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");
 
@@ -5652,6 +5713,8 @@ match_previous_words(int pattern_id,
                        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"))
index e78952d448d485670ec49a9c3dcc978255b9ef7a..7e1f9b22c49cd553543b3b8f3bc4e8bd7b7ed5be 100644 (file)
@@ -293,6 +293,8 @@ less_equals         "<="
 greater_equals ">="
 less_greater   "<>"
 not_equals             "!="
+/* Note there is no need for left_arrow, since "<-" is not a single operator. */
+right_arrow            "->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -304,7 +306,7 @@ not_equals          "!="
  * If you change either set, adjust the character lists appearing in the
  * rule for "operator"!
  */
-self                   [,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self                   [,()\[\].;\:\|\+\-\*\/\%\^\<\>\=]
 op_chars               [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
 operator               {op_chars}+
 
@@ -652,6 +654,10 @@ other                      .
                                        ECHO;
                                }
 
+{right_arrow}  {
+                                       ECHO;
+                               }
+
        /*
         * These rules are specific to psql --- they implement parenthesis
         * counting and detection of command-ending semicolon.  These must
index 444fc76eed6950fbfd4849940aff30cb40878bf9..bab57372b887694f97363c55058ae35fb89a19f4 100644 (file)
@@ -81,7 +81,12 @@ CATALOG_HEADERS := \
        pg_publication_namespace.h \
        pg_publication_rel.h \
        pg_subscription.h \
-       pg_subscription_rel.h
+       pg_subscription_rel.h \
+       pg_propgraph_element.h \
+       pg_propgraph_element_label.h \
+       pg_propgraph_label.h \
+       pg_propgraph_label_property.h \
+       pg_propgraph_property.h
 
 GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h)
 
index aa8708b195dc9271e9cf3d94bc30bf1185a976f6..eaaf2ff20b77cff974a42c411376e5f66c2b2dc6 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202603161
+#define CATALOG_VERSION_NO     202603162
 
 #endif
index bcc01c87c2e1cba627dd586685efaba2bf5c1d76..fa836e4ee253d6c5db5f87d9ae01884e3e307050 100644 (file)
@@ -69,6 +69,11 @@ catalog_headers = [
   'pg_publication_rel.h',
   'pg_subscription.h',
   'pg_subscription_rel.h',
+  'pg_propgraph_element.h',
+  'pg_propgraph_element_label.h',
+  'pg_propgraph_label.h',
+  'pg_propgraph_label_property.h',
+  'pg_propgraph_property.h',
 ]
 
 # The .dat files we need can just be listed alphabetically.
index ae6e36aeff63d08fe47ffe49034828d8adf16199..c4af599dc906d99e32273b95be0b78d53b785076 100644 (file)
@@ -178,6 +178,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 */
index 361e2cfffebe94f39f4671bd56ee01ffab23ed52..fc8d82665b881771e1ee627cb833b4cc963dbd00 100644 (file)
   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 (file)
index 0000000..2f8af53
--- /dev/null
@@ -0,0 +1,118 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_element.h
+ *       definition of the "property graph elements" system catalog (pg_propgraph_element)
+ *
+ * Portions Copyright (c) 1996-2026, 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
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+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: Oids of the equality operators for comparing source keys
+        */
+       Oid                     pgesrceqop[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];
+
+       /*
+        * for edges: Oids of the equality operators for comparing destination
+        * keys
+        */
+       Oid                     pgedesteqop[1];
+#endif
+} FormData_pg_propgraph_element;
+
+END_CATALOG_STRUCT
+
+/* ----------------
+ *             Form_pg_propgraph_element corresponds to a pointer to a tuple with
+ *             the format of pg_propgraph_element relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_element *Form_pg_propgraph_element;
+
+DECLARE_TOAST(pg_propgraph_element, 8315, 8316);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_element_oid_index, 8300, PropgraphElementObjectIndexId, pg_propgraph_element, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_element_alias_index, 8301, PropgraphElementAliasIndexId, pg_propgraph_element, btree(pgepgid oid_ops, pgealias name_ops));
+
+MAKE_SYSCACHE(PROPGRAPHELOID, pg_propgraph_element_oid_index, 128);
+MAKE_SYSCACHE(PROPGRAPHELALIAS, pg_propgraph_element_alias_index, 128);
+
+#ifdef EXPOSE_TO_CLIENT_CODE
+
+/*
+ * Symbolic values for pgekind column
+ */
+#define PGEKIND_VERTEX 'v'
+#define PGEKIND_EDGE 'e'
+
+#endif                                                 /* EXPOSE_TO_CLIENT_CODE */
+
+#endif                                                 /* PG_PROPGRAPH_ELEMENT_H */
diff --git a/src/include/catalog/pg_propgraph_element_label.h b/src/include/catalog/pg_propgraph_element_label.h
new file mode 100644 (file)
index 0000000..afd1533
--- /dev/null
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_element_label.h
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_propgraph_element_label.h
+ *
+ * NOTES
+ *       The Catalog.pm module reads this file and derives schema
+ *       information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROPGRAPH_ELEMENT_LABEL_H
+#define PG_PROPGRAPH_ELEMENT_LABEL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_propgraph_element_label_d.h"
+
+/* ----------------
+ *             pg_propgraph_element_label definition.  cpp turns this into
+ *             typedef struct FormData_pg_propgraph_element_label
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+CATALOG(pg_propgraph_element_label,8305,PropgraphElementLabelRelationId)
+{
+       Oid                     oid;
+
+       /* OID of the label */
+       Oid                     pgellabelid BKI_LOOKUP(pg_propgraph_label);
+
+       /* OID of the property graph element */
+       Oid                     pgelelid BKI_LOOKUP(pg_propgraph_element);
+} FormData_pg_propgraph_element_label;
+
+END_CATALOG_STRUCT
+
+/* ----------------
+ *             Form_pg_propgraph_element_label corresponds to a pointer to a tuple with
+ *             the format of pg_propgraph_element_label relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_element_label *Form_pg_propgraph_element_label;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_element_label_oid_index, 8312, PropgraphElementLabelObjectIndexId, pg_propgraph_element_label, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_element_label_element_label_index, 8313, PropgraphElementLabelElementLabelIndexId, pg_propgraph_element_label, btree(pgelelid oid_ops, pgellabelid oid_ops));
+DECLARE_INDEX(pg_propgraph_element_label_label_index, 8317, PropgraphElementLabelLabelIndexId, pg_propgraph_element_label, btree(pgellabelid oid_ops));
+
+MAKE_SYSCACHE(PROPGRAPHELEMENTLABELELEMENTLABEL, pg_propgraph_element_label_element_label_index, 128);
+
+#endif                                                 /* PG_PROPGRAPH_ELEMENT_LABEL_H */
diff --git a/src/include/catalog/pg_propgraph_label.h b/src/include/catalog/pg_propgraph_label.h
new file mode 100644 (file)
index 0000000..5c78bfa
--- /dev/null
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_label.h
+ *
+ * Portions Copyright (c) 1996-2026, 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
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+CATALOG(pg_propgraph_label,8303,PropgraphLabelRelationId)
+{
+       Oid                     oid;
+
+       /* OID of the property graph relation */
+       Oid                     pglpgid BKI_LOOKUP(pg_class);
+
+       /* label name */
+       NameData        pgllabel;
+} FormData_pg_propgraph_label;
+
+END_CATALOG_STRUCT
+
+/* ----------------
+ *             Form_pg_propgraph_label corresponds to a pointer to a tuple with
+ *             the format of pg_propgraph_label relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_label *Form_pg_propgraph_label;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_label_oid_index, 8304, PropgraphLabelObjectIndexId, pg_propgraph_label, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_label_graph_name_index, 8314, PropgraphLabelGraphNameIndexId, pg_propgraph_label, btree(pglpgid oid_ops, pgllabel name_ops));
+
+MAKE_SYSCACHE(PROPGRAPHLABELOID, pg_propgraph_label_oid_index, 128);
+MAKE_SYSCACHE(PROPGRAPHLABELNAME, pg_propgraph_label_graph_name_index, 128);
+
+#endif                                                 /* PG_PROPGRAPH_LABEL_H */
diff --git a/src/include/catalog/pg_propgraph_label_property.h b/src/include/catalog/pg_propgraph_label_property.h
new file mode 100644 (file)
index 0000000..39d55bf
--- /dev/null
@@ -0,0 +1,63 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_label_property.h
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_propgraph_label_property.h
+ *
+ * NOTES
+ *       The Catalog.pm module reads this file and derives schema
+ *       information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PROPGRAPH_LABEL_PROPERTY_H
+#define PG_PROPGRAPH_LABEL_PROPERTY_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_propgraph_label_property_d.h"
+
+/* ----------------
+ *             pg_propgraph_label_property definition.  cpp turns this into
+ *             typedef struct FormData_pg_propgraph_label_property
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+CATALOG(pg_propgraph_label_property,8318,PropgraphLabelPropertyRelationId)
+{
+       Oid                     oid;
+
+       /* OID of the property */
+       Oid                     plppropid BKI_LOOKUP(pg_propgraph_property);
+
+       /* OID of the element label */
+       Oid                     plpellabelid BKI_LOOKUP(pg_propgraph_element_label);
+
+#ifdef CATALOG_VARLEN                  /* variable-length fields start here */
+
+       /* property expression */
+       pg_node_tree plpexpr BKI_FORCE_NOT_NULL;
+
+#endif
+} FormData_pg_propgraph_label_property;
+
+END_CATALOG_STRUCT
+
+/* ----------------
+ *             Form_pg_propgraph_label_property corresponds to a pointer to a tuple with
+ *             the format of pg_propgraph_label_property relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_label_property *Form_pg_propgraph_label_property;
+
+DECLARE_TOAST(pg_propgraph_label_property, 8319, 8320);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_label_property_oid_index, 8328, PropgraphLabelPropertyObjectIndexId, pg_propgraph_label_property, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_label_property_label_prop_index, 8329, PropgraphLabelPropertyLabelPropIndexId, pg_propgraph_label_property, btree(plpellabelid oid_ops, plppropid oid_ops));
+
+MAKE_SYSCACHE(PROPGRAPHLABELPROP, pg_propgraph_label_property_label_prop_index, 128);
+
+#endif                                                 /* PG_PROPGRAPH_LABEL_PROPERTY_H */
diff --git a/src/include/catalog/pg_propgraph_property.h b/src/include/catalog/pg_propgraph_property.h
new file mode 100644 (file)
index 0000000..72beafb
--- /dev/null
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_propgraph_property.h
+ *
+ * Portions Copyright (c) 1996-2026, 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
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+CATALOG(pg_propgraph_property,8306,PropgraphPropertyRelationId)
+{
+       Oid                     oid;
+
+       /* OID of the property graph relation */
+       Oid                     pgppgid BKI_LOOKUP(pg_class);
+
+       /* property name */
+       NameData        pgpname;
+
+       /* data type of the property */
+       Oid                     pgptypid BKI_LOOKUP_OPT(pg_type);
+
+       /* typemod of the property */
+       int32           pgptypmod;
+
+       /* collation of the property */
+       Oid                     pgpcollation BKI_LOOKUP_OPT(pg_collation);
+} FormData_pg_propgraph_property;
+
+END_CATALOG_STRUCT
+
+/* ----------------
+ *             Form_pg_propgraph_property corresponds to a pointer to a tuple with
+ *             the format of pg_propgraph_property relation.
+ * ----------------
+ */
+typedef FormData_pg_propgraph_property *Form_pg_propgraph_property;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_property_oid_index, 8307, PropgraphPropertyObjectIndexId, pg_propgraph_property, btree(oid oid_ops));
+DECLARE_UNIQUE_INDEX(pg_propgraph_property_name_index, 8308, PropgraphPropertyNameIndexId, pg_propgraph_property, btree(pgppgid oid_ops, pgpname name_ops));
+
+MAKE_SYSCACHE(PROPGRAPHPROPOID, pg_propgraph_property_oid_index, 128);
+MAKE_SYSCACHE(PROPGRAPHPROPNAME, pg_propgraph_property_name_index, 128);
+
+#endif                                                 /* PG_PROPGRAPH_PROPERTY_H */
diff --git a/src/include/commands/propgraphcmds.h b/src/include/commands/propgraphcmds.h
new file mode 100644 (file)
index 0000000..1bf7d9e
--- /dev/null
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * propgraphcmds.h
+ *       prototypes for propgraphcmds.c.
+ *
+ * Portions Copyright (c) 1996-2026, 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 */
index f3d32ef0188df7b248fcc7772c20f551005bed07..ffadd6671670d2be9f5406594ba04d6a3afdd8b7 100644 (file)
@@ -710,6 +710,19 @@ typedef struct RangeTableFuncCol
        ParseLoc        location;               /* token location, or -1 if unknown */
 } RangeTableFuncCol;
 
+/*
+ * RangeGraphTable - raw form of GRAPH_TABLE clause
+ */
+typedef struct RangeGraphTable
+{
+       NodeTag         type;
+       RangeVar   *graph_name;
+       struct GraphPattern *graph_pattern;
+       List       *columns;
+       Alias      *alias;                      /* table alias & optional column aliases */
+       ParseLoc        location;               /* token location, or -1 if unknown */
+} RangeGraphTable;
+
 /*
  * RangeTableSample - TABLESAMPLE appearing in a raw FROM clause
  *
@@ -1003,6 +1016,42 @@ typedef struct PartitionCmd
        bool            concurrent;
 } PartitionCmd;
 
+/*
+ * Nodes for graph pattern
+ */
+
+typedef struct GraphPattern
+{
+       NodeTag         type;
+       List       *path_pattern_list;
+       Node       *whereClause;
+} GraphPattern;
+
+typedef enum GraphElementPatternKind
+{
+       VERTEX_PATTERN,
+       EDGE_PATTERN_LEFT,
+       EDGE_PATTERN_RIGHT,
+       EDGE_PATTERN_ANY,
+       PAREN_EXPR,
+} GraphElementPatternKind;
+
+#define IS_EDGE_PATTERN(kind) ((kind) == EDGE_PATTERN_ANY || \
+                                                          (kind) == EDGE_PATTERN_RIGHT || \
+                                                          (kind) == EDGE_PATTERN_LEFT)
+
+typedef struct GraphElementPattern
+{
+       NodeTag         type;
+       GraphElementPatternKind kind;
+       const char *variable;
+       Node       *labelexpr;
+       List       *subexpr;
+       Node       *whereClause;
+       List       *quantifier;
+       ParseLoc        location;
+} GraphElementPattern;
+
 /****************************************************************************
  *     Nodes for a Query tree
  ****************************************************************************/
@@ -1075,6 +1124,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 */
@@ -1241,6 +1291,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):
         */
@@ -2381,6 +2437,7 @@ typedef enum ObjectType
        OBJECT_PARAMETER_ACL,
        OBJECT_POLICY,
        OBJECT_PROCEDURE,
+       OBJECT_PROPGRAPH,
        OBJECT_PUBLICATION,
        OBJECT_PUBLICATION_NAMESPACE,
        OBJECT_PUBLICATION_REL,
@@ -4186,6 +4243,88 @@ typedef struct CreateCastStmt
        bool            inout;
 } CreateCastStmt;
 
+/* ----------------------
+ *     CREATE PROPERTY GRAPH Statement
+ * ----------------------
+ */
+typedef struct CreatePropGraphStmt
+{
+       NodeTag         type;
+       RangeVar   *pgname;
+       List       *vertex_tables;
+       List       *edge_tables;
+} CreatePropGraphStmt;
+
+typedef struct PropGraphVertex
+{
+       NodeTag         type;
+       RangeVar   *vtable;
+       List       *vkey;
+       List       *labels;
+       ParseLoc        location;
+} PropGraphVertex;
+
+typedef struct PropGraphEdge
+{
+       NodeTag         type;
+       RangeVar   *etable;
+       List       *ekey;
+       List       *esrckey;
+       char       *esrcvertex;
+       List       *esrcvertexcols;
+       List       *edestkey;
+       char       *edestvertex;
+       List       *edestvertexcols;
+       List       *labels;
+       ParseLoc        location;
+} PropGraphEdge;
+
+typedef struct PropGraphLabelAndProperties
+{
+       NodeTag         type;
+       const char *label;
+       struct PropGraphProperties *properties;
+       ParseLoc        location;
+} PropGraphLabelAndProperties;
+
+typedef struct PropGraphProperties
+{
+       NodeTag         type;
+       List       *properties;
+       bool            all;
+       ParseLoc        location;
+} PropGraphProperties;
+
+/* ----------------------
+ *     ALTER PROPERTY GRAPH Statement
+ * ----------------------
+ */
+
+typedef enum AlterPropGraphElementKind
+{
+       PROPGRAPH_ELEMENT_KIND_VERTEX = 1,
+       PROPGRAPH_ELEMENT_KIND_EDGE = 2,
+} AlterPropGraphElementKind;
+
+typedef struct AlterPropGraphStmt
+{
+       NodeTag         type;
+       RangeVar   *pgname;
+       bool            missing_ok;
+       List       *add_vertex_tables;
+       List       *add_edge_tables;
+       List       *drop_vertex_tables;
+       List       *drop_edge_tables;
+       DropBehavior drop_behavior;
+       AlterPropGraphElementKind element_kind;
+       const char *element_alias;
+       List       *add_labels;
+       const char *drop_label;
+       const char *alter_label;
+       PropGraphProperties *add_properties;
+       List       *drop_properties;
+} AlterPropGraphStmt;
+
 /* ----------------------
  *     CREATE TRANSFORM Statement
  * ----------------------
index 384df50c80aeac7513b519de1e46230639f2e692..6fdf8807533a930d1f2897ee5b30b26736b3bef5 100644 (file)
@@ -2178,6 +2178,30 @@ typedef struct ReturningExpr
        Expr       *retexpr;            /* expression to be returned */
 } ReturningExpr;
 
+/*
+ * GraphLabelRef - label reference in label expression inside GRAPH_TABLE clause
+ */
+typedef struct GraphLabelRef
+{
+       NodeTag         type;
+       Oid                     labelid;
+       ParseLoc        location;
+} GraphLabelRef;
+
+/*
+ * GraphPropertyRef - property reference inside GRAPH_TABLE clause
+ */
+typedef struct GraphPropertyRef
+{
+       Expr            xpr;
+       const char *elvarname;
+       Oid                     propid;
+       Oid                     typeId;
+       int32           typmod;
+       Oid                     collation;
+       ParseLoc        location;
+} GraphPropertyRef;
+
 /*--------------------
  * TargetEntry -
  *        a target entry (used in query target lists)
index abc5f11cafd6655c57e77f4cac7522ad6a1ed4b1..e10270ff0ffdedc9cc1e713c5b1431558cc594ca 100644 (file)
@@ -64,5 +64,8 @@ extern List *BuildOnConflictExcludedTargetlist(Relation targetrel,
                                                                                           Index exclRelIndex);
 
 extern SortGroupClause *makeSortGroupClauseForSetOp(Oid rescoltype, bool require_hash);
+extern void constructSetOpTargetlist(ParseState *pstate, SetOperationStmt *op,
+                                                                        const List *ltargetlist, const List *rtargetlist,
+                                                                        List **targetlist, const char *context, bool recursive);
 
 #endif                                                 /* ANALYZE_H */
index 6f74a8c05c73121be4155faf39d9b9abd58ab58e..b7ded6e6088713b0548d80300b7c82a93114f2e8 100644 (file)
@@ -136,6 +136,7 @@ PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("depth", DEPTH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("desc", DESC, RESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("destination", DESTINATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -147,6 +148,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("edge", EDGE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -191,6 +193,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)
@@ -297,6 +301,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)
@@ -361,6 +366,8 @@ PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("properties", PROPERTIES, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("property", PROPERTY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -374,6 +381,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)
@@ -496,6 +504,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("virtual", VIRTUAL, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h
new file mode 100644 (file)
index 0000000..e52e215
--- /dev/null
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * parse_graphtable.h
+ *             parsing of GRAPH_TABLE
+ *
+ *
+ * Portions Copyright (c) 1996-2026, 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/pg_list.h"
+#include "parser/parse_node.h"
+
+extern Node *transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref);
+
+extern Node *transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern);
+
+#endif                                                 /* PARSE_GRAPHTABLE_H */
index f23e21f318bfcce650beb2688062821b1dfe8247..fc2cbeb2083ef2a26c1311e3976c68ce1f04efab 100644 (file)
@@ -82,6 +82,7 @@ typedef enum ParseExprKind
        EXPR_KIND_COPY_WHERE,           /* WHERE condition in COPY FROM */
        EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
        EXPR_KIND_CYCLE_MARK,           /* cycle mark value */
+       EXPR_KIND_PROPGRAPH_PROPERTY,   /* derived property expression */
 } ParseExprKind;
 
 
@@ -95,6 +96,21 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
                                                                  Oid targetTypeId, int32 targetTypeMod,
                                                                  int location);
 
+/*
+ * Namespace for the GRAPH_TABLE reference being transformed.
+ *
+ * Labels, properties and variables used in the GRAPH_TABLE form the namespace.
+ * The names of the labels and properties used in GRAPH_TABLE are looked up using
+ * the OID of the property graph. Variables are collected in a list as graph
+ * patterns are transformed. This namespace is used to resolve label and property
+ * references in the GRAPH_TABLE.
+ */
+typedef struct GraphTableParseState
+{
+       Oid                     graphid;                /* OID of the graph being referenced */
+       List       *variables;          /* list of element pattern variables in
+                                                                * GRAPH_TABLE */
+} GraphTableParseState;
 
 /*
  * State information used during parse analysis
@@ -174,6 +190,9 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * p_resolve_unknowns: resolve unknown-type SELECT output columns as type TEXT
  * (this is true by default).
  *
+ * p_graph_table_pstate: Namespace for the GRAPH_TABLE reference being
+ * transformed, if any.
+ *
  * p_hasAggs, p_hasWindowFuncs, etc: true if we've found any of the indicated
  * constructs in the query.
  *
@@ -216,6 +235,8 @@ struct ParseState
                                                                         * type text */
 
        QueryEnvironment *p_queryEnv;   /* curr env, incl refs to enclosing env */
+       GraphTableParseState *p_graph_table_pstate; /* Current graph table
+                                                                                                * namespace, if any */
 
        /* Flags telling about things found in the query: */
        bool            p_hasAggs;
index 46b2d5ca02a390b69d67021f47f39ec5e4a9d803..59721a70efbdd53fd25cb33ab32f9e630bbab14a 100644 (file)
@@ -82,6 +82,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 (file)
index 0000000..2b3be15
--- /dev/null
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * rewriteGraphTable.h
+ *             Support for rewriting GRAPH_TABLE clauses.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, 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 */
index 652dc61b834c81d4ce2a51f0d31cf6ad6e3da65e..befae5f6b4fcedd7f10699b1bfbdf6e900449053 100644 (file)
@@ -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)
index ec01fd581cf3f7d635d86d037cdde7dbc5d1029f..0bd1a5ce5066ab454a9c6a598f8adaee6fe79837 100644 (file)
@@ -166,6 +166,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_LANGUAGE                (ACL_USAGE)
 #define ACL_ALL_RIGHTS_LARGEOBJECT     (ACL_SELECT|ACL_UPDATE)
 #define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM)
+#define ACL_ALL_RIGHTS_PROPGRAPH       (ACL_SELECT)
 #define ACL_ALL_RIGHTS_SCHEMA          (ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE      (ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE                    (ACL_USAGE)
index b9ad84ecd4170e4d987eb22cff126a034ad5265e..71b1a8f277dda6a51533abf2f3a13ca6afcc3002 100644 (file)
@@ -213,6 +213,9 @@ extern char *get_publication_name(Oid pubid, bool missing_ok);
 extern Oid     get_subscription_oid(const char *subname, bool missing_ok);
 extern char *get_subscription_name(Oid subid, bool missing_ok);
 
+extern char *get_propgraph_label_name(Oid labeloid);
+extern char *get_propgraph_property_name(Oid propoid);
+
 #define type_is_array(typid)  (get_element_type(typid) != InvalidOid)
 /* type_is_array_domain accepts both plain arrays and domains over arrays */
 #define type_is_array_domain(typid)  (get_base_element_type(typid) != InvalidOid)
index 96eb076ea1fca331b16b1be93cad5a2e2c13932f..c6f36e0275b4a13d4f2cb5a608589f65acbd7815 100644 (file)
@@ -335,6 +335,8 @@ less_equals         "<="
 greater_equals ">="
 less_greater   "<>"
 not_equals             "!="
+/* Note there is no need for left_arrow, since "<-" is not a single operator. */
+right_arrow            "->"
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -346,7 +348,7 @@ not_equals          "!="
  * If you change either set, adjust the character lists appearing in the
  * rule for "operator"!
  */
-self                   [,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self                   [,()\[\].;\:\|\+\-\*\/\%\^\<\>\=]
 op_chars               [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
 operator               {op_chars}+
 
@@ -854,6 +856,10 @@ cppline                    {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
                                        return NOT_EQUALS;
                                }
 
+{right_arrow}  {
+                                       return RIGHT_ARROW;
+                               }
+
 {informix_special} {
                                        /* are we simulating Informix? */
                                        if (INFORMIX_MODE)
@@ -947,7 +953,7 @@ cppline                     {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
                                                 * that the "self" rule would have.
                                                 */
                                                if (nchars == 1 &&
-                                                       strchr(",()[].;:+-*/%^<>=", yytext[0]))
+                                                       strchr(",()[].;:|+-*/%^<>=", yytext[0]))
                                                        return yytext[0];
 
                                                /*
@@ -968,6 +974,8 @@ cppline                     {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
                                                                return NOT_EQUALS;
                                                        if (yytext[0] == '!' && yytext[1] == '=')
                                                                return NOT_EQUALS;
+                                                       if (yytext[0] == '-' && yytext[1] == '>')
+                                                               return RIGHT_ARROW;
                                                }
                                        }
 
index 254a0bacc755eadac1344417d98a0c15cce31cdd..b75e16fde1e72ac1dac57386e04f160bff3767ef 100644 (file)
@@ -53,6 +53,7 @@ test: sql/quote
 test: sql/show
 test: sql/sqljson
 test: sql/sqljson_jsontable
+test: sql/sqlpgq
 test: sql/insupd
 test: sql/parser
 test: sql/prepareas
diff --git a/src/interfaces/ecpg/test/expected/sql-sqlpgq.c b/src/interfaces/ecpg/test/expected/sql-sqlpgq.c
new file mode 100644 (file)
index 0000000..66dedc2
--- /dev/null
@@ -0,0 +1,285 @@
+/* Processed by ecpg (regression mode) */
+/* These include files are added by the preprocessor */
+#include <ecpglib.h>
+#include <ecpgerrno.h>
+#include <sqlca.h>
+/* End of automatic include section */
+#define ECPGdebug(X,Y) ECPGdebug((X)+100,(Y))
+
+#line 1 "sqlpgq.pgc"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+
+#line 1 "regression.h"
+
+
+
+
+
+
+#line 5 "sqlpgq.pgc"
+
+
+/* exec sql whenever sqlerror  sqlprint ; */
+#line 7 "sqlpgq.pgc"
+
+
+int
+main(void)
+{
+/* exec sql begin declare section */
+        
+        
+        
+        
+
+#line 13 "sqlpgq.pgc"
+ char command [ 512 ] ;
+#line 14 "sqlpgq.pgc"
+ char search_address [ 10 ] ;
+#line 15 "sqlpgq.pgc"
+ char cname [ 100 ] ;
+#line 16 "sqlpgq.pgc"
+ int reg ;
+/* exec sql end declare section */
+#line 17 "sqlpgq.pgc"
+
+
+       ECPGdebug(1, stderr);
+
+       { ECPGconnect(__LINE__, 0, "ecpg1_regression" , NULL, NULL , "main", 0); 
+#line 21 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 21 "sqlpgq.pgc"
+
+
+       /* Create schema and tables for property graph testing */
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create schema graph_ecpg_tests", ECPGt_EOIT, ECPGt_EORT);
+#line 24 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 24 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "set search_path = graph_ecpg_tests", ECPGt_EOIT, ECPGt_EORT);
+#line 25 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 25 "sqlpgq.pgc"
+
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create table customers ( customer_id integer primary key , name varchar , address varchar )", ECPGt_EOIT, ECPGt_EORT);
+#line 31 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 31 "sqlpgq.pgc"
+
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create table orders ( order_id integer primary key , register integer )", ECPGt_EOIT, ECPGt_EORT);
+#line 36 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 36 "sqlpgq.pgc"
+
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create table customer_orders ( customer_orders_id integer primary key , customer_id integer references customers ( customer_id ) , order_id integer references orders ( order_id ) )", ECPGt_EOIT, ECPGt_EORT);
+#line 42 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 42 "sqlpgq.pgc"
+
+
+       /* Insert test data */
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "insert into customers values ( 1 , 'customer1' , 'US' ) , ( 2 , 'customer2' , 'CA' ) , ( 3 , 'customer3' , 'GL' )", ECPGt_EOIT, ECPGt_EORT);
+#line 45 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 45 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "insert into orders values ( 1 , 100 ) , ( 2 , 200 ) , ( 3 , 500 )", ECPGt_EOIT, ECPGt_EORT);
+#line 46 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 46 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "insert into customer_orders ( customer_orders_id , customer_id , order_id ) values ( 1 , 1 , 1 ) , ( 2 , 2 , 2 ) , ( 3 , 3 , 3 )", ECPGt_EOIT, ECPGt_EORT);
+#line 47 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 47 "sqlpgq.pgc"
+
+
+       /* Create property graph */
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "create property graph shopgraph vertex tables ( customers , orders ) edge tables ( customer_orders key ( customer_orders_id ) source key ( customer_id ) references customers ( customer_id ) destination key ( order_id ) references orders ( order_id ) )", ECPGt_EOIT, ECPGt_EORT);
+#line 59 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 59 "sqlpgq.pgc"
+
+
+       { ECPGtrans(__LINE__, NULL, "commit");
+#line 61 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 61 "sqlpgq.pgc"
+
+
+       /* direct sql - US customers */
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select * from graph_table ( shopgraph match ( c is customers where c . address = 'US' ) - [ is customer_orders ] -> ( o is orders ) columns ( c . name , o . register ) )", ECPGt_EOIT, 
+       ECPGt_char,(cname),(long)100,(long)1,(100)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+       ECPGt_int,&(reg),(long)1,(long)1,sizeof(int), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 64 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 64 "sqlpgq.pgc"
+
+       printf("found %ld results (%s, %d)\n", sqlca.sqlerrd[2], cname, reg);
+
+       /* direct sql with C variable - GL customers */
+       strcpy(search_address, "GL");
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select * from graph_table ( shopgraph match ( c is customers where c . address = $1  ) - [ is customer_orders ] -> ( o is orders ) columns ( c . name , o . register ) )", 
+       ECPGt_char,(search_address),(long)10,(long)1,(10)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+       ECPGt_char,(cname),(long)100,(long)1,(100)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+       ECPGt_int,&(reg),(long)1,(long)1,sizeof(int), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 69 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 69 "sqlpgq.pgc"
+
+       printf("found %ld results (%s, %d)\n", sqlca.sqlerrd[2], cname, reg);
+
+       /* prepared statement - CA customers */
+       sprintf(command, "select * from graph_table (shopgraph match (c is customers where c.address = $1)-[is customer_orders]->(o is orders) columns (c.name, o.register))");
+       { ECPGprepare(__LINE__, NULL, 0, "graph_stmt", command);
+#line 74 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 74 "sqlpgq.pgc"
+
+       strcpy(search_address, "CA");
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_execute, "graph_stmt", 
+       ECPGt_char,(search_address),(long)10,(long)1,(10)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, 
+       ECPGt_char,(cname),(long)100,(long)1,(100)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+       ECPGt_int,&(reg),(long)1,(long)1,sizeof(int), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 76 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 76 "sqlpgq.pgc"
+
+       printf("found %ld results (%s, %d)\n", sqlca.sqlerrd[2], cname, reg);
+       { ECPGdeallocate(__LINE__, 0, NULL, "graph_stmt");
+#line 78 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 78 "sqlpgq.pgc"
+
+
+       /* cursor test - all customers with orders */
+       /* declare graph_cursor cursor for select * from graph_table ( shopgraph match ( c is customers ) - [ is customer_orders ] -> ( o is orders ) columns ( c . name , o . register ) ) order by name */
+#line 81 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "declare graph_cursor cursor for select * from graph_table ( shopgraph match ( c is customers ) - [ is customer_orders ] -> ( o is orders ) columns ( c . name , o . register ) ) order by name", ECPGt_EOIT, ECPGt_EORT);
+#line 82 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 82 "sqlpgq.pgc"
+
+       /* exec sql whenever not found  break ; */
+#line 83 "sqlpgq.pgc"
+
+       while (1) {
+               { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "fetch graph_cursor", ECPGt_EOIT, 
+       ECPGt_char,(cname),(long)100,(long)1,(100)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, 
+       ECPGt_int,&(reg),(long)1,(long)1,sizeof(int), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 85 "sqlpgq.pgc"
+
+if (sqlca.sqlcode == ECPG_NOT_FOUND) break;
+#line 85 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 85 "sqlpgq.pgc"
+
+               printf("cursor result: %s, %d\n", cname, reg);
+       }
+       /* exec sql whenever not found  continue ; */
+#line 88 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "close graph_cursor", ECPGt_EOIT, ECPGt_EORT);
+#line 89 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 89 "sqlpgq.pgc"
+
+
+       /* label disjunction syntax test */
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select * from graph_table ( shopgraph match ( c is customers | customers where c . address = 'US' ) columns ( c . name ) )", ECPGt_EOIT, 
+       ECPGt_char,(cname),(long)100,(long)1,(100)*sizeof(char), 
+       ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 92 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 92 "sqlpgq.pgc"
+
+       printf("found %ld results (%s)\n", sqlca.sqlerrd[2], cname);
+
+       /* Clean up */
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "drop property graph shopgraph", ECPGt_EOIT, ECPGt_EORT);
+#line 96 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 96 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "drop table customer_orders", ECPGt_EOIT, ECPGt_EORT);
+#line 97 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 97 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "drop table orders", ECPGt_EOIT, ECPGt_EORT);
+#line 98 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 98 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "drop table customers", ECPGt_EOIT, ECPGt_EORT);
+#line 99 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 99 "sqlpgq.pgc"
+
+       { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "drop schema graph_ecpg_tests", ECPGt_EOIT, ECPGt_EORT);
+#line 100 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 100 "sqlpgq.pgc"
+
+       { ECPGtrans(__LINE__, NULL, "commit");
+#line 101 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 101 "sqlpgq.pgc"
+
+       { ECPGdisconnect(__LINE__, "CURRENT");
+#line 102 "sqlpgq.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 102 "sqlpgq.pgc"
+
+
+       return 0;
+}
diff --git a/src/interfaces/ecpg/test/expected/sql-sqlpgq.stderr b/src/interfaces/ecpg/test/expected/sql-sqlpgq.stderr
new file mode 100644 (file)
index 0000000..503a344
--- /dev/null
@@ -0,0 +1,190 @@
+[NO_PID]: ECPGdebug: set to 1
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGconnect: opening database ecpg1_regression on <DEFAULT> port <DEFAULT>  
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 24: query: create schema graph_ecpg_tests; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 24: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 24: OK: CREATE SCHEMA
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 25: query: set search_path = graph_ecpg_tests; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 25: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 25: OK: SET
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 27: query: create table customers ( customer_id integer primary key , name varchar , address varchar ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 27: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 27: OK: CREATE TABLE
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 33: query: create table orders ( order_id integer primary key , register integer ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 33: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 33: OK: CREATE TABLE
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 38: query: create table customer_orders ( customer_orders_id integer primary key , customer_id integer references customers ( customer_id ) , order_id integer references orders ( order_id ) ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 38: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 38: OK: CREATE TABLE
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 45: query: insert into customers values ( 1 , 'customer1' , 'US' ) , ( 2 , 'customer2' , 'CA' ) , ( 3 , 'customer3' , 'GL' ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 45: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 45: OK: INSERT 0 3
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 46: query: insert into orders values ( 1 , 100 ) , ( 2 , 200 ) , ( 3 , 500 ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 46: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 46: OK: INSERT 0 3
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 47: query: insert into customer_orders ( customer_orders_id , customer_id , order_id ) values ( 1 , 1 , 1 ) , ( 2 , 2 , 2 ) , ( 3 , 3 , 3 ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 47: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 47: OK: INSERT 0 3
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 50: query: create property graph shopgraph vertex tables ( customers , orders ) edge tables ( customer_orders key ( customer_orders_id ) source key ( customer_id ) references customers ( customer_id ) destination key ( order_id ) references orders ( order_id ) ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 50: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 50: OK: CREATE PROPERTY GRAPH
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGtrans on line 61: action "commit"; connection "main"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 64: query: select * from graph_table ( shopgraph match ( c is customers where c . address = 'US' ) - [ is customer_orders ] -> ( o is orders ) columns ( c . name , o . register ) ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 64: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 64: correctly got 1 tuples with 2 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 64: RESULT: customer1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 64: RESULT: 100 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 69: query: select * from graph_table ( shopgraph match ( c is customers where c . address = $1  ) - [ is customer_orders ] -> ( o is orders ) columns ( c . name , o . register ) ); with 1 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 69: using PQexecParams
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_free_params on line 69: parameter 1 = GL
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 69: correctly got 1 tuples with 2 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 69: RESULT: customer3 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 69: RESULT: 500 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: prepare_common on line 74: name graph_stmt; query: "select * from graph_table (shopgraph match (c is customers where c.address = $1)-[is customer_orders]->(o is orders) columns (c.name, o.register))"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 76: query: select * from graph_table (shopgraph match (c is customers where c.address = $1)-[is customer_orders]->(o is orders) columns (c.name, o.register)); with 1 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 76: using PQexecPrepared for "select * from graph_table (shopgraph match (c is customers where c.address = $1)-[is customer_orders]->(o is orders) columns (c.name, o.register))"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_free_params on line 76: parameter 1 = CA
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 76: correctly got 1 tuples with 2 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 76: RESULT: customer2 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 76: RESULT: 200 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: deallocate_one on line 78: name graph_stmt
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 82: query: declare graph_cursor cursor for select * from graph_table ( shopgraph match ( c is customers ) - [ is customer_orders ] -> ( o is orders ) columns ( c . name , o . register ) ) order by name; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 82: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 82: OK: DECLARE CURSOR
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: query: fetch graph_cursor; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 85: correctly got 1 tuples with 2 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 85: RESULT: customer1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 85: RESULT: 100 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: query: fetch graph_cursor; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 85: correctly got 1 tuples with 2 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 85: RESULT: customer2 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 85: RESULT: 200 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: query: fetch graph_cursor; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 85: correctly got 1 tuples with 2 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 85: RESULT: customer3 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 85: RESULT: 500 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: query: fetch graph_cursor; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 85: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 85: correctly got 0 tuples with 2 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: raising sqlcode 100 on line 85: no data found on line 85
+[NO_PID]: sqlca: code: 100, state: 02000
+[NO_PID]: ecpg_execute on line 89: query: close graph_cursor; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 89: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 89: OK: CLOSE CURSOR
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 92: query: select * from graph_table ( shopgraph match ( c is customers | customers where c . address = 'US' ) columns ( c . name ) ); with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 92: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 92: correctly got 1 tuples with 1 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 92: RESULT: customer1 offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 96: query: drop property graph shopgraph; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 96: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 96: OK: DROP PROPERTY GRAPH
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 97: query: drop table customer_orders; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 97: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 97: OK: DROP TABLE
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 98: query: drop table orders; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 98: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 98: OK: DROP TABLE
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 99: query: drop table customers; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 99: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 99: OK: DROP TABLE
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 100: query: drop schema graph_ecpg_tests; with 0 parameter(s) on connection main
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 100: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 100: OK: DROP SCHEMA
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGtrans on line 101: action "commit"; connection "main"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_finish: connection main closed
+[NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/sql-sqlpgq.stdout b/src/interfaces/ecpg/test/expected/sql-sqlpgq.stdout
new file mode 100644 (file)
index 0000000..430a0fa
--- /dev/null
@@ -0,0 +1,7 @@
+found 1 results (customer1, 100)
+found 1 results (customer3, 500)
+found 1 results (customer2, 200)
+cursor result: customer1, 100
+cursor result: customer2, 200
+cursor result: customer3, 500
+found 1 results (customer1)
index 46adc571b482978cfca4e2bb1bd629eea133bad9..b0be3d0c73d907ca69bff945355e4e47c18c4b4b 100644 (file)
@@ -50,5 +50,7 @@
 /sqljson.c
 /sqljson_jsontable
 /sqljson_jsontable.c
+/sqlpgq
+/sqlpgq.c
 /twophase
 /twophase.c
index 3ff190a52336578a26b34a4df87a92cd7eaa9fe4..5296848cda97f86361e253f85003866a56ca19ca 100644 (file)
@@ -25,6 +25,7 @@ TESTS = array array.c \
         show show.c \
         sqljson sqljson.c \
         sqljson_jsontable sqljson_jsontable.c \
+        sqlpgq sqlpgq.c \
         insupd insupd.c \
         twophase twophase.c \
         insupd insupd.c \
index 5a962c47f4bc78a30d849db1e05b08e7425d017c..00d7debe80f536522555bcd0d68796654959a94f 100644 (file)
@@ -27,6 +27,7 @@ pgc_files = [
   'sqlda',
   'sqljson',
   'sqljson_jsontable',
+  'sqlpgq',
   'twophase',
 ]
 
diff --git a/src/interfaces/ecpg/test/sql/sqlpgq.pgc b/src/interfaces/ecpg/test/sql/sqlpgq.pgc
new file mode 100644 (file)
index 0000000..b3c21c6
--- /dev/null
@@ -0,0 +1,105 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+exec sql include ../regression;
+
+exec sql whenever sqlerror sqlprint;
+
+int
+main(void)
+{
+exec sql begin declare section;
+       char command[512];
+       char search_address[10];
+       char cname[100];
+       int reg;
+exec sql end declare section;
+
+       ECPGdebug(1, stderr);
+
+       exec sql connect to REGRESSDB1 as main;
+
+       /* Create schema and tables for property graph testing */
+       exec sql create schema graph_ecpg_tests;
+       exec sql set search_path = graph_ecpg_tests;
+
+       exec sql create table customers (
+               customer_id integer primary key,
+               name varchar,
+               address varchar
+       );
+
+       exec sql create table orders (
+               order_id integer primary key,
+               register integer
+       );
+
+       exec sql create table customer_orders (
+               customer_orders_id integer primary key,
+               customer_id integer references customers (customer_id),
+               order_id integer references orders (order_id)
+       );
+
+       /* Insert test data */
+       exec sql insert into customers values (1, 'customer1', 'US'), (2, 'customer2', 'CA'), (3, 'customer3', 'GL');
+       exec sql insert into orders values (1, 100), (2, 200), (3, 500);
+       exec sql insert into customer_orders (customer_orders_id, customer_id, order_id) values (1, 1, 1), (2, 2, 2), (3, 3, 3);
+
+       /* Create property graph */
+       exec sql create property graph shopgraph
+               vertex tables (
+                       customers,
+                       orders
+               )
+               edge tables (
+                       customer_orders key (customer_orders_id)
+                               source key (customer_id) references customers (customer_id)
+                               destination key (order_id) references orders (order_id)
+               );
+
+       exec sql commit;
+
+       /* direct sql - US customers */
+       exec sql select * into :cname, :reg from graph_table (shopgraph match (c is customers where c.address = 'US')-[is customer_orders]->(o is orders) columns (c.name, o.register));
+       printf("found %ld results (%s, %d)\n", sqlca.sqlerrd[2], cname, reg);
+
+       /* direct sql with C variable - GL customers */
+       strcpy(search_address, "GL");
+       exec sql select * into :cname, :reg from graph_table (shopgraph match (c is customers where c.address = :search_address)-[is customer_orders]->(o is orders) columns (c.name, o.register));
+       printf("found %ld results (%s, %d)\n", sqlca.sqlerrd[2], cname, reg);
+
+       /* prepared statement - CA customers */
+       sprintf(command, "select * from graph_table (shopgraph match (c is customers where c.address = $1)-[is customer_orders]->(o is orders) columns (c.name, o.register))");
+       exec sql prepare graph_stmt from :command;
+       strcpy(search_address, "CA");
+       exec sql execute graph_stmt into :cname, :reg using :search_address;
+       printf("found %ld results (%s, %d)\n", sqlca.sqlerrd[2], cname, reg);
+       exec sql deallocate graph_stmt;
+
+       /* cursor test - all customers with orders */
+       exec sql declare graph_cursor cursor for select * from graph_table (shopgraph match (c is customers)-[is customer_orders]->(o is orders) columns (c.name, o.register)) order by name;
+       exec sql open graph_cursor;
+       exec sql whenever not found do break;
+       while (1) {
+               exec sql fetch graph_cursor into :cname, :reg;
+               printf("cursor result: %s, %d\n", cname, reg);
+       }
+       exec sql whenever not found continue;
+       exec sql close graph_cursor;
+
+       /* label disjunction syntax test */
+       exec sql select * into :cname from graph_table (shopgraph match (c is customers | customers where c.address = 'US') columns (c.name));
+       printf("found %ld results (%s)\n", sqlca.sqlerrd[2], cname);
+
+       /* Clean up */
+       exec sql drop property graph shopgraph;
+       exec sql drop table customer_orders;
+       exec sql drop table orders;
+       exec sql drop table customers;
+       exec sql drop schema graph_ecpg_tests;
+       exec sql commit;
+       exec sql disconnect;
+
+       return 0;
+}
index 23bf33f10a919ffa55fb5f89ba580c6f38ee7114..97d83f4e9b4f297c4ee3c913a3c24e2af542eb60 100644 (file)
@@ -524,6 +524,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
 --
@@ -714,7 +757,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)
@@ -731,6 +774,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
@@ -744,12 +790,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 (file)
index 0000000..ffc263c
--- /dev/null
@@ -0,0 +1,926 @@
+CREATE SCHEMA create_property_graph_tests;
+GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC;
+SET search_path = create_property_graph_tests;
+CREATE SCHEMA create_property_graph_tests_2;
+GRANT USAGE ON SCHEMA create_property_graph_tests_2 TO PUBLIC;
+CREATE ROLE regress_graph_user1;
+CREATE ROLE regress_graph_user2;
+CREATE PROPERTY GRAPH g1;
+COMMENT ON PROPERTY GRAPH g1 IS 'a graph';
+CREATE PROPERTY GRAPH g1;  -- error: duplicate
+ERROR:  relation "g1" already exists
+CREATE TABLE t1 (a int, b text);
+CREATE TABLE t2 (i int PRIMARY KEY, j int, k int);
+CREATE TABLE t3 (x int, y text, z text);
+CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i));
+CREATE TABLE e2 (a int, x int, t text);
+CREATE PROPERTY GRAPH g2
+    VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2)
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+-- test dependencies/object descriptions
+DROP TABLE t1;  -- fail
+ERROR:  cannot drop table t1 because other objects depend on it
+DETAIL:  vertex t1 of property graph g2 depends on table t1
+edge e1 of property graph g2 depends on table t1
+edge e2 of property graph g2 depends on table t1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TABLE t1 DROP COLUMN b;  -- non-key column; fail
+ERROR:  cannot drop column b of table t1 because other objects depend on it
+DETAIL:  property b of label t1 of vertex t1 of property graph g2 depends on column b of table t1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TABLE t1 DROP COLUMN a;  -- key column; fail
+ERROR:  cannot drop column a of table t1 because other objects depend on it
+DETAIL:  vertex t1 of property graph g2 depends on column a of table t1
+edge e1 of property graph g2 depends on column a of table t1
+edge e2 of property graph g2 depends on column a of table t1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+-- like g2 but assembled with ALTER
+CREATE PROPERTY GRAPH g3;
+ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL);
+ALTER PROPERTY GRAPH g3
+    ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1)
+    ADD EDGE TABLES (
+        e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+ALTER PROPERTY GRAPH g3
+    ALTER VERTEX TABLE t3
+        ADD LABEL t3l2 PROPERTIES ALL COLUMNS
+        ADD LABEL t3l3 PROPERTIES ALL COLUMNS;
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x;  -- error
+ERROR:  property graph "g3" element "t3" has no label "t3l3x"
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3;
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2);  -- fail
+ERROR:  cannot drop vertex t2 of property graph g3 because other objects depend on it
+DETAIL:  edge e1 of property graph g3 depends on vertex t2 of property graph g3
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE;
+NOTICE:  drop cascades to edge e1 of property graph g3
+ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2);
+CREATE PROPERTY GRAPH g4
+    VERTEX TABLES (
+        t1 KEY (a) NO PROPERTIES,
+        t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k),
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz)
+    )
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i)
+            PROPERTIES ALL COLUMNS,
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+            PROPERTIES ALL COLUMNS
+    );
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk);
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
+CREATE TABLE t11 (a int PRIMARY KEY);
+CREATE TABLE t12 (b int PRIMARY KEY);
+CREATE TABLE t13 (
+    c int PRIMARY KEY,
+    d int REFERENCES t11,
+    e int REFERENCES t12
+);
+CREATE PROPERTY GRAPH g5
+    VERTEX TABLES (t11, t12)
+    EDGE TABLES (t13 SOURCE t11 DESTINATION t12);
+SELECT pg_get_propgraphdef('g5'::regclass);
+                                                pg_get_propgraphdef                                                
+-------------------------------------------------------------------------------------------------------------------
+ CREATE PROPERTY GRAPH create_property_graph_tests.g5                                                             +
+     VERTEX TABLES (                                                                                              +
+         t11 KEY (a) PROPERTIES (a),                                                                              +
+         t12 KEY (b) PROPERTIES (b)                                                                               +
+     )                                                                                                            +
+     EDGE TABLES (                                                                                                +
+         t13 KEY (c) SOURCE KEY (d) REFERENCES t11 (a) DESTINATION KEY (e) REFERENCES t12 (b) PROPERTIES (c, d, e)+
+     )
+(1 row)
+
+-- error cases
+CREATE UNLOGGED PROPERTY GRAPH gx VERTEX TABLES (xx, yy);
+ERROR:  property graphs cannot be unlogged because they do not have storage
+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)...
+                                                             ^
+ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t3 KEY (x));  -- duplicate alias
+ERROR:  alias "t3" already exists in property graph "g3"
+LINE 1: ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t3 KEY (x));
+                                                   ^
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2
+    );
+ERROR:  source vertex "t1" of edge "e1" does not exist
+LINE 4:         e1 SOURCE t1 DESTINATION t2
+                ^
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION tx
+    );
+ERROR:  destination vertex "tx" of edge "e1" does not exist
+LINE 4:         e1 SOURCE t1 DESTINATION tx
+                ^
+COMMENT ON PROPERTY GRAPH gx IS 'not a graph';
+ERROR:  relation "gx" does not exist
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2)
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2  -- no foreign keys
+    );
+ERROR:  no SOURCE key specified and no suitable foreign key exists for definition of edge "e1"
+LINE 4:         e1 SOURCE t1 DESTINATION t2  -- no foreign keys
+                ^
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a)
+            LABEL foo PROPERTIES (a + 1 AS aa)
+            LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+ERROR:  element "t1" property "aa" expression mismatch: (1 + a) vs. (a + 1)
+DETAIL:  In a property graph element, a property of the same name has to have the same expression in each label.
+ALTER PROPERTY GRAPH g2
+    ADD VERTEX TABLES (
+        t1 AS t1x KEY (a)
+            LABEL foo PROPERTIES (a + 1 AS aa)
+            LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+ERROR:  element "t1x" property "aa" expression mismatch: (1 + a) vs. (a + 1)
+DETAIL:  In a property graph element, a property of the same name has to have the same expression in each label.
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) PROPERTIES (b AS p1),
+        t2 PROPERTIES (k AS p1)  -- type mismatch
+    );
+ERROR:  property "p1" data type mismatch: text vs. integer
+DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE t1 ADD LABEL foo PROPERTIES (b AS k);  -- type mismatch
+ERROR:  property "k" data type mismatch: integer vs. text
+DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+CREATE TABLE t1x (a int, b varchar(10));
+CREATE TABLE t2x (i int, j varchar(15));
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1x KEY (a) PROPERTIES (b AS p1),
+        t2x KEY (i) PROPERTIES (j AS p1)  -- typmod mismatch
+    );
+ERROR:  property "p1" data type mismatch: character varying(10) vs. character varying(15)
+DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1x KEY (a) PROPERTIES (b::varchar(20) AS p1),
+        t2x KEY (i) PROPERTIES (j::varchar(25) AS p1)  -- typmod mismatch
+    );
+ERROR:  property "p1" data type mismatch: character varying(20) vs. character varying(25)
+DETAIL:  In a property graph, a property of the same name has to have the same data type in each label.
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1x KEY (a) PROPERTIES (b::varchar(20) AS p1),
+        t2x KEY (i) PROPERTIES (j::varchar(20) AS p1)  -- matching typmods by casting works
+    );
+DROP PROPERTY GRAPH gx;
+DROP TABLE t1x, t2x;
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS b, k)  -- mismatching number of properties on label
+    );
+ERROR:  mismatching number of properties in definition of label "l1"
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a)  -- mismatching number of properties on label
+    );
+ERROR:  mismatching number of properties in definition of label "l1"
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS j)  -- mismatching property names on label
+    );
+ERROR:  mismatching properties names in definition of label "l1"
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS yy, b AS zz);  -- mismatching number of properties on label
+ERROR:  mismatching number of properties in definition of label "t3l1"
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS zz);  -- mismatching property names on label
+ERROR:  mismatching properties names in definition of label "t3l1"
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x);  -- mismatching number of properties on label
+ERROR:  mismatching number of properties in definition of label "t3l1"
+ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1;
+SET ROLE regress_graph_user1;
+GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
+GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
+ERROR:  invalid privilege type UPDATE for property graph
+RESET ROLE;
+-- collation
+CREATE TABLE tc1 (a int, b text);
+CREATE TABLE tc2 (a int, b text);
+CREATE TABLE tc3 (a int, b text COLLATE "C");
+CREATE TABLE ec1 (ek1 int, ek2 int, eb text);
+CREATE TABLE ec2 (ek1 int, ek2 int, eb text COLLATE "POSIX");
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a), tc3 KEY (a)); -- fail
+ERROR:  property "b" collation mismatch: default vs. C
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+    ); -- fail
+ERROR:  property "eb" collation mismatch: default vs. POSIX
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a) DEFAULT LABEL PROPERTIES (a), tc3 KEY (b))
+    EDGE TABLES (
+        ec2 KEY (ek1, eb)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (eb) REFERENCES tc3 (b)
+    ); -- fail
+ERROR:  collation mismatch in DESTINATION key of edge "ec2": POSIX vs. C
+LINE 4:         ec2 KEY (ek1, eb)
+                ^
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+    );
+ALTER PROPERTY GRAPH gc1 ADD VERTEX TABLES (tc3 KEY (a)); -- fail
+ERROR:  property "b" collation mismatch: default vs. C
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+ALTER PROPERTY GRAPH gc1
+    ADD EDGE TABLES (
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+    ); -- fail
+ERROR:  property "eb" collation mismatch: default vs. POSIX
+DETAIL:  In a property graph, a property of the same name has to have the same collation in each label.
+ALTER PROPERTY GRAPH gc1
+    ADD VERTEX TABLES (
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b COLLATE pg_catalog.DEFAULT AS b)
+    );
+ALTER PROPERTY GRAPH gc1
+    ADD EDGE TABLES (
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb)
+    );
+DROP PROPERTY GRAPH gc1;
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (
+        tc1 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar COLLATE "C" AS b),
+        tc2 KEY (a) DEFAULT LABEL PROPERTIES (a, (b COLLATE "C")::varchar AS b),
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar AS b)
+    )
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb)
+    );
+-- type inconsistency check
+CREATE TABLE v1 (a int primary key, b text);
+CREATE TABLE e(k1 text, k2 text, c text);
+CREATE TABLE v2 (m text, n text);
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2(m)
+    ); -- fail
+ERROR:  no equality operator exists for SOURCE key comparison of edge "e"
+LINE 4:         e KEY (k1, k2)
+                ^
+ALTER TABLE e DROP COLUMN k1, ADD COLUMN k1 bigint primary key;
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2(m)
+    );
+-- information schema
+SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name 
+------------------------+-----------------------------+---------------------
+ regression             | create_property_graph_tests | g1
+ regression             | create_property_graph_tests | g2
+ regression             | create_property_graph_tests | g3
+ regression             | create_property_graph_tests | g4
+ regression             | create_property_graph_tests | g5
+ regression             | create_property_graph_tests | gc1
+ regression             | create_property_graph_tests | gt
+(7 rows)
+
+SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | element_table_kind | table_catalog |        table_schema         | table_name | element_table_definition 
+------------------------+-----------------------------+---------------------+---------------------+--------------------+---------------+-----------------------------+------------+--------------------------
+ regression             | create_property_graph_tests | g2                  | e1                  | EDGE               | regression    | create_property_graph_tests | e1         | 
+ regression             | create_property_graph_tests | g2                  | e2                  | EDGE               | regression    | create_property_graph_tests | e2         | 
+ regression             | create_property_graph_tests | g2                  | t1                  | VERTEX             | regression    | create_property_graph_tests | t1         | 
+ regression             | create_property_graph_tests | g2                  | t2                  | VERTEX             | regression    | create_property_graph_tests | t2         | 
+ regression             | create_property_graph_tests | g2                  | t3                  | VERTEX             | regression    | create_property_graph_tests | t3         | 
+ regression             | create_property_graph_tests | g3                  | t1                  | VERTEX             | regression    | create_property_graph_tests | t1         | 
+ regression             | create_property_graph_tests | g3                  | t3                  | VERTEX             | regression    | create_property_graph_tests | t3         | 
+ regression             | create_property_graph_tests | g4                  | e1                  | EDGE               | regression    | create_property_graph_tests | e1         | 
+ regression             | create_property_graph_tests | g4                  | e2                  | EDGE               | regression    | create_property_graph_tests | e2         | 
+ regression             | create_property_graph_tests | g4                  | t1                  | VERTEX             | regression    | create_property_graph_tests | t1         | 
+ regression             | create_property_graph_tests | g4                  | t2                  | VERTEX             | regression    | create_property_graph_tests | t2         | 
+ regression             | create_property_graph_tests | g4                  | t3                  | VERTEX             | regression    | create_property_graph_tests | t3         | 
+ regression             | create_property_graph_tests | g5                  | t11                 | VERTEX             | regression    | create_property_graph_tests | t11        | 
+ regression             | create_property_graph_tests | g5                  | t12                 | VERTEX             | regression    | create_property_graph_tests | t12        | 
+ regression             | create_property_graph_tests | g5                  | t13                 | EDGE               | regression    | create_property_graph_tests | t13        | 
+ regression             | create_property_graph_tests | gc1                 | ec1                 | EDGE               | regression    | create_property_graph_tests | ec1        | 
+ regression             | create_property_graph_tests | gc1                 | ec2                 | EDGE               | regression    | create_property_graph_tests | ec2        | 
+ regression             | create_property_graph_tests | gc1                 | tc1                 | VERTEX             | regression    | create_property_graph_tests | tc1        | 
+ regression             | create_property_graph_tests | gc1                 | tc2                 | VERTEX             | regression    | create_property_graph_tests | tc2        | 
+ regression             | create_property_graph_tests | gc1                 | tc3                 | VERTEX             | regression    | create_property_graph_tests | tc3        | 
+ regression             | create_property_graph_tests | gt                  | e                   | EDGE               | regression    | create_property_graph_tests | e          | 
+ regression             | create_property_graph_tests | gt                  | v1                  | VERTEX             | regression    | create_property_graph_tests | v1         | 
+ regression             | create_property_graph_tests | gt                  | v2                  | VERTEX             | regression    | create_property_graph_tests | v2         | 
+(23 rows)
+
+SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | column_name | ordinal_position 
+------------------------+-----------------------------+---------------------+---------------------+-------------+------------------
+ regression             | create_property_graph_tests | g2                  | e1                  | a           |                1
+ regression             | create_property_graph_tests | g2                  | e1                  | i           |                2
+ regression             | create_property_graph_tests | g2                  | e2                  | a           |                1
+ regression             | create_property_graph_tests | g2                  | e2                  | x           |                2
+ regression             | create_property_graph_tests | g2                  | t1                  | a           |                1
+ regression             | create_property_graph_tests | g2                  | t2                  | i           |                1
+ regression             | create_property_graph_tests | g2                  | t3                  | x           |                1
+ regression             | create_property_graph_tests | g3                  | t1                  | a           |                1
+ regression             | create_property_graph_tests | g3                  | t3                  | x           |                1
+ regression             | create_property_graph_tests | g4                  | e1                  | a           |                1
+ regression             | create_property_graph_tests | g4                  | e1                  | i           |                2
+ regression             | create_property_graph_tests | g4                  | e2                  | a           |                1
+ regression             | create_property_graph_tests | g4                  | e2                  | x           |                2
+ regression             | create_property_graph_tests | g4                  | t1                  | a           |                1
+ regression             | create_property_graph_tests | g4                  | t2                  | i           |                1
+ regression             | create_property_graph_tests | g4                  | t3                  | x           |                1
+ regression             | create_property_graph_tests | g5                  | t11                 | a           |                1
+ regression             | create_property_graph_tests | g5                  | t12                 | b           |                1
+ regression             | create_property_graph_tests | g5                  | t13                 | c           |                1
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek1         |                1
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek2         |                2
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek1         |                1
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek2         |                2
+ regression             | create_property_graph_tests | gc1                 | tc1                 | a           |                1
+ regression             | create_property_graph_tests | gc1                 | tc2                 | a           |                1
+ regression             | create_property_graph_tests | gc1                 | tc3                 | a           |                1
+ regression             | create_property_graph_tests | gt                  | e                   | k1          |                1
+ regression             | create_property_graph_tests | gt                  | e                   | k2          |                2
+ regression             | create_property_graph_tests | gt                  | v1                  | a           |                1
+ regression             | create_property_graph_tests | gt                  | v2                  | m           |                1
+(30 rows)
+
+SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | edge_table_alias | vertex_table_alias |  edge_end   | edge_table_column_name | vertex_table_column_name | ordinal_position 
+------------------------+-----------------------------+---------------------+------------------+--------------------+-------------+------------------------+--------------------------+------------------
+ regression             | create_property_graph_tests | g2                  | e1               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g2                  | e1               | t2                 | DESTINATION | i                      | i                        |                1
+ regression             | create_property_graph_tests | g2                  | e2               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g2                  | e2               | t3                 | DESTINATION | x                      | x                        |                1
+ regression             | create_property_graph_tests | g2                  | e2               | t3                 | DESTINATION | t                      | y                        |                2
+ regression             | create_property_graph_tests | g4                  | e1               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g4                  | e1               | t2                 | DESTINATION | i                      | i                        |                1
+ regression             | create_property_graph_tests | g4                  | e2               | t1                 | SOURCE      | a                      | a                        |                1
+ regression             | create_property_graph_tests | g4                  | e2               | t3                 | DESTINATION | x                      | x                        |                1
+ regression             | create_property_graph_tests | g4                  | e2               | t3                 | DESTINATION | t                      | y                        |                2
+ regression             | create_property_graph_tests | g5                  | t13              | t11                | SOURCE      | d                      | a                        |                1
+ regression             | create_property_graph_tests | g5                  | t13              | t12                | DESTINATION | e                      | b                        |                1
+ regression             | create_property_graph_tests | gc1                 | ec1              | tc1                | SOURCE      | ek1                    | a                        |                1
+ regression             | create_property_graph_tests | gc1                 | ec1              | tc2                | DESTINATION | ek2                    | a                        |                1
+ regression             | create_property_graph_tests | gc1                 | ec2              | tc1                | SOURCE      | ek1                    | a                        |                1
+ regression             | create_property_graph_tests | gc1                 | ec2              | tc2                | DESTINATION | ek2                    | a                        |                1
+ regression             | create_property_graph_tests | gt                  | e                | v1                 | SOURCE      | k1                     | a                        |                1
+ regression             | create_property_graph_tests | gt                  | e                | v2                 | DESTINATION | k2                     | m                        |                1
+(18 rows)
+
+SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | label_name 
+------------------------+-----------------------------+---------------------+---------------------+------------
+ regression             | create_property_graph_tests | g2                  | e1                  | e1
+ regression             | create_property_graph_tests | g2                  | e2                  | e2
+ regression             | create_property_graph_tests | g2                  | t1                  | t1
+ regression             | create_property_graph_tests | g2                  | t2                  | t2
+ regression             | create_property_graph_tests | g2                  | t3                  | t3l1
+ regression             | create_property_graph_tests | g2                  | t3                  | t3l2
+ regression             | create_property_graph_tests | g3                  | t1                  | t1
+ regression             | create_property_graph_tests | g3                  | t3                  | t3l1
+ regression             | create_property_graph_tests | g3                  | t3                  | t3l2
+ regression             | create_property_graph_tests | g4                  | e1                  | e1
+ regression             | create_property_graph_tests | g4                  | e2                  | e2
+ regression             | create_property_graph_tests | g4                  | t1                  | t1
+ regression             | create_property_graph_tests | g4                  | t2                  | t2
+ regression             | create_property_graph_tests | g4                  | t3                  | t3l1
+ regression             | create_property_graph_tests | g4                  | t3                  | t3l2
+ regression             | create_property_graph_tests | g5                  | t11                 | t11
+ regression             | create_property_graph_tests | g5                  | t12                 | t12
+ regression             | create_property_graph_tests | g5                  | t13                 | t13
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ec1
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ec2
+ regression             | create_property_graph_tests | gc1                 | tc1                 | tc1
+ regression             | create_property_graph_tests | gc1                 | tc2                 | tc2
+ regression             | create_property_graph_tests | gc1                 | tc3                 | tc3
+ regression             | create_property_graph_tests | gt                  | e                   | e
+ regression             | create_property_graph_tests | gt                  | v1                  | v1
+ regression             | create_property_graph_tests | gt                  | v2                  | v2
+(26 rows)
+
+SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | element_table_alias | property_name |         property_expression          
+------------------------+-----------------------------+---------------------+---------------------+---------------+--------------------------------------
+ regression             | create_property_graph_tests | g2                  | e1                  | a             | a
+ regression             | create_property_graph_tests | g2                  | e1                  | i             | i
+ regression             | create_property_graph_tests | g2                  | e1                  | t             | t
+ regression             | create_property_graph_tests | g2                  | e2                  | a             | a
+ regression             | create_property_graph_tests | g2                  | e2                  | t             | t
+ regression             | create_property_graph_tests | g2                  | e2                  | x             | x
+ regression             | create_property_graph_tests | g2                  | t1                  | a             | a
+ regression             | create_property_graph_tests | g2                  | t1                  | b             | b
+ regression             | create_property_graph_tests | g2                  | t2                  | i             | i
+ regression             | create_property_graph_tests | g2                  | t2                  | j             | j
+ regression             | create_property_graph_tests | g2                  | t2                  | k             | k
+ regression             | create_property_graph_tests | g2                  | t3                  | x             | x
+ regression             | create_property_graph_tests | g2                  | t3                  | y             | y
+ regression             | create_property_graph_tests | g2                  | t3                  | z             | z
+ regression             | create_property_graph_tests | g3                  | t1                  | a             | a
+ regression             | create_property_graph_tests | g3                  | t1                  | b             | b
+ regression             | create_property_graph_tests | g3                  | t3                  | x             | x
+ regression             | create_property_graph_tests | g3                  | t3                  | y             | y
+ regression             | create_property_graph_tests | g3                  | t3                  | z             | z
+ regression             | create_property_graph_tests | g4                  | e1                  | a             | a
+ regression             | create_property_graph_tests | g4                  | e1                  | i             | i
+ regression             | create_property_graph_tests | g4                  | e1                  | t             | t
+ regression             | create_property_graph_tests | g4                  | e2                  | a             | a
+ regression             | create_property_graph_tests | g4                  | e2                  | t             | t
+ regression             | create_property_graph_tests | g4                  | e2                  | x             | x
+ regression             | create_property_graph_tests | g4                  | t2                  | i_j           | (i + j)
+ regression             | create_property_graph_tests | g4                  | t2                  | kk            | (k * 2)
+ regression             | create_property_graph_tests | g4                  | t3                  | x             | x
+ regression             | create_property_graph_tests | g4                  | t3                  | yy            | y
+ regression             | create_property_graph_tests | g4                  | t3                  | zz            | z
+ regression             | create_property_graph_tests | g5                  | t11                 | a             | a
+ regression             | create_property_graph_tests | g5                  | t12                 | b             | b
+ regression             | create_property_graph_tests | g5                  | t13                 | c             | c
+ regression             | create_property_graph_tests | g5                  | t13                 | d             | d
+ regression             | create_property_graph_tests | g5                  | t13                 | e             | e
+ regression             | create_property_graph_tests | gc1                 | ec1                 | eb            | eb
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek1           | ek1
+ regression             | create_property_graph_tests | gc1                 | ec1                 | ek2           | ek2
+ regression             | create_property_graph_tests | gc1                 | ec2                 | eb            | (eb COLLATE "default")
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek1           | ek1
+ regression             | create_property_graph_tests | gc1                 | ec2                 | ek2           | ek2
+ regression             | create_property_graph_tests | gc1                 | tc1                 | a             | a
+ regression             | create_property_graph_tests | gc1                 | tc1                 | b             | ((b)::character varying COLLATE "C")
+ regression             | create_property_graph_tests | gc1                 | tc2                 | a             | a
+ regression             | create_property_graph_tests | gc1                 | tc2                 | b             | ((b)::character varying COLLATE "C")
+ regression             | create_property_graph_tests | gc1                 | tc3                 | a             | a
+ regression             | create_property_graph_tests | gc1                 | tc3                 | b             | (b)::character varying
+ regression             | create_property_graph_tests | gt                  | e                   | c             | c
+ regression             | create_property_graph_tests | gt                  | e                   | k1            | k1
+ regression             | create_property_graph_tests | gt                  | e                   | k2            | k2
+ regression             | create_property_graph_tests | gt                  | v1                  | a             | a
+ regression             | create_property_graph_tests | gt                  | v1                  | b             | b
+ regression             | create_property_graph_tests | gt                  | v2                  | m             | m
+ regression             | create_property_graph_tests | gt                  | v2                  | n             | n
+(54 rows)
+
+SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | label_name | property_name 
+------------------------+-----------------------------+---------------------+------------+---------------
+ regression             | create_property_graph_tests | g2                  | e1         | a
+ regression             | create_property_graph_tests | g2                  | e1         | i
+ regression             | create_property_graph_tests | g2                  | e1         | t
+ regression             | create_property_graph_tests | g2                  | e2         | a
+ regression             | create_property_graph_tests | g2                  | e2         | t
+ regression             | create_property_graph_tests | g2                  | e2         | x
+ regression             | create_property_graph_tests | g2                  | t1         | a
+ regression             | create_property_graph_tests | g2                  | t1         | b
+ regression             | create_property_graph_tests | g2                  | t2         | i
+ regression             | create_property_graph_tests | g2                  | t2         | j
+ regression             | create_property_graph_tests | g2                  | t2         | k
+ regression             | create_property_graph_tests | g2                  | t3l1       | x
+ regression             | create_property_graph_tests | g2                  | t3l1       | y
+ regression             | create_property_graph_tests | g2                  | t3l1       | z
+ regression             | create_property_graph_tests | g2                  | t3l2       | x
+ regression             | create_property_graph_tests | g2                  | t3l2       | y
+ regression             | create_property_graph_tests | g2                  | t3l2       | z
+ regression             | create_property_graph_tests | g3                  | t1         | a
+ regression             | create_property_graph_tests | g3                  | t1         | b
+ regression             | create_property_graph_tests | g3                  | t3l1       | x
+ regression             | create_property_graph_tests | g3                  | t3l1       | y
+ regression             | create_property_graph_tests | g3                  | t3l1       | z
+ regression             | create_property_graph_tests | g3                  | t3l2       | x
+ regression             | create_property_graph_tests | g3                  | t3l2       | y
+ regression             | create_property_graph_tests | g3                  | t3l2       | z
+ regression             | create_property_graph_tests | g4                  | e1         | a
+ regression             | create_property_graph_tests | g4                  | e1         | i
+ regression             | create_property_graph_tests | g4                  | e1         | t
+ regression             | create_property_graph_tests | g4                  | e2         | a
+ regression             | create_property_graph_tests | g4                  | e2         | t
+ regression             | create_property_graph_tests | g4                  | e2         | x
+ regression             | create_property_graph_tests | g4                  | t2         | i_j
+ regression             | create_property_graph_tests | g4                  | t2         | kk
+ regression             | create_property_graph_tests | g4                  | t3l1       | x
+ regression             | create_property_graph_tests | g4                  | t3l1       | yy
+ regression             | create_property_graph_tests | g4                  | t3l2       | x
+ regression             | create_property_graph_tests | g4                  | t3l2       | zz
+ regression             | create_property_graph_tests | g5                  | t11        | a
+ regression             | create_property_graph_tests | g5                  | t12        | b
+ regression             | create_property_graph_tests | g5                  | t13        | c
+ regression             | create_property_graph_tests | g5                  | t13        | d
+ regression             | create_property_graph_tests | g5                  | t13        | e
+ regression             | create_property_graph_tests | gc1                 | ec1        | eb
+ regression             | create_property_graph_tests | gc1                 | ec1        | ek1
+ regression             | create_property_graph_tests | gc1                 | ec1        | ek2
+ regression             | create_property_graph_tests | gc1                 | ec2        | eb
+ regression             | create_property_graph_tests | gc1                 | ec2        | ek1
+ regression             | create_property_graph_tests | gc1                 | ec2        | ek2
+ regression             | create_property_graph_tests | gc1                 | tc1        | a
+ regression             | create_property_graph_tests | gc1                 | tc1        | b
+ regression             | create_property_graph_tests | gc1                 | tc2        | a
+ regression             | create_property_graph_tests | gc1                 | tc2        | b
+ regression             | create_property_graph_tests | gc1                 | tc3        | a
+ regression             | create_property_graph_tests | gc1                 | tc3        | b
+ regression             | create_property_graph_tests | gt                  | e          | c
+ regression             | create_property_graph_tests | gt                  | e          | k1
+ regression             | create_property_graph_tests | gt                  | e          | k2
+ regression             | create_property_graph_tests | gt                  | v1         | a
+ regression             | create_property_graph_tests | gt                  | v1         | b
+ regression             | create_property_graph_tests | gt                  | v2         | m
+ regression             | create_property_graph_tests | gt                  | v2         | n
+(61 rows)
+
+SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | label_name 
+------------------------+-----------------------------+---------------------+------------
+ regression             | create_property_graph_tests | g2                  | e1
+ regression             | create_property_graph_tests | g2                  | e2
+ regression             | create_property_graph_tests | g2                  | t1
+ regression             | create_property_graph_tests | g2                  | t2
+ regression             | create_property_graph_tests | g2                  | t3l1
+ regression             | create_property_graph_tests | g2                  | t3l2
+ regression             | create_property_graph_tests | g3                  | t1
+ regression             | create_property_graph_tests | g3                  | t3l1
+ regression             | create_property_graph_tests | g3                  | t3l2
+ regression             | create_property_graph_tests | g4                  | e1
+ regression             | create_property_graph_tests | g4                  | e2
+ regression             | create_property_graph_tests | g4                  | t1
+ regression             | create_property_graph_tests | g4                  | t2
+ regression             | create_property_graph_tests | g4                  | t3l1
+ regression             | create_property_graph_tests | g4                  | t3l2
+ regression             | create_property_graph_tests | g5                  | t11
+ regression             | create_property_graph_tests | g5                  | t12
+ regression             | create_property_graph_tests | g5                  | t13
+ regression             | create_property_graph_tests | gc1                 | ec1
+ regression             | create_property_graph_tests | gc1                 | ec2
+ regression             | create_property_graph_tests | gc1                 | tc1
+ regression             | create_property_graph_tests | gc1                 | tc2
+ regression             | create_property_graph_tests | gc1                 | tc3
+ regression             | create_property_graph_tests | gt                  | e
+ regression             | create_property_graph_tests | gt                  | v1
+ regression             | create_property_graph_tests | gt                  | v2
+(26 rows)
+
+SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name;
+ property_graph_catalog |    property_graph_schema    | property_graph_name | property_name |     data_type     | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier 
+------------------------+-----------------------------+---------------------+---------------+-------------------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+---------------------------+--------------------------+------------------------+---------------+--------------+------------+---------------------+----------------
+ regression             | create_property_graph_tests | g2                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g2                  | b             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g2                  | i             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
+ regression             | create_property_graph_tests | g2                  | j             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | j
+ regression             | create_property_graph_tests | g2                  | k             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | k
+ regression             | create_property_graph_tests | g2                  | t             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
+ regression             | create_property_graph_tests | g2                  | x             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g2                  | y             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
+ regression             | create_property_graph_tests | g2                  | z             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
+ regression             | create_property_graph_tests | g3                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g3                  | b             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g3                  | x             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g3                  | y             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | y
+ regression             | create_property_graph_tests | g3                  | z             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | z
+ regression             | create_property_graph_tests | g4                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g4                  | i             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i
+ regression             | create_property_graph_tests | g4                  | i_j           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | i_j
+ regression             | create_property_graph_tests | g4                  | kk            | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | kk
+ regression             | create_property_graph_tests | g4                  | t             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | t
+ regression             | create_property_graph_tests | g4                  | x             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | x
+ regression             | create_property_graph_tests | g4                  | yy            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | yy
+ regression             | create_property_graph_tests | g4                  | zz            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | zz
+ regression             | create_property_graph_tests | g5                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | g5                  | b             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | g5                  | c             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | c
+ regression             | create_property_graph_tests | g5                  | d             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | d
+ regression             | create_property_graph_tests | g5                  | e             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | e
+ regression             | create_property_graph_tests | gc1                 | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | gc1                 | b             | character varying |                          |                        |                       |                      |                    | regression        | pg_catalog       | C              |                   |                         |               |                    |               |                    | regression                | pg_catalog               | varchar                |               |              |            |                     | b
+ regression             | create_property_graph_tests | gc1                 | eb            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | eb
+ regression             | create_property_graph_tests | gc1                 | ek1           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | ek1
+ regression             | create_property_graph_tests | gc1                 | ek2           | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | ek2
+ regression             | create_property_graph_tests | gt                  | a             | integer           |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int4                   |               |              |            |                     | a
+ regression             | create_property_graph_tests | gt                  | b             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | b
+ regression             | create_property_graph_tests | gt                  | c             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | c
+ regression             | create_property_graph_tests | gt                  | k1            | bigint            |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | int8                   |               |              |            |                     | k1
+ regression             | create_property_graph_tests | gt                  | k2            | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | k2
+ regression             | create_property_graph_tests | gt                  | m             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | m
+ regression             | create_property_graph_tests | gt                  | n             | text              |                          |                        |                       |                      |                    | regression        |                  |                |                   |                         |               |                    |               |                    | regression                | pg_catalog               | text                   |               |              |            |                     | n
+(39 rows)
+
+SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name;
+       grantor       |       grantee       | property_graph_catalog |    property_graph_schema    | property_graph_name | privilege_type | is_grantable 
+---------------------+---------------------+------------------------+-----------------------------+---------------------+----------------+--------------
+ 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)
+
+-- test object address functions
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as reference_graph
+    FROM pg_depend
+    WHERE refclassid = 'pg_class'::regclass AND
+          refobjid = 'create_property_graph_tests.g2'::regclass
+    ORDER BY 1, 2;
+               obj               |  reference_graph  
+---------------------------------+-------------------
+ edge e1 of property graph g2    | property graph g2
+ edge e2 of property graph g2    | property graph g2
+ label e1 of property graph g2   | property graph g2
+ label e2 of property graph g2   | property graph g2
+ label t1 of property graph g2   | property graph g2
+ label t2 of property graph g2   | property graph g2
+ label t3l1 of property graph g2 | property graph g2
+ label t3l2 of property graph g2 | property graph g2
+ property a of property graph g2 | property graph g2
+ property b of property graph g2 | property graph g2
+ property i of property graph g2 | property graph g2
+ property j of property graph g2 | property graph g2
+ property k of property graph g2 | property graph g2
+ property t of property graph g2 | property graph g2
+ property x of property graph g2 | property graph g2
+ property y of property graph g2 | property graph g2
+ property z of property graph g2 | property graph g2
+ type g2                         | property graph g2
+ vertex t1 of property graph g2  | property graph g2
+ vertex t2 of property graph g2  | property graph g2
+ vertex t3 of property graph g2  | property graph g2
+(21 rows)
+
+SELECT (pg_identify_object_as_address(classid, objid, objsubid)).*
+    FROM pg_depend
+    WHERE refclassid = 'pg_class'::regclass AND
+          refobjid = 'create_property_graph_tests.g2'::regclass
+    ORDER BY 1, 2, 3;
+          type           |             object_names              | object_args 
+-------------------------+---------------------------------------+-------------
+ property graph element  | {create_property_graph_tests,g2,e1}   | {}
+ property graph element  | {create_property_graph_tests,g2,e2}   | {}
+ property graph element  | {create_property_graph_tests,g2,t1}   | {}
+ property graph element  | {create_property_graph_tests,g2,t2}   | {}
+ property graph element  | {create_property_graph_tests,g2,t3}   | {}
+ property graph label    | {create_property_graph_tests,g2,e1}   | {}
+ property graph label    | {create_property_graph_tests,g2,e2}   | {}
+ property graph label    | {create_property_graph_tests,g2,t1}   | {}
+ property graph label    | {create_property_graph_tests,g2,t2}   | {}
+ property graph label    | {create_property_graph_tests,g2,t3l1} | {}
+ property graph label    | {create_property_graph_tests,g2,t3l2} | {}
+ property graph property | {create_property_graph_tests,g2,a}    | {}
+ property graph property | {create_property_graph_tests,g2,b}    | {}
+ property graph property | {create_property_graph_tests,g2,i}    | {}
+ property graph property | {create_property_graph_tests,g2,j}    | {}
+ property graph property | {create_property_graph_tests,g2,k}    | {}
+ property graph property | {create_property_graph_tests,g2,t}    | {}
+ property graph property | {create_property_graph_tests,g2,x}    | {}
+ property graph property | {create_property_graph_tests,g2,y}    | {}
+ property graph property | {create_property_graph_tests,g2,z}    | {}
+ type                    | {create_property_graph_tests.g2}      | {}
+(21 rows)
+
+SELECT (pg_identify_object(classid, objid, objsubid)).*
+    FROM pg_depend
+    WHERE refclassid = 'pg_class'::regclass AND
+          refobjid = 'create_property_graph_tests.g2'::regclass
+    ORDER BY 1, 2, 3, 4;
+          type           |           schema            | name |                identity                
+-------------------------+-----------------------------+------+----------------------------------------
+ property graph element  |                             |      | e1 of create_property_graph_tests.g2
+ property graph element  |                             |      | e2 of create_property_graph_tests.g2
+ property graph element  |                             |      | t1 of create_property_graph_tests.g2
+ property graph element  |                             |      | t2 of create_property_graph_tests.g2
+ property graph element  |                             |      | t3 of create_property_graph_tests.g2
+ property graph label    |                             |      | e1 of create_property_graph_tests.g2
+ property graph label    |                             |      | e2 of create_property_graph_tests.g2
+ property graph label    |                             |      | t1 of create_property_graph_tests.g2
+ property graph label    |                             |      | t2 of create_property_graph_tests.g2
+ property graph label    |                             |      | t3l1 of create_property_graph_tests.g2
+ property graph label    |                             |      | t3l2 of create_property_graph_tests.g2
+ property graph property |                             |      | a of create_property_graph_tests.g2
+ property graph property |                             |      | b of create_property_graph_tests.g2
+ property graph property |                             |      | i of create_property_graph_tests.g2
+ property graph property |                             |      | j of create_property_graph_tests.g2
+ property graph property |                             |      | k of create_property_graph_tests.g2
+ property graph property |                             |      | t of create_property_graph_tests.g2
+ property graph property |                             |      | x of create_property_graph_tests.g2
+ property graph property |                             |      | y of create_property_graph_tests.g2
+ property graph property |                             |      | z of create_property_graph_tests.g2
+ type                    | create_property_graph_tests | g2   | create_property_graph_tests.g2
+(21 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
+-- Test \d variants for property graphs
+\dG g1
+                          List of property graphs
+           Schema            | Name |      Type      |        Owner        
+-----------------------------+------+----------------+---------------------
+ create_property_graph_tests | g1   | property graph | regress_graph_user1
+(1 row)
+
+\dG+ g1
+                                             List of property graphs
+           Schema            | Name |      Type      |        Owner        | Persistence |  Size   | Description 
+-----------------------------+------+----------------+---------------------+-------------+---------+-------------
+ create_property_graph_tests | g1   | property graph | regress_graph_user1 | permanent   | 0 bytes | a graph
+(1 row)
+
+\dGx g1
+List of property graphs
+-[ RECORD 1 ]-----------------------
+Schema | create_property_graph_tests
+Name   | g1
+Type   | property graph
+Owner  | regress_graph_user1
+
+\d g2
+                                Property Graph "create_property_graph_tests.g2"
+ Element Alias |         Element Table          | Element Kind | Source Vertex Alias | Destination Vertex Alias 
+---------------+--------------------------------+--------------+---------------------+--------------------------
+ e1            | create_property_graph_tests.e1 | edge         | t1                  | t2
+ e2            | create_property_graph_tests.e2 | edge         | t1                  | t3
+ t1            | create_property_graph_tests.t1 | vertex       |                     | 
+ t2            | create_property_graph_tests.t2 | vertex       |                     | 
+ t3            | create_property_graph_tests.t3 | vertex       |                     | 
+
+\d g1
+                        Property Graph "create_property_graph_tests.g1"
+ Element Alias | Element Table | Element Kind | Source Vertex Alias | Destination Vertex Alias 
+---------------+---------------+--------------+---------------------+--------------------------
+
+\d+ g2
+                                Property Graph "create_property_graph_tests.g2"
+ Element Alias |         Element Table          | Element Kind | Source Vertex Alias | Destination Vertex Alias 
+---------------+--------------------------------+--------------+---------------------+--------------------------
+ e1            | create_property_graph_tests.e1 | edge         | t1                  | t2
+ e2            | create_property_graph_tests.e2 | edge         | t1                  | t3
+ t1            | create_property_graph_tests.t1 | vertex       |                     | 
+ t2            | create_property_graph_tests.t2 | vertex       |                     | 
+ t3            | create_property_graph_tests.t3 | vertex       |                     | 
+Property graph definition:
+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)
+    )
+
+\d+ g1
+                        Property Graph "create_property_graph_tests.g1"
+ Element Alias | Element Table | Element Kind | Source Vertex Alias | Destination Vertex Alias 
+---------------+---------------+--------------+---------------------+--------------------------
+Property graph definition:
+CREATE PROPERTY GRAPH create_property_graph_tests.g1
+
+\dG g_nonexistent
+   List of property graphs
+ Schema | Name | Type | Owner 
+--------+------+------+-------
+(0 rows)
+
+\dG t11
+   List of property graphs
+ Schema | Name | Type | Owner 
+--------+------+------+-------
+(0 rows)
+
+\set QUIET 'off'
+\dG g_nonexistent
+Did not find any property graphs named "g_nonexistent".
+\set QUIET 'on'
+-- temporary property graph
+-- Keep this at the end to avoid test failure due to changing temporary
+-- namespace names in information schema query outputs
+CREATE TEMPORARY PROPERTY GRAPH g1; -- same name as persistent graph
+DROP PROPERTY GRAPH g1;  -- drops temporary graph retaining persistent graph
+\dG g1
+                          List of property graphs
+           Schema            | Name |      Type      |        Owner        
+-----------------------------+------+----------------+---------------------
+ create_property_graph_tests | g1   | property graph | regress_graph_user1
+(1 row)
+
+CREATE TEMPORARY TABLE v2tmp (m text, n text);
+CREATE TEMPORARY PROPERTY GRAPH gtmp
+    VERTEX TABLES (v1 KEY (a), v2tmp KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2tmp(m)
+    );
+DROP PROPERTY GRAPH gtmp;
+CREATE PROPERTY GRAPH gtmp
+    VERTEX TABLES (v1 KEY (a), v2tmp KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2tmp(m)
+    );
+NOTICE:  property graph "gtmp" will be temporary
+ALTER PROPERTY GRAPH g1
+    ADD VERTEX TABLES (v2tmp KEY (m));  -- error
+ERROR:  cannot add temporary element table to non-temporary property graph
+LINE 2:     ADD VERTEX TABLES (v2tmp KEY (m));
+                               ^
+DETAIL:  Table "v2tmp" is a temporary table.
+-- DROP, ALTER SET SCHEMA, ALTER PROPERTY GRAPH RENAME TO
+DROP TABLE g2;  -- error: wrong object type
+ERROR:  "g2" is not a table
+HINT:  Use DROP PROPERTY GRAPH to remove a property graph.
+CREATE VIEW vg1 AS SELECT * FROM GRAPH_TABLE(g1 MATCH () COLUMNS (1 AS one));
+DROP PROPERTY GRAPH g1; -- error
+ERROR:  cannot drop property graph g1 because other objects depend on it
+DETAIL:  view vg1 depends on property graph g1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER PROPERTY GRAPH g1 SET SCHEMA create_property_graph_tests_2;
+ALTER PROPERTY GRAPH create_property_graph_tests_2.g1 RENAME TO g2;
+DROP PROPERTY GRAPH create_property_graph_tests_2.g2 CASCADE;
+NOTICE:  drop cascades to view vg1
+DROP PROPERTY GRAPH g1;  -- error
+ERROR:  property graph "g1" does not exist
+ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (t1 KEY (a));  -- error
+ERROR:  relation "g1" does not exist
+ALTER PROPERTY GRAPH IF EXISTS g1 SET SCHEMA create_property_graph_tests_2;
+NOTICE:  relation "g1" does not exist, skipping
+DROP PROPERTY GRAPH IF EXISTS g1;
+NOTICE:  property graph "g1" does not exist, skipping
+DROP ROLE regress_graph_user1, regress_graph_user2;
+-- leave remaining objects behind for pg_upgrade/pg_dump tests
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
new file mode 100644 (file)
index 0000000..3043a73
--- /dev/null
@@ -0,0 +1,976 @@
+CREATE SCHEMA graph_table_tests;
+GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC;
+SET search_path = graph_table_tests;
+CREATE TABLE products (
+    product_no integer PRIMARY KEY,
+    name varchar,
+    price numeric
+);
+CREATE TABLE customers (
+    customer_id integer PRIMARY KEY,
+    name varchar,
+    address varchar
+);
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    ordered_when date
+);
+CREATE TABLE order_items (
+    order_items_id integer PRIMARY KEY,
+    order_id integer REFERENCES orders (order_id),
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+CREATE TABLE customer_orders (
+    customer_orders_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    order_id integer REFERENCES orders (order_id)
+);
+CREATE TABLE wishlists (
+    wishlist_id integer PRIMARY KEY,
+    wishlist_name varchar
+);
+CREATE TABLE wishlist_items (
+    wishlist_items_id integer PRIMARY KEY,
+    wishlist_id integer REFERENCES wishlists (wishlist_id),
+    product_no integer REFERENCES products (product_no)
+);
+CREATE TABLE customer_wishlists (
+    customer_wishlist_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    wishlist_id integer REFERENCES wishlists (wishlist_id)
+);
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products,
+        customers,
+        orders
+           DEFAULT LABEL
+           LABEL lists PROPERTIES (order_id AS node_id, 'order'::varchar(10) AS list_type),
+        wishlists
+           DEFAULT LABEL
+           LABEL lists PROPERTIES (wishlist_id AS node_id, 'wishlist'::varchar(10) AS list_type)
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (order_id AS link_id, product_no),
+        wishlist_items KEY (wishlist_items_id)
+            SOURCE KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (wishlist_id AS link_id, product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, order_id AS link_id),
+        customer_wishlists KEY (customer_wishlist_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, wishlist_id AS link_id)
+    );
+SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  relation "xxx" does not exist
+LINE 1: SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS custo...
+                                               ^
+SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  "pg_class" is not a property graph
+LINE 1: SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS ...
+                                               ^
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name));  -- error
+ERROR:  missing FROM-clause entry for table "cx"
+LINE 1: ...US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS...
+                                                             ^
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name));  -- error
+ERROR:  property "namex" does not exist
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers|employees WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  label "employees" does not exist in property graph "myshop"
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c.name AS customer_name));  -- error
+ERROR:  syntax error at or near "COLUMNS"
+LINE 1: ...mers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c...
+                                                             ^
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers), (o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  multiple path patterns in one GRAPH_TABLE clause not supported
+SELECT * FROM GRAPH_TABLE (myshop MATCH COLUMNS (1 AS col));  -- error, empty match clause
+ERROR:  syntax error at or near "COLUMNS"
+LINE 1: SELECT * FROM GRAPH_TABLE (myshop MATCH COLUMNS (1 AS col));
+                                                ^
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)->{1,2}(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+ERROR:  element pattern quantifier is not supported
+SELECT * FROM GRAPH_TABLE (myshop MATCH ((c IS customers)->(o IS orders)) COLUMNS (c.name));
+ERROR:  unsupported element pattern kind: "nested path pattern"
+-- a property graph can be referenced only from within GRAPH_TABLE clause.
+SELECT * FROM myshop; -- error
+ERROR:  cannot open relation "myshop"
+LINE 1: SELECT * FROM myshop;
+                      ^
+DETAIL:  This operation is not supported for property graphs.
+COPY myshop TO stdout; -- error
+ERROR:  cannot open relation "myshop"
+DETAIL:  This operation is not supported for property graphs.
+INSERT INTO myshop VALUES (1); -- error
+ERROR:  cannot open relation "myshop"
+LINE 1: INSERT INTO myshop VALUES (1);
+                    ^
+DETAIL:  This operation is not supported for property graphs.
+INSERT INTO products VALUES
+    (1, 'product1', 10),
+    (2, 'product2', 20),
+    (3, 'product3', 30);
+INSERT INTO customers VALUES
+    (1, 'customer1', 'US'),
+    (2, 'customer2', 'CA'),
+    (3, 'customer3', 'GL');
+INSERT INTO orders VALUES
+    (1, date '2024-01-01'),
+    (2, date '2024-01-02'),
+    (3, date '2024-01-03');
+INSERT INTO wishlists VALUES
+    (1, 'wishlist1'),
+    (2, 'wishlist2'),
+    (3, 'wishlist3');
+INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES
+    (1, 1, 1, 5),
+    (2, 1, 2, 10),
+    (3, 2, 1, 7);
+INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES
+    (1, 1, 1),
+    (2, 2, 2);
+INSERT INTO customer_wishlists (customer_wishlist_id, customer_id, wishlist_id) VALUES
+    (1, 2, 3),
+    (2, 3, 1),
+    (3, 3, 2);
+INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES
+    (1, 1, 2),
+    (2, 1, 3),
+    (3, 2, 1),
+    (4, 3, 1);
+-- single element path pattern
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
+   name    
+-----------
+ customer1
+ customer2
+ customer3
+(3 rows)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
+   name    
+-----------
+ customer1
+(1 row)
+
+-- graph element specification without label or variable
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[]->(o IS orders) COLUMNS (c.name AS customer_name));
+ customer_name 
+---------------
+ customer1
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[co IS customer_orders]->(o IS orders WHERE o.ordered_when = date '2024-01-02') COLUMNS (c.name, c.address));
+   name    | address 
+-----------+---------
+ customer2 | CA
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when));
+ name | ordered_when 
+------+--------------
+(0 rows)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when));
+   name    | ordered_when 
+-----------+--------------
+ customer1 | 01-01-2024
+ customer2 | 01-02-2024
+(2 rows)
+
+-- spaces around pattern operators
+SELECT * FROM GRAPH_TABLE (myshop MATCH ( o IS orders ) <- [ IS customer_orders ] - (c IS customers) COLUMNS ( c.name, o.ordered_when));
+   name    | ordered_when 
+-----------+--------------
+ customer1 | 01-01-2024
+ customer2 | 01-02-2024
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS cust_lists]->(l IS lists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, l.list_type)) ORDER BY customer_name, product_name, list_type;
+ customer_name | product_name | list_type 
+---------------+--------------+-----------
+ customer1     | product1     | order
+ customer1     | product2     | order
+ customer2     | product1     | order
+ customer2     | product1     | wishlist
+ customer3     | product1     | wishlist
+ customer3     | product2     | wishlist
+ customer3     | product3     | wishlist
+(7 rows)
+
+-- label disjunction
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name)) ORDER BY customer_name, product_name;
+ customer_name | product_name 
+---------------+--------------
+ customer1     | product1
+ customer1     | product2
+ customer2     | product1
+ customer2     | product1
+ customer3     | product1
+ customer3     | product2
+ customer3     | product3
+(7 rows)
+
+-- property not associated with labels queried results in error
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, l.list_type)) ORDER BY 1, 2, 3;
+ERROR:  property "list_type" for element variable "l" not found
+-- vertex to vertex connection abbreviation
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1;
+   name    | ordered_when 
+-----------+--------------
+ customer1 | 01-01-2024
+ customer2 | 01-02-2024
+(2 rows)
+
+-- lateral test
+CREATE TABLE x1 (a int, b text);
+INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
+ a |  b  | customer_name | cid 
+---+-----+---------------+-----
+ 1 | one | customer1     |   1
+(1 row)
+
+DROP TABLE x1;
+CREATE TABLE v1 (
+    id int PRIMARY KEY,
+    vname varchar(10),
+    vprop1 int,
+    vprop2 int
+);
+CREATE TABLE v2 (
+    id1 int,
+    id2 int,
+    vname varchar(10),
+    vprop1 int,
+    vprop2 int
+);
+CREATE TABLE v3 (
+    id int PRIMARY KEY,
+    vname varchar(10),
+    vprop1 int,
+    vprop2 int
+);
+-- edge connecting v1 and v2
+CREATE TABLE e1_2 (
+    id_1 int,
+    id_2_1 int,
+    id_2_2 int,
+    ename varchar(10),
+    eprop1 int
+);
+-- edge connecting v1 and v3
+CREATE TABLE e1_3 (
+    id_1 int,
+    id_3 int,
+    ename varchar(10),
+    eprop1 int,
+    PRIMARY KEY (id_1, id_3)
+);
+CREATE TABLE e2_3 (
+    id_2_1 int,
+    id_2_2 int,
+    id_3 int,
+    ename varchar(10),
+    eprop1 int
+);
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1
+            LABEL vl1 PROPERTIES (vname, vprop1)
+            LABEL l1 PROPERTIES (vname AS elname), -- label shared by vertexes as well as edges
+        v2 KEY (id1, id2)
+            LABEL vl2 PROPERTIES (vname, vprop2, 'vl2_prop'::varchar(10) AS lprop1)
+            LABEL vl3 PROPERTIES (vname, vprop1, 'vl2_prop'::varchar(10) AS lprop1)
+            LABEL l1 PROPERTIES (vname AS elname),
+        v3
+            LABEL vl3 PROPERTIES (vname, vprop1, 'vl3_prop'::varchar(10) AS lprop1)
+            LABEL l1 PROPERTIES (vname AS elname)
+    )
+    -- edges with differing number of columns in destination keys
+    EDGE TABLES (
+        e1_2 key (id_1, id_2_1, id_2_2)
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL el1 PROPERTIES (eprop1, ename)
+            LABEL l1 PROPERTIES (ename AS elname),
+        e1_3
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            -- order of property names doesn't matter
+            LABEL el1 PROPERTIES (ename, eprop1)
+            LABEL l1 PROPERTIES (ename AS elname),
+        e2_3 key (id_2_1, id_2_2, id_3)
+            SOURCE KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            -- new property lprop2 not shared by el1
+            -- does not share eprop1 from by el1
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+INSERT INTO v1 VALUES
+    (1, 'v11', 10, 100),
+    (2, 'v12', 20, 200),
+    (3, 'v13', 30, 300);
+INSERT INTO v2 VALUES
+    (1000, 1, 'v21', 1010, 1100),
+    (1000, 2, 'v22', 1020, 1200),
+    (1000, 3, 'v23', 1030, 1300);
+INSERT INTO v3 VALUES
+    (2001, 'v31', 2010, 2100),
+    (2002, 'v32', 2020, 2200),
+    (2003, 'v33', 2030, 2300);
+INSERT INTO e1_2 VALUES
+    (1, 1000, 2, 'e121', 10001),
+    (2, 1000, 1, 'e122', 10002);
+INSERT INTO e1_3 VALUES
+    (1, 2003, 'e131', 10003),
+    (1, 2001, 'e132', 10004);
+INSERT INTO e2_3 VALUES (1000, 2, 2002, 'e231', 10005);
+-- empty element path pattern, counts number of edges in the graph
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 AS one));
+ count 
+-------
+     5
+(1 row)
+
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()->() COLUMNS (1 AS one));
+ count 
+-------
+     5
+(1 row)
+
+-- Project property associated with a label specified in the graph pattern even
+-- if it is defined for a graph element through a different label. (Refer
+-- section 6.5 of SQL/PGQ standard). For example, vprop1 in the query below. It
+-- is defined on v2 through label vl3, but gets exposed in the query through
+-- label vl1 which is not associated with v2. v2, in turn, is included because
+-- of label vl2.
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname, a.vprop1));
+ vname | vprop1 
+-------+--------
+ v11   |     10
+ v12   |     20
+ v13   |     30
+ v21   |   1010
+ v22   |   1020
+ v23   |   1030
+(6 rows)
+
+-- vprop2 is associated with vl2 but not vl3
+SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, b.ename AS conn, c.vname AS dest, c.lprop1, c.vprop2, c.vprop1));
+ src | conn | dest |  lprop1  | vprop2 | vprop1 
+-----+------+------+----------+--------+--------
+ v12 | e122 | v21  | vl2_prop |   1100 |   1010
+ v11 | e121 | v22  | vl2_prop |   1200 |   1020
+ v11 | e131 | v33  | vl3_prop |        |   2030
+ v11 | e132 | v31  | vl3_prop |        |   2010
+(4 rows)
+
+-- edges directed in both ways - to and from v2
+SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-[conn]-(v2) COLUMNS (v1.vname AS v1name, conn.ename AS cname, v2.vname AS v2name));
+ v1name | cname | v2name 
+--------+-------+--------
+ v21    | e122  | v12
+ v22    | e121  | v11
+ v22    | e231  | v32
+(3 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name, v2.vname AS v2name));
+ v1name | v2name 
+--------+--------
+ v21    | v12
+ v22    | v11
+ v22    | v32
+(3 rows)
+
+-- Errors
+-- vl1 is not associated with property vprop2
+SELECT src, src_vprop2, conn, dest FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, a.vprop2 AS src_vprop2, b.ename AS conn, c.vname AS dest));
+ERROR:  property "vprop2" for element variable "a" not found
+-- property ename is associated with edge labels but not with a vertex label
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, src.ename AS sename));
+ERROR:  property "ename" for element variable "src" not found
+-- vname is associated vertex labels but not with an edge label
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (conn.vname AS cvname, conn.ename AS cename));
+ERROR:  property "vname" for element variable "conn" not found
+-- el1 is associated with only edges, and cannot qualify a vertex
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
+ERROR:  no property graph element of type "vertex" has label "el1" associated with it in property graph "g1"
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1 | vl1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
+ERROR:  no property graph element of type "vertex" has label "el1" associated with it in property graph "g1"
+-- star in COLUMNs is specified but not supported
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
+ERROR:  "*" is not supported here
+LINE 1: ... = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
+                                                                 ^
+-- star anywhere else is not allowed as a property reference
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
+ERROR:  "*" not allowed here
+LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT...
+                                                             ^
+-- select all the properties across all the labels associated with a given type
+-- of graph element
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname, src.vprop1 AS svp1, src.vprop2 AS svp2, src.lprop1 AS slp1, dest.vprop1 AS dvp1, dest.vprop2 AS dvp2, dest.lprop1 AS dlp1, conn.eprop1 AS cep1, conn.lprop2 AS clp2));
+ svname | cename | dvname | svp1 | svp2 |   slp1   | dvp1 | dvp2 |   dlp1   | cep1  |  clp2  
+--------+--------+--------+------+------+----------+------+------+----------+-------+--------
+ v12    | e122   | v21    |   20 |      |          | 1010 | 1100 | vl2_prop | 10002 |       
+ v11    | e121   | v22    |   10 |      |          | 1020 | 1200 | vl2_prop | 10001 |       
+ v11    | e131   | v33    |   10 |      |          | 2030 |      | vl3_prop | 10003 |       
+ v11    | e132   | v31    |   10 |      |          | 2010 |      | vl3_prop | 10004 |       
+ v22    | e231   | v32    | 1020 | 1200 | vl2_prop | 2020 |      | vl3_prop |       | 100050
+(5 rows)
+
+-- three label disjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 | vl3)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname));
+ svname | cename | dvname 
+--------+--------+--------
+ v12    | e122   | v21
+ v11    | e121   | v22
+ v11    | e131   | v33
+ v11    | e132   | v31
+ v22    | e231   | v32
+(5 rows)
+
+-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination.
+WITH all_connected_vertices AS (SELECT svn, dvn FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svn, dest.vname AS dvn))),
+    all_vertices AS (SELECT vn FROM GRAPH_TABLE (g1 MATCH (vertex) COLUMNS (vertex.vname AS vn)))
+SELECT vn FROM all_vertices EXCEPT (SELECT svn FROM all_connected_vertices UNION SELECT dvn FROM all_connected_vertices);
+ vn  
+-----
+ v13
+ v23
+(2 rows)
+
+-- query all connections using a label shared by vertices and edges
+SELECT sn, cn, dn FROM GRAPH_TABLE (g1 MATCH (src IS l1)-[conn IS l1]->(dest IS l1) COLUMNS (src.elname AS sn, conn.elname AS cn, dest.elname AS dn));
+ sn  |  cn  | dn  
+-----+------+-----
+ v12 | e122 | v21
+ v11 | e121 | v22
+ v11 | e131 | v33
+ v11 | e132 | v31
+ v22 | e231 | v32
+(5 rows)
+
+-- Tests for cyclic path patterns
+CREATE TABLE e2_1 (
+    id_2_1 int,
+    id_2_2 int,
+    id_1 int,
+    ename varchar(10),
+    eprop1 int
+);
+CREATE TABLE e3_2 (
+    id_3 int,
+    id_2_1 int,
+    id_2_2 int,
+    ename varchar(10),
+    eprop1 int
+);
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e2_1 KEY (id_2_1, id_2_2, id_1)
+            SOURCE KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            DESTINATION KEY (id_1) REFERENCES v1 (id)
+            LABEL el1 PROPERTIES (eprop1, ename)
+            LABEL l1 PROPERTIES (ename AS elname),
+        e3_2 KEY (id_3, id_2_1, id_2_2)
+            SOURCE KEY (id_3) REFERENCES v3 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+INSERT INTO e1_2 VALUES (3, 1000, 3, 'e123', 10007);
+INSERT INTO e2_1 VALUES (1000, 1, 2, 'e211', 10006);
+INSERT INTO e2_1 VALUES (1000, 3, 3, 'e212', 10008);
+INSERT INTO e3_2 VALUES (2002, 1000, 2, 'e321', 10009);
+-- cyclic pattern using WHERE clause in graph pattern,
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v21  | v12     |    1010 |         20
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+ v32  | v22     |    2020 |       1020
+(6 rows)
+
+-- cyclic pattern using element patterns with the same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v21  | v12     |    1010 |         20
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+ v32  | v22     |    2020 |       1020
+(6 rows)
+
+-- cyclic pattern with WHERE clauses in element patterns with the same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 < 2000)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 > 20) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v13  | v23     |      30 |       1030
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+(3 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+(4 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 between 20 and 2000)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+ v22  | v32     |    1020 |       2020
+ v23  | v13     |    1030 |         30
+(4 rows)
+
+-- labels and elements kinds of element patterns with the same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
+ERROR:  element patterns with same variable name "a" but different element pattern types
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;  -- error
+ERROR:  element patterns with same variable name "a" but different label expressions are not supported
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+ self | through | self_p1 | through_p1 
+------+---------+---------+------------
+ v12  | v21     |      20 |       1010
+ v13  | v23     |      30 |       1030
+(2 rows)
+
+-- add loop to test edge patterns with same variable name
+CREATE TABLE e3_3 (
+    src_id int,
+    dest_id int,
+    ename varchar(10),
+    eprop1 int
+);
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e3_3 KEY (src_id, dest_id)
+            SOURCE KEY (src_id) REFERENCES v3 (id)
+            DESTINATION KEY (dest_id) REFERENCES v3 (id)
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+INSERT INTO e3_3 VALUES (2003, 2003, 'e331', 10010);
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS aname, b.ename AS bname, c.vname AS cname, d.vname AS dname)); --error
+ERROR:  an edge cannot connect more than two vertexes even in a cyclic pattern
+-- the looping edge should be reported only once even when edge pattern with any direction is used
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[c]-(a) COLUMNS (a.vname AS self, c.ename AS loop_name));
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-(a) COLUMNS (a.vname AS self));
+ self 
+------
+ v33
+(1 row)
+
+-- test collation specified in the expression
+INSERT INTO e3_3 VALUES (2003, 2003, 'E331', 10011);
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name COLLATE "C" ASC;
+ self | loop_name 
+------+-----------
+ v33  | E331
+ v33  | e331
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b IS el2 WHERE b.ename > 'E331' COLLATE "C"]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.ename > 'E331' COLLATE "C" COLUMNS (a.vname AS self, b.ename AS loop_name));
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) WHERE loop_name > 'E331' COLLATE "C";
+ self | loop_name 
+------+-----------
+ v33  | e331
+(1 row)
+
+-- property graph with some of the elements, labels and properties same as the
+-- previous one. Test whether components from the specified property graph are
+-- used. Also test explicit collation specification in property.
+CREATE PROPERTY GRAPH g2
+    VERTEX TABLES (
+        v1
+            LABEL l1 PROPERTIES ('g2.' || vname COLLATE "C" AS elname),
+        v2 KEY (id1, id2)
+            LABEL l1 PROPERTIES ('g2.' || vname COLLATE "C" AS elname),
+        v3
+            LABEL l1 PROPERTIES ('g2.' || vname COLLATE "C" AS elname)
+    )
+    EDGE TABLES (
+        e1_2 key (id_1, id_2_1, id_2_2)
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname),
+        e1_3
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname),
+        e2_3 KEY (id_2_1, id_2_2, id_3)
+            SOURCE KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname),
+        e3_3 KEY (src_id, dest_id)
+            SOURCE KEY (src_id) REFERENCES v3 (id)
+            DESTINATION KEY (src_id) REFERENCES v3 (id)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname)
+    );
+SELECT sn, cn, dn FROM GRAPH_TABLE (g2 MATCH (src IS l1)-[conn IS l1]->(dest IS l1) COLUMNS (src.elname AS sn, conn.elname AS cn, dest.elname AS dn)) ORDER BY 1, 2, 3;
+   sn   |   cn    |   dn   
+--------+---------+--------
+ g2.v11 | g2.e121 | g2.v22
+ g2.v11 | g2.e131 | g2.v33
+ g2.v11 | g2.e132 | g2.v31
+ g2.v12 | g2.e122 | g2.v21
+ g2.v13 | g2.e123 | g2.v23
+ g2.v22 | g2.e231 | g2.v32
+ g2.v33 | g2.E331 | g2.v33
+ g2.v33 | g2.e331 | g2.v33
+(8 rows)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b WHERE b.elname > 'g2.E331']->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name));
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.elname > 'g2.E331' COLUMNS (a.elname AS self, b.elname AS loop_name));
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.e331
+(1 row)
+
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name)) WHERE loop_name > 'g2.E331';
+  self  | loop_name 
+--------+-----------
+ g2.v33 | g2.e331
+(1 row)
+
+-- prepared statements, any changes to the property graph should be reflected in
+-- the already prepared statements
+PREPARE cyclestmt AS SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)->(b IS l1)->(c IS l1) WHERE a.elname = c.elname COLUMNS (a.elname AS self, b.elname AS through)) ORDER BY self, through;
+EXECUTE cyclestmt;
+ self | through 
+------+---------
+ v12  | v21
+ v13  | v23
+ v21  | v12
+ v22  | v32
+ v23  | v13
+ v32  | v22
+ v33  | v33
+ v33  | v33
+ v33  | v33
+ v33  | v33
+(10 rows)
+
+ALTER PROPERTY GRAPH g1 DROP EDGE TABLES (e3_2, e3_3);
+EXECUTE cyclestmt;
+ self | through 
+------+---------
+ v12  | v21
+ v13  | v23
+ v21  | v12
+ v23  | v13
+(4 rows)
+
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e3_2 KEY (id_3, id_2_1, id_2_2)
+            SOURCE KEY (id_3) REFERENCES v3 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+EXECUTE cyclestmt;
+ self | through 
+------+---------
+ v12  | v21
+ v13  | v23
+ v21  | v12
+ v22  | v32
+ v23  | v13
+ v32  | v22
+(6 rows)
+
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 DROP LABEL l1;
+EXECUTE cyclestmt;
+ self | through 
+------+---------
+ v12  | v21
+ v13  | v23
+ v21  | v12
+ v23  | v13
+(4 rows)
+
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 ADD LABEL l1 PROPERTIES (vname AS elname);
+EXECUTE cyclestmt;
+ self | through 
+------+---------
+ v12  | v21
+ v13  | v23
+ v21  | v12
+ v22  | v32
+ v23  | v13
+ v32  | v22
+(6 rows)
+
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e3_3 KEY (src_id, dest_id)
+            SOURCE KEY (src_id) REFERENCES v3 (id)
+            DESTINATION KEY (src_id) REFERENCES v3 (id)
+            LABEL l2 PROPERTIES (ename AS elname)
+    );
+PREPARE loopstmt AS SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[e IS l2]->(a) COLUMNS (e.elname AS loop)) ORDER BY loop COLLATE "C" ASC;
+EXECUTE loopstmt;
+ loop 
+------
+ E331
+ e331
+(2 rows)
+
+ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 DROP PROPERTIES (elname);
+EXECUTE loopstmt; -- error
+ERROR:  property "elname" for element variable "e" not found
+ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 ADD PROPERTIES ((ename || '_new')::varchar(10) AS elname);
+EXECUTE loopstmt;
+   loop   
+----------
+ E331_new
+ e331_new
+(2 rows)
+
+-- inheritance and partitioning
+CREATE TABLE pv (id int, val int);
+CREATE TABLE cv1 () INHERITS (pv);
+CREATE TABLE cv2 () INHERITS (pv);
+INSERT INTO pv VALUES (1, 10);
+INSERT INTO cv1 VALUES (2, 20);
+INSERT INTO cv2 VALUES (3, 30);
+CREATE TABLE pe (id int, src int, dest int, val int);
+CREATE TABLE ce1 () INHERITS (pe);
+CREATE TABLE ce2 () INHERITS (pe);
+INSERT INTO pe VALUES (1, 1, 2, 100);
+INSERT INTO ce1 VALUES (2, 2, 3, 200);
+INSERT INTO ce2 VALUES (3, 3, 1, 300);
+CREATE PROPERTY GRAPH g3
+    NODE TABLES (
+        pv KEY (id)
+    )
+    RELATIONSHIP TABLES (
+        pe KEY (id)
+            SOURCE KEY(src) REFERENCES pv(id)
+            DESTINATION KEY(dest) REFERENCES pv(id)
+    );
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  10 | 100 |  20
+  20 | 200 |  30
+  30 | 300 |  10
+(3 rows)
+
+-- temporary property graph
+CREATE TEMPORARY PROPERTY GRAPH gtmp
+    VERTEX TABLES (
+        pv KEY (id)
+    )
+    EDGE TABLES (
+        pe KEY (id)
+            SOURCE KEY(src) REFERENCES pv(id)
+            DESTINATION KEY(dest) REFERENCES pv(id)
+    );
+SELECT * FROM GRAPH_TABLE (gtmp MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  10 | 100 |  20
+  20 | 200 |  30
+  30 | 300 |  10
+(3 rows)
+
+CREATE TABLE ptnv (id int PRIMARY KEY, val int) PARTITION BY LIST(id);
+CREATE TABLE prtv1 PARTITION OF ptnv FOR VALUES IN (1, 2);
+CREATE TABLE prtv2 PARTITION OF ptnv FOR VALUES IN (3);
+INSERT INTO ptnv VALUES (1, 10), (2, 20), (3, 30);
+CREATE TABLE ptne (id int PRIMARY KEY, src int REFERENCES ptnv(id), dest int REFERENCES ptnv(id), val int) PARTITION BY LIST(id);
+CREATE TABLE ptne1 PARTITION OF ptne FOR VALUES IN (1, 2);
+CREATE TABLE ptne2 PARTITION OF ptne FOR VALUES IN (3);
+INSERT INTO ptne VALUES (1, 1, 2, 100), (2, 2, 3, 200), (3, 3, 1, 300);
+CREATE PROPERTY GRAPH g4
+    VERTEX TABLES (ptnv)
+    EDGE TABLES (
+        ptne
+            SOURCE KEY (src) REFERENCES ptnv(id)
+            DESTINATION KEY (dest) REFERENCES ptnv(id)
+    );
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s IS ptnv)-[e IS ptne]->(d IS ptnv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  10 | 100 |  20
+  20 | 200 |  30
+  30 | 300 |  10
+(3 rows)
+
+-- edges from the same vertex in both directions connecting to other vertexes in the same table
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s)-[e]-(d) WHERE s.id = 3 COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  30 | 200 |  20
+  30 | 300 |  10
+(2 rows)
+
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+ val | val | val 
+-----+-----+-----
+  30 | 200 |  20
+  30 | 300 |  10
+(2 rows)
+
+-- ruleutils reverse parsing
+CREATE VIEW customers_us AS SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name)) ORDER BY customer_name, product_name;
+SELECT pg_get_viewdef('customers_us'::regclass);
+                                                                                                                     pg_get_viewdef                                                                                                                     
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  SELECT customer_name,                                                                                                                                                                                                                                +
+     product_name                                                                                                                                                                                                                                      +
+    FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE ((c.address)::text = 'US'::text))-[IS customer_orders|customer_wishlists]->(l IS orders|wishlists)-[IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name))+
+   ORDER BY customer_name, product_name;
+(1 row)
+
+-- test view/graph nesting
+CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
+SELECT * FROM customers;
+ customer_id |   name    | address 
+-------------+-----------+---------
+           1 | customer1 | US
+           2 | customer2 | CA
+           3 | customer3 | GL
+(3 rows)
+
+SELECT * FROM customers_view;
+ customer_id | name_redacted | address 
+-------------+---------------+---------
+           1 | redacted1     | US
+           2 | redacted2     | CA
+           3 | redacted3     | GL
+(3 rows)
+
+CREATE PROPERTY GRAPH myshop2
+    VERTEX TABLES (
+        products,
+        customers_view KEY (customer_id) LABEL customers,
+        orders
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers_view (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+    );
+CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE (myshop2 MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name_redacted AS customer_name_redacted));
+SELECT * FROM customers_us_redacted;
+ customer_name_redacted 
+------------------------
+ redacted1
+(1 row)
+
+-- GRAPH_TABLE in UDFs
+CREATE FUNCTION out_degree(sname varchar) RETURNS varchar AS $$
+DECLARE
+    out_degree int;
+BEGIN
+    SELECT count(*) INTO out_degree FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)->() COLUMNS (src.vname));
+    RETURN out_degree;
+END;
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION direct_connections(sname varchar)
+RETURNS TABLE (cname varchar, dname varchar)
+AS $$
+    SELECT cname, dname FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)-[conn]->(dst) COLUMNS (conn.ename AS cname, dst.vname AS dname));
+$$ LANGUAGE SQL;
+SELECT sname, out_degree(sname) FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname));
+ sname | out_degree 
+-------+------------
+ v11   | 3
+ v12   | 1
+ v13   | 1
+(3 rows)
+
+SELECT sname, cname, dname FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname)), LATERAL direct_connections(sname);
+ sname | cname | dname 
+-------+-------+-------
+ v11   | e121  | v22
+ v11   | e131  | v33
+ v11   | e132  | v31
+ v12   | e122  | v21
+ v13   | e123  | v23
+(5 rows)
+
+-- GRAPH_TABLE joined to a regular table
+SELECT * FROM customers co, GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = co.address)-[IS customer_orders]->(o IS orders) COLUMNS (cg.name_redacted AS customer_name_redacted)) WHERE co.customer_id = 1;
+ customer_id |   name    | address | customer_name_redacted 
+-------------+-----------+---------+------------------------
+           1 | customer1 | US      | redacted1
+(1 row)
+
+-- graph table in a subquery
+SELECT * FROM customers co WHERE co.customer_id = (SELECT customer_id FROM GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cg.customer_id)));
+ customer_id |   name    | address 
+-------------+-----------+---------
+           1 | customer1 | US
+(1 row)
+
+-- query within graph table
+SELECT sname, dname FROM GRAPH_TABLE (g1 MATCH (src)->(dest) WHERE src.vprop1 > (SELECT max(v1.vprop1) FROM v1) COLUMNS(src.vname AS sname, dest.vname AS dname));
+ERROR:  subqueries within GRAPH_TABLE reference are not supported
+SELECT sname, dname FROM GRAPH_TABLE (g1 MATCH (src)->(dest) WHERE out_degree(src.vname) > (SELECT max(out_degree(nname)) FROM GRAPH_TABLE (g1 MATCH (node) COLUMNS (node.vname AS nname))) COLUMNS(src.vname AS sname, dest.vname AS dname));
+ERROR:  subqueries within GRAPH_TABLE reference are not supported
+-- leave the objects behind for pg_upgrade/pg_dump tests
diff --git a/src/test/regress/expected/graph_table_rls.out b/src/test/regress/expected/graph_table_rls.out
new file mode 100644 (file)
index 0000000..5cbd69d
--- /dev/null
@@ -0,0 +1,774 @@
+--
+--Test RLS with GRAPH_TABLE
+--
+--This test verifies that Row Level Security (RLS) policies are correctly
+--enforced when querying tables underlying property graphs using GRAPH_TABLE.
+--graph_table.sql has extensive tests covering interaction of GRAPH_TABLE with
+--other query constructs. rowsecurity.sql has extensive coverage of interaction
+--of RLS and other features of PostgreSQL. This test along with those two tests
+--is sufficient to make sure that all combinations of RLS and GRAPH_TABLE will
+--work as expected.
+-- Clean up in case a prior regression run failed
+-- Suppress NOTICE messages when users/groups don't exist
+SET client_min_messages TO 'warning';
+DROP USER IF EXISTS regress_graph_rls_alice;
+DROP USER IF EXISTS regress_graph_rls_bob;
+DROP USER IF EXISTS regress_graph_rls_carol;
+DROP USER IF EXISTS regress_graph_rls_dave;
+DROP USER IF EXISTS regress_graph_rls_exempt_user;
+DROP ROLE IF EXISTS regress_graph_rls_group1;
+DROP ROLE IF EXISTS regress_graph_rls_group2;
+DROP SCHEMA IF EXISTS graph_rls_schema CASCADE;
+RESET client_min_messages;
+-- initial setup
+CREATE USER regress_graph_rls_alice NOLOGIN;
+CREATE USER regress_graph_rls_bob NOLOGIN;
+CREATE USER regress_graph_rls_carol NOLOGIN;
+CREATE USER regress_graph_rls_dave NOLOGIN;
+CREATE USER regress_graph_rls_exempt_user BYPASSRLS NOLOGIN;
+CREATE ROLE regress_graph_rls_group1 NOLOGIN;
+CREATE ROLE regress_graph_rls_group2 NOLOGIN;
+GRANT regress_graph_rls_group1 TO regress_graph_rls_dave;
+GRANT regress_graph_rls_group2 TO regress_graph_rls_bob;
+CREATE SCHEMA graph_rls_schema;
+GRANT ALL ON SCHEMA graph_rls_schema to public;
+SET search_path = graph_rls_schema;
+-- setup for leaky-function tests
+CREATE FUNCTION f_leak(text) RETURNS bool
+    COST 0.0000001 LANGUAGE plpgsql
+    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+CREATE TABLE users (uid int PRIMARY KEY, pguser name, seclv int);
+INSERT INTO users VALUES
+    (1, 'regress_graph_rls_alice', 99),
+    (2, 'regress_graph_rls_bob', 1),
+    (3, 'regress_graph_rls_carol', 2),
+    (4, 'regress_graph_rls_dave', 3);
+GRANT SELECT ON users TO public;
+CREATE TABLE document_people (
+    did int,
+    dlevel int,
+    dtitle text);
+INSERT INTO document_people VALUES
+    ( 1, 2, 'Politicians'),
+    ( 2, 3, 'Artists'),
+    ( 3, 1, 'Scientists'),
+    ( 4, 100, 'Unspeakables');
+GRANT SELECT ON document_people TO public;
+CREATE TABLE accessed (
+    aid int,
+    uid int,
+    did int);
+INSERT INTO accessed VALUES
+    (1, 2, 3),
+    (2, 3, 1),
+    (3, 3, 3),
+    (4, 4, 3),
+    (5, 1, 1),
+    (6, 1, 4),
+    (7, 4, 2);
+GRANT SELECT ON accessed TO public;
+CREATE PROPERTY GRAPH cabinet
+    VERTEX TABLES (users KEY (uid),
+                   document_people AS document KEY (did))
+    EDGE TABLES (accessed KEY (aid)
+                 SOURCE KEY (uid) REFERENCES users (uid)
+                 DESTINATION KEY (did) REFERENCES document (did));
+GRANT SELECT ON cabinet TO public;
+--
+-- Basic RLS tests
+--
+ALTER TABLE document_people ENABLE ROW LEVEL SECURITY;
+-- user's security level must be higher than or equal to document's
+CREATE POLICY p1 ON document_people AS PERMISSIVE
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+-- but Dave isn't allowed to see document titled 'Scientists'
+CREATE POLICY p2 ON document_people AS RESTRICTIVE TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+CREATE POLICY p3 ON document_people AS RESTRICTIVE TO regress_graph_rls_group1
+    USING (dlevel < 3);
+CREATE POLICY p4 ON document_people AS PERMISSIVE TO regress_graph_rls_group2
+    USING (dlevel < 3);
+SET row_security TO ON;
+-- Use the same query in all the test cases below. Prepare it once and
+-- use multiple times. Apart from making the test file shorter and avoiding
+-- duplication, it also tests that a prepared statement correctly reflect changes
+-- to RLS policies, session user or RLS settings.
+PREPARE graph_rls_query AS
+SELECT * FROM GRAPH_TABLE (cabinet
+                           MATCH (u IS users)-[a IS accessed]->(d IS document)
+                           WHERE f_leak(d.dtitle)
+                           COLUMNS (u.pguser, a.aid, d.dtitle, d.dlevel))
+         ORDER BY 1, 2, 3, 4;
+-- viewpoint from regress_graph_rls_bob
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Scientists
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_bob   |   1 | Scientists  |      1
+ regress_graph_rls_carol |   2 | Politicians |      2
+ regress_graph_rls_carol |   3 | Scientists  |      1
+ regress_graph_rls_dave  |   4 | Scientists  |      1
+(5 rows)
+
+-- viewpoint from regress_graph_rls_carol
+SET SESSION AUTHORIZATION regress_graph_rls_carol;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Scientists
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_bob   |   1 | Scientists  |      1
+ regress_graph_rls_carol |   2 | Politicians |      2
+ regress_graph_rls_carol |   3 | Scientists  |      1
+ regress_graph_rls_dave  |   4 | Scientists  |      1
+(5 rows)
+
+-- viewpoint from regress_graph_rls_dave
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_carol |   2 | Politicians |      2
+(2 rows)
+
+-- RLS policy does not apply to table owner when RLS enabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+(7 rows)
+
+SET row_security TO OFF;
+-- database superuser does bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+(7 rows)
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+(7 rows)
+
+-- RLS policy does not apply to table owner when RLS disabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+(7 rows)
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+ERROR:  query would be affected by row-level security policy for table "document_people"
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP PROPERTY GRAPH cabinet;
+--
+-- Table inheritance
+--
+ALTER TABLE document_people ADD COLUMN category text DEFAULT 'People';
+CREATE TABLE document_places (
+    did int,
+    dlevel int,
+    dtitle text,
+    category text DEFAULT 'Places');
+INSERT INTO document_places VALUES
+    ( 5, 1, 'Paris'),
+    ( 6, 2, 'Tokyo'),
+    ( 7, 3, 'New York');
+GRANT SELECT ON document_places TO public;
+-- Setup inheritance
+CREATE TABLE document (
+    did int,
+    dlevel int,
+    dtitle text);
+GRANT SELECT ON document TO public;
+ALTER TABLE document_people INHERIT document;
+ALTER TABLE document_places INHERIT document;
+INSERT INTO accessed VALUES
+    (11, 2, 5),
+    (12, 3, 6),
+    (13, 1, 7),
+    (14, 4, 5),
+    (15, 1, 6);
+-- Enable RLS and move policies p1 and p2 to parent table but leave p3 and p4 on
+-- child table. The policies on child table are not applied when querying parent
+-- table.
+ALTER TABLE document ENABLE ROW LEVEL SECURITY;
+DROP POLICY p1 ON document_people;
+DROP POLICY p2 ON document_people;
+CREATE POLICY p1 ON document AS PERMISSIVE
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+CREATE POLICY p2 ON document AS RESTRICTIVE TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+CREATE PROPERTY GRAPH cabinet
+    VERTEX TABLES (users KEY (uid), document KEY (did))
+    EDGE TABLES (accessed KEY (aid)
+                 SOURCE KEY (uid) REFERENCES users (uid)
+                 DESTINATION KEY (did) REFERENCES document (did));
+GRANT SELECT ON cabinet TO public;
+SET row_security TO ON;
+-- viewpoint from regress_graph_rls_bob
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Paris
+         pguser          | aid |   dtitle   | dlevel 
+-------------------------+-----+------------+--------
+ regress_graph_rls_bob   |   1 | Scientists |      1
+ regress_graph_rls_bob   |  11 | Paris      |      1
+ regress_graph_rls_carol |   3 | Scientists |      1
+ regress_graph_rls_dave  |   4 | Scientists |      1
+ regress_graph_rls_dave  |  14 | Paris      |      1
+(5 rows)
+
+-- viewpoint from regress_graph_rls_carol
+SET SESSION AUTHORIZATION regress_graph_rls_carol;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_alice |  15 | Tokyo       |      2
+ regress_graph_rls_bob   |   1 | Scientists  |      1
+ regress_graph_rls_bob   |  11 | Paris       |      1
+ regress_graph_rls_carol |   2 | Politicians |      2
+ regress_graph_rls_carol |   3 | Scientists  |      1
+ regress_graph_rls_carol |  12 | Tokyo       |      2
+ regress_graph_rls_dave  |   4 | Scientists  |      1
+ regress_graph_rls_dave  |  14 | Paris       |      1
+(9 rows)
+
+-- viewpoint from regress_graph_rls_dave
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_alice |  13 | New York    |      3
+ regress_graph_rls_alice |  15 | Tokyo       |      2
+ regress_graph_rls_bob   |  11 | Paris       |      1
+ regress_graph_rls_carol |   2 | Politicians |      2
+ regress_graph_rls_carol |  12 | Tokyo       |      2
+ regress_graph_rls_dave  |   7 | Artists     |      3
+ regress_graph_rls_dave  |  14 | Paris       |      1
+(8 rows)
+
+-- RLS policy does not apply to table owner when RLS enabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+SET row_security TO OFF;
+-- database superuser does bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+-- RLS policy does not apply to table owner when RLS disabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+ERROR:  query would be affected by row-level security policy for table "document"
+-- cleanup
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP PROPERTY GRAPH cabinet;
+ALTER TABLE document_people NO INHERIT document;
+ALTER TABLE document_places NO INHERIT document;
+DROP TABLE document;
+--
+-- Partitioned Tables
+--
+CREATE TABLE document (
+    did int,
+    dlevel int,
+    dtitle text,
+    category text) PARTITION BY LIST (category);
+GRANT SELECT ON document TO public;
+ALTER TABLE document ATTACH PARTITION document_people FOR VALUES IN ('People');
+ALTER TABLE document ATTACH PARTITION document_places FOR VALUES IN ('Places');
+-- Enable RLS on partitioned table
+ALTER TABLE document ENABLE ROW LEVEL SECURITY;
+-- create policies on partitioned table
+CREATE POLICY p1 ON document AS PERMISSIVE
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+CREATE POLICY p2 ON document AS RESTRICTIVE TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+CREATE PROPERTY GRAPH cabinet
+    VERTEX TABLES (users KEY (uid), document KEY (did))
+    EDGE TABLES (accessed KEY (aid)
+                 SOURCE KEY (uid) REFERENCES users (uid)
+                 DESTINATION KEY (did) REFERENCES document (did));
+GRANT SELECT ON cabinet TO public;
+SET row_security TO ON;
+-- viewpoint from regress_graph_rls_bob
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Paris
+         pguser          | aid |   dtitle   | dlevel 
+-------------------------+-----+------------+--------
+ regress_graph_rls_bob   |   1 | Scientists |      1
+ regress_graph_rls_bob   |  11 | Paris      |      1
+ regress_graph_rls_carol |   3 | Scientists |      1
+ regress_graph_rls_dave  |   4 | Scientists |      1
+ regress_graph_rls_dave  |  14 | Paris      |      1
+(5 rows)
+
+-- viewpoint from regress_graph_rls_carol
+SET SESSION AUTHORIZATION regress_graph_rls_carol;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_alice |  15 | Tokyo       |      2
+ regress_graph_rls_bob   |   1 | Scientists  |      1
+ regress_graph_rls_bob   |  11 | Paris       |      1
+ regress_graph_rls_carol |   2 | Politicians |      2
+ regress_graph_rls_carol |   3 | Scientists  |      1
+ regress_graph_rls_carol |  12 | Tokyo       |      2
+ regress_graph_rls_dave  |   4 | Scientists  |      1
+ regress_graph_rls_dave  |  14 | Paris       |      1
+(9 rows)
+
+-- viewpoint from regress_graph_rls_dave
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_alice |  13 | New York    |      3
+ regress_graph_rls_alice |  15 | Tokyo       |      2
+ regress_graph_rls_bob   |  11 | Paris       |      1
+ regress_graph_rls_carol |   2 | Politicians |      2
+ regress_graph_rls_carol |  12 | Tokyo       |      2
+ regress_graph_rls_dave  |   7 | Artists     |      3
+ regress_graph_rls_dave  |  14 | Paris       |      1
+(8 rows)
+
+-- RLS policy does not apply to table owner when RLS enabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+SET row_security TO OFF;
+-- database superuser does bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+-- RLS policy does not apply to table owner when RLS disabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+ERROR:  query would be affected by row-level security policy for table "document"
+--
+-- Recursion through GRAPH_TABLE also throws error
+--
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+-- Create a policy on document that references document itself via GRAPH_TABLE
+CREATE POLICY pr ON document TO regress_graph_rls_dave
+    USING (EXISTS (SELECT 1 FROM GRAPH_TABLE (cabinet
+                           MATCH (u IS users)-[a IS accessed]->(d IS document)
+                           WHERE u.pguser = current_user
+                           COLUMNS (a.aid))));
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+ERROR:  infinite recursion detected in policy for relation "document"
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP POLICY pr ON document;
+--
+-- Command specific policy. Since GRAPH_TABLE can be used in only SELECT, test
+-- only FOR SELECT policies.
+--
+DROP POLICY p1 ON document;
+DROP POLICY p2 ON document;
+CREATE POLICY p1 ON document AS PERMISSIVE
+    FOR SELECT
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+CREATE POLICY p2 ON document AS RESTRICTIVE
+    FOR SELECT TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |   dtitle    | dlevel 
+-------------------------+-----+-------------+--------
+ regress_graph_rls_alice |   5 | Politicians |      2
+ regress_graph_rls_alice |  13 | New York    |      3
+ regress_graph_rls_alice |  15 | Tokyo       |      2
+ regress_graph_rls_bob   |  11 | Paris       |      1
+ regress_graph_rls_carol |   2 | Politicians |      2
+ regress_graph_rls_carol |  12 | Tokyo       |      2
+ regress_graph_rls_dave  |   7 | Artists     |      3
+ regress_graph_rls_dave  |  14 | Paris       |      1
+(8 rows)
+
+--
+-- Default deny policy, FORCE ROW LEVEL SECURITY
+--
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP POLICY p1 ON document;
+DROP POLICY p2 ON document;
+-- default deny policy applies to non-owners, non-rls-exempt and non-superusers
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+ pguser | aid | dtitle | dlevel 
+--------+-----+--------+--------
+(0 rows)
+
+-- Deny RLS policy does not apply to table owner, superuser or RLS exempt user
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+NOTICE:  f_leak => Politicians
+NOTICE:  f_leak => Artists
+NOTICE:  f_leak => Scientists
+NOTICE:  f_leak => Unspeakables
+NOTICE:  f_leak => Paris
+NOTICE:  f_leak => Tokyo
+NOTICE:  f_leak => New York
+         pguser          | aid |    dtitle    | dlevel 
+-------------------------+-----+--------------+--------
+ regress_graph_rls_alice |   5 | Politicians  |      2
+ regress_graph_rls_alice |   6 | Unspeakables |    100
+ regress_graph_rls_alice |  13 | New York     |      3
+ regress_graph_rls_alice |  15 | Tokyo        |      2
+ regress_graph_rls_bob   |   1 | Scientists   |      1
+ regress_graph_rls_bob   |  11 | Paris        |      1
+ regress_graph_rls_carol |   2 | Politicians  |      2
+ regress_graph_rls_carol |   3 | Scientists   |      1
+ regress_graph_rls_carol |  12 | Tokyo        |      2
+ regress_graph_rls_dave  |   4 | Scientists   |      1
+ regress_graph_rls_dave  |   7 | Artists      |      3
+ regress_graph_rls_dave  |  14 | Paris        |      1
+(12 rows)
+
+-- FORCE ROW LEVEL SECURITY applies RLS to owners too
+ALTER TABLE document FORCE ROW LEVEL SECURITY;
+EXECUTE graph_rls_query;
+ pguser | aid | dtitle | dlevel 
+--------+-----+--------+--------
+(0 rows)
+
+SET row_security TO OFF;
+EXECUTE graph_rls_query; -- error
+ERROR:  query would be affected by row-level security policy for table "document"
+HINT:  To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
+-- Clean up
+DEALLOCATE graph_rls_query;
+-- leave objects behind for pg_upgrade/pg_dump tests
index b026d38dcfb10033b16d6a6122b4137179d753bb..280c3aaf1a477f83eb820b0b2d1e0b249c7a33c0 100644 (file)
@@ -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;
@@ -65,7 +66,8 @@ DECLARE
     objtype text;
 BEGIN
     FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
-        ('toast table column'), ('view column'), ('materialized view column')
+        ('toast table column'), ('view column'), ('materialized view column'),
+        ('property graph element'), ('property graph label'), ('property graph property')
     LOOP
         BEGIN
             PERFORM pg_get_object_address(objtype, '{one}', '{}');
@@ -81,6 +83,9 @@ WARNING:  error for sequence column: unsupported object type "sequence column"
 WARNING:  error for toast table column: unsupported object type "toast table column"
 WARNING:  error for view column: unsupported object type "view column"
 WARNING:  error for materialized view column: unsupported object type "materialized view column"
+WARNING:  error for property graph element: unsupported object type "property graph element"
+WARNING:  error for property graph label: unsupported object type "property graph label"
+WARNING:  error for property graph property: unsupported object type "property graph property"
 -- miscellaneous other errors
 select * from pg_get_object_address('operator of access method', '{btree,integer_ops,1}', '{int4,bool}');
 ERROR:  operator 1 (int4, bool) of operator family integer_ops for access method btree does not exist
@@ -98,7 +103,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 +164,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 +409,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 +486,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 +531,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 +546,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;
 --
@@ -578,6 +592,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
     ('pg_parameter_acl'::regclass, 0, 0), -- no parameter ACL
     ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
+    ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
+    ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
     ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
@@ -634,5 +651,8 @@ ORDER BY objects.classid, objects.objid, objects.objsubid;
 ("(""publication relation"",,,)")|("(""publication relation"",,)")|NULL
 ("(""publication namespace"",,,)")|("(""publication namespace"",,)")|NULL
 ("(""parameter ACL"",,,)")|("(""parameter ACL"",,)")|NULL
+("(""property graph element"",,,)")|("(""property graph element"",,)")|NULL
+("(""property graph label"",,,)")|("(""property graph label"",,)")|NULL
+("(""property graph property"",,,)")|("(""property graph property"",,)")|NULL
 -- restore normal output mode
 \a\t
index 51b9608a6680854bce7ae6e47fea380ad970a427..d64169b7bf005c018a1398acd201288a1ec42898 100644 (file)
@@ -273,3 +273,15 @@ NOTICE:  checking pg_subscription {subowner} => pg_authid {oid}
 NOTICE:  checking pg_subscription {subserver} => pg_foreign_server {oid}
 NOTICE:  checking pg_subscription_rel {srsubid} => pg_subscription {oid}
 NOTICE:  checking pg_subscription_rel {srrelid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_element {pgepgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_element {pgerelid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_element {pgesrcvertexid} => pg_propgraph_element {oid}
+NOTICE:  checking pg_propgraph_element {pgedestvertexid} => pg_propgraph_element {oid}
+NOTICE:  checking pg_propgraph_element_label {pgellabelid} => pg_propgraph_label {oid}
+NOTICE:  checking pg_propgraph_element_label {pgelelid} => pg_propgraph_element {oid}
+NOTICE:  checking pg_propgraph_label {pglpgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_label_property {plppropid} => pg_propgraph_property {oid}
+NOTICE:  checking pg_propgraph_label_property {plpellabelid} => pg_propgraph_element_label {oid}
+NOTICE:  checking pg_propgraph_property {pgppgid} => pg_class {oid}
+NOTICE:  checking pg_propgraph_property {pgptypid} => pg_type {oid}
+NOTICE:  checking pg_propgraph_property {pgpcollation} => pg_collation {oid}
index 7bc274566c38cb2c14d56584f89d8751422b8bbb..9c9cdd12af5ed5ec35e3c4529d397106c32b74eb 100644 (file)
@@ -3147,9 +3147,100 @@ revoke select on dep_priv_test from regress_priv_user4 cascade;
 
 set session role regress_priv_user1;
 drop table dep_priv_test;
+--
+-- Property graphs
+--
+set session role regress_priv_user1;
+create property graph ptg1
+       vertex tables (
+               atest5 key (four)
+                       default label properties (four)
+                       label lttc properties (three as lttck),
+               atest1 key (a)
+                       default label
+                       label lttc properties (a as lttck),
+               atest2 key (col1)
+                       default label
+                       label ltv properties (col1 as ltvk));
+-- select privileges on property graph as well as table
+select * from graph_table (ptg1 match (is atest5) COLUMNS (1 as value)) limit 0; -- ok
+ value 
+-------
+(0 rows)
+
+grant select on ptg1 to regress_priv_user2;
+set session role regress_priv_user2;
+select * from graph_table (ptg1 match (is atest1) COLUMNS (1 as value)) limit 0; -- ok
+ value 
+-------
+(0 rows)
+
+-- select privileges on property graph but not table
+select * from graph_table (ptg1 match (is atest5) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for table atest5
+select * from graph_table (ptg1 match (is lttc) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for table atest5
+set session role regress_priv_user3;
+-- select privileges on table but not property graph
+select * from graph_table (ptg1 match (is atest1) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for property graph ptg1
+-- select privileges on neither
+select * from graph_table (ptg1 match (is atest5) COLUMNS (1 as value)) limit 0; -- fails
+ERROR:  permission denied for property graph ptg1
+-- column privileges
+set session role regress_priv_user1;
+select * from graph_table (ptg1 match (v is lttc) COLUMNS (v.lttck)) limit 0; -- ok
+ lttck 
+-------
+(0 rows)
+
+grant select on ptg1 to regress_priv_user4;
+set session role regress_priv_user4;
+select * from graph_table (ptg1 match (a is atest5) COLUMNS (a.four)) limit 0; -- ok
+ four 
+------
+(0 rows)
+
+select * from graph_table (ptg1 match (v is lttc) COLUMNS (v.lttck)) limit 0; -- fail
+ERROR:  permission denied for table atest5
+-- access property graph through security definer view
+set session role regress_priv_user4;
+create view atpgv1 as select * from graph_table (ptg1 match (is atest1) COLUMNS (1 as value)) limit 0;
+grant select on atpgv1 to regress_priv_user3;
+select * from atpgv1; -- ok
+ value 
+-------
+(0 rows)
+
+set session role regress_priv_user3;
+select * from atpgv1; -- ok
+ value 
+-------
+(0 rows)
+
+set session role regress_priv_user4;
+create view atpgv2 as select * from graph_table (ptg1 match (v is ltv) COLUMNS (v.ltvk)) limit 0;
+-- though the session user is the owner of the view and also has access to the
+-- property graph, it does not have access to a table referenced in the graph
+-- pattern
+select * from atpgv2; -- fail
+ERROR:  permission denied for table atest2
+grant select on atpgv2 to regress_priv_user2;
+-- The user who otherwise does not have access to the property graph, gets
+-- access to it through a security definer view and uses it successfully since
+-- it has access to the tables referenced in the graph pattern.
+set session role regress_priv_user2;
+select * from atpgv2; -- ok
+ ltvk 
+------
+(0 rows)
+
 -- clean up
 \c
 drop sequence x_seq;
+drop view atpgv1;
+drop view atpgv2;
+drop property graph ptg1;
 DROP AGGREGATE priv_testagg1(int);
 DROP FUNCTION priv_testfunc2(int);
 DROP FUNCTION priv_testfunc4(boolean);
index 549e9b2d7be4aaabbf2c83c5e7688c84522edd72..e779ada70cbd58f194c3f75c2aaf023ee1f8b772 100644 (file)
@@ -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 *
@@ -102,7 +102,7 @@ test: publication subscription
 # Another group of parallel tests
 # select_views depends on create_view
 # ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass stats_rewrite
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass stats_rewrite graph_table
 
 # ----------
 # Another group of parallel tests (JSON related)
@@ -123,7 +123,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # The stats test resets stats, so nothing else needing stats access can be in
 # this group.
 # ----------
-test: partition_merge partition_split partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate
+test: partition_merge partition_split partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain compression compression_lz4 memoize stats predicate numa eager_aggregate graph_table_rls
 
 # event_trigger depends on create_am and cannot run concurrently with
 # any test that runs DDL
index 5e20dc63337c98251dd3df49b53aa7d68ab5199d..528e6f548a4a13b5c8b75d27326ac28970bf735a 100644 (file)
@@ -459,6 +459,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 (file)
index 0000000..5c2ced1
--- /dev/null
@@ -0,0 +1,365 @@
+CREATE SCHEMA create_property_graph_tests;
+GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC;
+SET search_path = create_property_graph_tests;
+CREATE SCHEMA create_property_graph_tests_2;
+GRANT USAGE ON SCHEMA create_property_graph_tests_2 TO PUBLIC;
+
+CREATE ROLE regress_graph_user1;
+CREATE ROLE regress_graph_user2;
+
+CREATE PROPERTY GRAPH g1;
+
+COMMENT ON PROPERTY GRAPH g1 IS 'a graph';
+
+CREATE PROPERTY GRAPH g1;  -- error: duplicate
+
+CREATE TABLE t1 (a int, b text);
+CREATE TABLE t2 (i int PRIMARY KEY, j int, k int);
+CREATE TABLE t3 (x int, y text, z text);
+
+CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i));
+CREATE TABLE e2 (a int, x int, t text);
+
+CREATE PROPERTY GRAPH g2
+    VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2)
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+
+-- test dependencies/object descriptions
+
+DROP TABLE t1;  -- fail
+ALTER TABLE t1 DROP COLUMN b;  -- non-key column; fail
+ALTER TABLE t1 DROP COLUMN a;  -- key column; fail
+
+-- like g2 but assembled with ALTER
+CREATE PROPERTY GRAPH g3;
+ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL);
+ALTER PROPERTY GRAPH g3
+    ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1)
+    ADD EDGE TABLES (
+        e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i),
+        e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+    );
+ALTER PROPERTY GRAPH g3
+    ALTER VERTEX TABLE t3
+        ADD LABEL t3l2 PROPERTIES ALL COLUMNS
+        ADD LABEL t3l3 PROPERTIES ALL COLUMNS;
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x;  -- error
+ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3;
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2);  -- fail
+ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE;
+ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2);
+
+CREATE PROPERTY GRAPH g4
+    VERTEX TABLES (
+        t1 KEY (a) NO PROPERTIES,
+        t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k),
+        t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz)
+    )
+    EDGE TABLES (
+        e1
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (i) REFERENCES t2 (i)
+            PROPERTIES ALL COLUMNS,
+        e2 KEY (a, x)
+            SOURCE KEY (a) REFERENCES t1 (a)
+            DESTINATION KEY (x, t) REFERENCES t3 (x, y)
+            PROPERTIES ALL COLUMNS
+    );
+
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk);
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k);
+
+CREATE TABLE t11 (a int PRIMARY KEY);
+CREATE TABLE t12 (b int PRIMARY KEY);
+CREATE TABLE t13 (
+    c int PRIMARY KEY,
+    d int REFERENCES t11,
+    e int REFERENCES t12
+);
+
+CREATE PROPERTY GRAPH g5
+    VERTEX TABLES (t11, t12)
+    EDGE TABLES (t13 SOURCE t11 DESTINATION t12);
+
+SELECT pg_get_propgraphdef('g5'::regclass);
+
+-- error cases
+CREATE UNLOGGED PROPERTY GRAPH gx VERTEX TABLES (xx, yy);
+CREATE PROPERTY GRAPH gx VERTEX TABLES (xx, yy);
+CREATE PROPERTY GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a));
+ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t3 KEY (x));  -- duplicate alias
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2 KEY (i))
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION tx
+    );
+COMMENT ON PROPERTY GRAPH gx IS 'not a graph';
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (t1 KEY (a), t2)
+    EDGE TABLES (
+        e1 SOURCE t1 DESTINATION t2  -- no foreign keys
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a)
+            LABEL foo PROPERTIES (a + 1 AS aa)
+            LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+ALTER PROPERTY GRAPH g2
+    ADD VERTEX TABLES (
+        t1 AS t1x KEY (a)
+            LABEL foo PROPERTIES (a + 1 AS aa)
+            LABEL bar PROPERTIES (1 + a AS aa)  -- expression mismatch
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) PROPERTIES (b AS p1),
+        t2 PROPERTIES (k AS p1)  -- type mismatch
+    );
+ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE t1 ADD LABEL foo PROPERTIES (b AS k);  -- type mismatch
+
+CREATE TABLE t1x (a int, b varchar(10));
+CREATE TABLE t2x (i int, j varchar(15));
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1x KEY (a) PROPERTIES (b AS p1),
+        t2x KEY (i) PROPERTIES (j AS p1)  -- typmod mismatch
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1x KEY (a) PROPERTIES (b::varchar(20) AS p1),
+        t2x KEY (i) PROPERTIES (j::varchar(25) AS p1)  -- typmod mismatch
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1x KEY (a) PROPERTIES (b::varchar(20) AS p1),
+        t2x KEY (i) PROPERTIES (j::varchar(20) AS p1)  -- matching typmods by casting works
+    );
+DROP PROPERTY GRAPH gx;
+DROP TABLE t1x, t2x;
+
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS b, k)  -- mismatching number of properties on label
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a)  -- mismatching number of properties on label
+    );
+CREATE PROPERTY GRAPH gx
+    VERTEX TABLES (
+        t1 KEY (a) LABEL l1 PROPERTIES (a, b),
+        t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS j)  -- mismatching property names on label
+    );
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS yy, b AS zz);  -- mismatching number of properties on label
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS zz);  -- mismatching property names on label
+ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x);  -- mismatching number of properties on label
+
+ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1;
+SET ROLE regress_graph_user1;
+GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2;
+GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2;  -- fail
+RESET ROLE;
+
+-- collation
+
+CREATE TABLE tc1 (a int, b text);
+CREATE TABLE tc2 (a int, b text);
+CREATE TABLE tc3 (a int, b text COLLATE "C");
+
+CREATE TABLE ec1 (ek1 int, ek2 int, eb text);
+CREATE TABLE ec2 (ek1 int, ek2 int, eb text COLLATE "POSIX");
+
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a), tc3 KEY (a)); -- fail
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+    ); -- fail
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a) DEFAULT LABEL PROPERTIES (a), tc3 KEY (b))
+    EDGE TABLES (
+        ec2 KEY (ek1, eb)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (eb) REFERENCES tc3 (b)
+    ); -- fail
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (tc1 KEY (a), tc2 KEY (a))
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+    );
+ALTER PROPERTY GRAPH gc1 ADD VERTEX TABLES (tc3 KEY (a)); -- fail
+ALTER PROPERTY GRAPH gc1
+    ADD EDGE TABLES (
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+    ); -- fail
+ALTER PROPERTY GRAPH gc1
+    ADD VERTEX TABLES (
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b COLLATE pg_catalog.DEFAULT AS b)
+    );
+ALTER PROPERTY GRAPH gc1
+    ADD EDGE TABLES (
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb)
+    );
+DROP PROPERTY GRAPH gc1;
+CREATE PROPERTY GRAPH gc1
+    VERTEX TABLES (
+        tc1 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar COLLATE "C" AS b),
+        tc2 KEY (a) DEFAULT LABEL PROPERTIES (a, (b COLLATE "C")::varchar AS b),
+        tc3 KEY (a) DEFAULT LABEL PROPERTIES (a, b::varchar AS b)
+    )
+    EDGE TABLES (
+        ec1 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb),
+        ec2 KEY (ek1, ek2)
+            SOURCE KEY (ek1) REFERENCES tc1 (a)
+            DESTINATION KEY (ek2) REFERENCES tc2 (a)
+            DEFAULT LABEL PROPERTIES (ek1, ek2, eb COLLATE pg_catalog.DEFAULT AS eb)
+    );
+
+-- type inconsistency check
+
+CREATE TABLE v1 (a int primary key, b text);
+CREATE TABLE e(k1 text, k2 text, c text);
+CREATE TABLE v2 (m text, n text);
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2(m)
+    ); -- fail
+ALTER TABLE e DROP COLUMN k1, ADD COLUMN k1 bigint primary key;
+CREATE PROPERTY GRAPH gt
+    VERTEX TABLES (v1 KEY (a), v2 KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2(m)
+    );
+
+-- information schema
+
+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;
+
+-- test object address functions
+SELECT pg_describe_object(classid, objid, objsubid) as obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as reference_graph
+    FROM pg_depend
+    WHERE refclassid = 'pg_class'::regclass AND
+          refobjid = 'create_property_graph_tests.g2'::regclass
+    ORDER BY 1, 2;
+SELECT (pg_identify_object_as_address(classid, objid, objsubid)).*
+    FROM pg_depend
+    WHERE refclassid = 'pg_class'::regclass AND
+          refobjid = 'create_property_graph_tests.g2'::regclass
+    ORDER BY 1, 2, 3;
+SELECT (pg_identify_object(classid, objid, objsubid)).*
+    FROM pg_depend
+    WHERE refclassid = 'pg_class'::regclass AND
+          refobjid = 'create_property_graph_tests.g2'::regclass
+    ORDER BY 1, 2, 3, 4;
+
+\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
+
+-- Test \d variants for property graphs
+\dG g1
+\dG+ g1
+\dGx g1
+\d g2
+\d g1
+\d+ g2
+\d+ g1
+\dG g_nonexistent
+\dG t11
+\set QUIET 'off'
+\dG g_nonexistent
+\set QUIET 'on'
+
+-- temporary property graph
+
+-- Keep this at the end to avoid test failure due to changing temporary
+-- namespace names in information schema query outputs
+CREATE TEMPORARY PROPERTY GRAPH g1; -- same name as persistent graph
+DROP PROPERTY GRAPH g1;  -- drops temporary graph retaining persistent graph
+\dG g1
+CREATE TEMPORARY TABLE v2tmp (m text, n text);
+CREATE TEMPORARY PROPERTY GRAPH gtmp
+    VERTEX TABLES (v1 KEY (a), v2tmp KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2tmp(m)
+    );
+DROP PROPERTY GRAPH gtmp;
+CREATE PROPERTY GRAPH gtmp
+    VERTEX TABLES (v1 KEY (a), v2tmp KEY (m))
+    EDGE TABLES (
+        e KEY (k1, k2)
+            SOURCE KEY (k1) REFERENCES v1(a)
+            DESTINATION KEY (k2) REFERENCES v2tmp(m)
+    );
+ALTER PROPERTY GRAPH g1
+    ADD VERTEX TABLES (v2tmp KEY (m));  -- error
+
+
+-- DROP, ALTER SET SCHEMA, ALTER PROPERTY GRAPH RENAME TO
+
+DROP TABLE g2;  -- error: wrong object type
+CREATE VIEW vg1 AS SELECT * FROM GRAPH_TABLE(g1 MATCH () COLUMNS (1 AS one));
+DROP PROPERTY GRAPH g1; -- error
+ALTER PROPERTY GRAPH g1 SET SCHEMA create_property_graph_tests_2;
+ALTER PROPERTY GRAPH create_property_graph_tests_2.g1 RENAME TO g2;
+DROP PROPERTY GRAPH create_property_graph_tests_2.g2 CASCADE;
+DROP PROPERTY GRAPH g1;  -- error
+ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (t1 KEY (a));  -- error
+ALTER PROPERTY GRAPH IF EXISTS g1 SET SCHEMA create_property_graph_tests_2;
+DROP PROPERTY GRAPH IF EXISTS g1;
+
+DROP ROLE regress_graph_user1, regress_graph_user2;
+
+-- leave remaining objects behind for pg_upgrade/pg_dump tests
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
new file mode 100644 (file)
index 0000000..61a1324
--- /dev/null
@@ -0,0 +1,567 @@
+CREATE SCHEMA graph_table_tests;
+GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC;
+SET search_path = graph_table_tests;
+
+CREATE TABLE products (
+    product_no integer PRIMARY KEY,
+    name varchar,
+    price numeric
+);
+
+CREATE TABLE customers (
+    customer_id integer PRIMARY KEY,
+    name varchar,
+    address varchar
+);
+
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    ordered_when date
+);
+
+CREATE TABLE order_items (
+    order_items_id integer PRIMARY KEY,
+    order_id integer REFERENCES orders (order_id),
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+CREATE TABLE customer_orders (
+    customer_orders_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    order_id integer REFERENCES orders (order_id)
+);
+
+CREATE TABLE wishlists (
+    wishlist_id integer PRIMARY KEY,
+    wishlist_name varchar
+);
+
+CREATE TABLE wishlist_items (
+    wishlist_items_id integer PRIMARY KEY,
+    wishlist_id integer REFERENCES wishlists (wishlist_id),
+    product_no integer REFERENCES products (product_no)
+);
+
+CREATE TABLE customer_wishlists (
+    customer_wishlist_id integer PRIMARY KEY,
+    customer_id integer REFERENCES customers (customer_id),
+    wishlist_id integer REFERENCES wishlists (wishlist_id)
+);
+
+CREATE PROPERTY GRAPH myshop
+    VERTEX TABLES (
+        products,
+        customers,
+        orders
+           DEFAULT LABEL
+           LABEL lists PROPERTIES (order_id AS node_id, 'order'::varchar(10) AS list_type),
+        wishlists
+           DEFAULT LABEL
+           LABEL lists PROPERTIES (wishlist_id AS node_id, 'wishlist'::varchar(10) AS list_type)
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (order_id AS link_id, product_no),
+        wishlist_items KEY (wishlist_items_id)
+            SOURCE KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no)
+            DEFAULT LABEL
+            LABEL list_items PROPERTIES (wishlist_id AS link_id, product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, order_id AS link_id),
+        customer_wishlists KEY (customer_wishlist_id)
+            SOURCE KEY (customer_id) REFERENCES customers (customer_id)
+            DESTINATION KEY (wishlist_id) REFERENCES wishlists (wishlist_id)
+            DEFAULT LABEL
+            LABEL cust_lists PROPERTIES (customer_id, wishlist_id AS link_id)
+    );
+
+SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers|employees WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c.name AS customer_name));  -- error
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers), (o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT * FROM GRAPH_TABLE (myshop MATCH COLUMNS (1 AS col));  -- error, empty match clause
+SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)->{1,2}(o IS orders) COLUMNS (c.name AS customer_name));  -- error
+SELECT * FROM GRAPH_TABLE (myshop MATCH ((c IS customers)->(o IS orders)) COLUMNS (c.name));
+
+-- a property graph can be referenced only from within GRAPH_TABLE clause.
+SELECT * FROM myshop; -- error
+COPY myshop TO stdout; -- error
+INSERT INTO myshop VALUES (1); -- error
+
+INSERT INTO products VALUES
+    (1, 'product1', 10),
+    (2, 'product2', 20),
+    (3, 'product3', 30);
+INSERT INTO customers VALUES
+    (1, 'customer1', 'US'),
+    (2, 'customer2', 'CA'),
+    (3, 'customer3', 'GL');
+INSERT INTO orders VALUES
+    (1, date '2024-01-01'),
+    (2, date '2024-01-02'),
+    (3, date '2024-01-03');
+INSERT INTO wishlists VALUES
+    (1, 'wishlist1'),
+    (2, 'wishlist2'),
+    (3, 'wishlist3');
+INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES
+    (1, 1, 1, 5),
+    (2, 1, 2, 10),
+    (3, 2, 1, 7);
+INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES
+    (1, 1, 1),
+    (2, 2, 2);
+INSERT INTO customer_wishlists (customer_wishlist_id, customer_id, wishlist_id) VALUES
+    (1, 2, 3),
+    (2, 3, 1),
+    (3, 3, 2);
+INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES
+    (1, 1, 2),
+    (2, 1, 3),
+    (3, 2, 1),
+    (4, 3, 1);
+
+-- single element path pattern
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
+-- graph element specification without label or variable
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[]->(o IS orders) COLUMNS (c.name AS customer_name));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[co IS customer_orders]->(o IS orders WHERE o.ordered_when = date '2024-01-02') COLUMNS (c.name, c.address));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when));
+-- spaces around pattern operators
+SELECT * FROM GRAPH_TABLE (myshop MATCH ( o IS orders ) <- [ IS customer_orders ] - (c IS customers) COLUMNS ( c.name, o.ordered_when));
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS cust_lists]->(l IS lists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, l.list_type)) ORDER BY customer_name, product_name, list_type;
+-- label disjunction
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name)) ORDER BY customer_name, product_name;
+-- property not associated with labels queried results in error
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, l.list_type)) ORDER BY 1, 2, 3;
+-- vertex to vertex connection abbreviation
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1;
+
+-- lateral test
+CREATE TABLE x1 (a int, b text);
+INSERT INTO x1 VALUES (1, 'one'), (2, 'two');
+SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid));
+DROP TABLE x1;
+
+CREATE TABLE v1 (
+    id int PRIMARY KEY,
+    vname varchar(10),
+    vprop1 int,
+    vprop2 int
+);
+
+CREATE TABLE v2 (
+    id1 int,
+    id2 int,
+    vname varchar(10),
+    vprop1 int,
+    vprop2 int
+);
+
+CREATE TABLE v3 (
+    id int PRIMARY KEY,
+    vname varchar(10),
+    vprop1 int,
+    vprop2 int
+);
+
+-- edge connecting v1 and v2
+CREATE TABLE e1_2 (
+    id_1 int,
+    id_2_1 int,
+    id_2_2 int,
+    ename varchar(10),
+    eprop1 int
+);
+
+-- edge connecting v1 and v3
+CREATE TABLE e1_3 (
+    id_1 int,
+    id_3 int,
+    ename varchar(10),
+    eprop1 int,
+    PRIMARY KEY (id_1, id_3)
+);
+
+CREATE TABLE e2_3 (
+    id_2_1 int,
+    id_2_2 int,
+    id_3 int,
+    ename varchar(10),
+    eprop1 int
+);
+
+CREATE PROPERTY GRAPH g1
+    VERTEX TABLES (
+        v1
+            LABEL vl1 PROPERTIES (vname, vprop1)
+            LABEL l1 PROPERTIES (vname AS elname), -- label shared by vertexes as well as edges
+        v2 KEY (id1, id2)
+            LABEL vl2 PROPERTIES (vname, vprop2, 'vl2_prop'::varchar(10) AS lprop1)
+            LABEL vl3 PROPERTIES (vname, vprop1, 'vl2_prop'::varchar(10) AS lprop1)
+            LABEL l1 PROPERTIES (vname AS elname),
+        v3
+            LABEL vl3 PROPERTIES (vname, vprop1, 'vl3_prop'::varchar(10) AS lprop1)
+            LABEL l1 PROPERTIES (vname AS elname)
+    )
+    -- edges with differing number of columns in destination keys
+    EDGE TABLES (
+        e1_2 key (id_1, id_2_1, id_2_2)
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL el1 PROPERTIES (eprop1, ename)
+            LABEL l1 PROPERTIES (ename AS elname),
+        e1_3
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            -- order of property names doesn't matter
+            LABEL el1 PROPERTIES (ename, eprop1)
+            LABEL l1 PROPERTIES (ename AS elname),
+        e2_3 key (id_2_1, id_2_2, id_3)
+            SOURCE KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            -- new property lprop2 not shared by el1
+            -- does not share eprop1 from by el1
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+
+INSERT INTO v1 VALUES
+    (1, 'v11', 10, 100),
+    (2, 'v12', 20, 200),
+    (3, 'v13', 30, 300);
+INSERT INTO v2 VALUES
+    (1000, 1, 'v21', 1010, 1100),
+    (1000, 2, 'v22', 1020, 1200),
+    (1000, 3, 'v23', 1030, 1300);
+INSERT INTO v3 VALUES
+    (2001, 'v31', 2010, 2100),
+    (2002, 'v32', 2020, 2200),
+    (2003, 'v33', 2030, 2300);
+INSERT INTO e1_2 VALUES
+    (1, 1000, 2, 'e121', 10001),
+    (2, 1000, 1, 'e122', 10002);
+INSERT INTO e1_3 VALUES
+    (1, 2003, 'e131', 10003),
+    (1, 2001, 'e132', 10004);
+INSERT INTO e2_3 VALUES (1000, 2, 2002, 'e231', 10005);
+
+-- empty element path pattern, counts number of edges in the graph
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 AS one));
+SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()->() COLUMNS (1 AS one));
+-- Project property associated with a label specified in the graph pattern even
+-- if it is defined for a graph element through a different label. (Refer
+-- section 6.5 of SQL/PGQ standard). For example, vprop1 in the query below. It
+-- is defined on v2 through label vl3, but gets exposed in the query through
+-- label vl1 which is not associated with v2. v2, in turn, is included because
+-- of label vl2.
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname, a.vprop1));
+-- vprop2 is associated with vl2 but not vl3
+SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, b.ename AS conn, c.vname AS dest, c.lprop1, c.vprop2, c.vprop1));
+-- edges directed in both ways - to and from v2
+SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-[conn]-(v2) COLUMNS (v1.vname AS v1name, conn.ename AS cname, v2.vname AS v2name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name, v2.vname AS v2name));
+
+-- Errors
+-- vl1 is not associated with property vprop2
+SELECT src, src_vprop2, conn, dest FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, a.vprop2 AS src_vprop2, b.ename AS conn, c.vname AS dest));
+-- property ename is associated with edge labels but not with a vertex label
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, src.ename AS sename));
+-- vname is associated vertex labels but not with an edge label
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (conn.vname AS cvname, conn.ename AS cename));
+-- el1 is associated with only edges, and cannot qualify a vertex
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1 | vl1)-[conn]->(dest) COLUMNS (conn.ename AS cename));
+-- star in COLUMNs is specified but not supported
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*));
+-- star anywhere else is not allowed as a property reference
+SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[IS customer_orders]->(o IS orders) COLUMNS (c.name));
+
+-- select all the properties across all the labels associated with a given type
+-- of graph element
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname, src.vprop1 AS svp1, src.vprop2 AS svp2, src.lprop1 AS slp1, dest.vprop1 AS dvp1, dest.vprop2 AS dvp2, dest.lprop1 AS dlp1, conn.eprop1 AS cep1, conn.lprop2 AS clp2));
+-- three label disjunction
+SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS vl1 | vl2 | vl3)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname));
+-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination.
+WITH all_connected_vertices AS (SELECT svn, dvn FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svn, dest.vname AS dvn))),
+    all_vertices AS (SELECT vn FROM GRAPH_TABLE (g1 MATCH (vertex) COLUMNS (vertex.vname AS vn)))
+SELECT vn FROM all_vertices EXCEPT (SELECT svn FROM all_connected_vertices UNION SELECT dvn FROM all_connected_vertices);
+-- query all connections using a label shared by vertices and edges
+SELECT sn, cn, dn FROM GRAPH_TABLE (g1 MATCH (src IS l1)-[conn IS l1]->(dest IS l1) COLUMNS (src.elname AS sn, conn.elname AS cn, dest.elname AS dn));
+
+-- Tests for cyclic path patterns
+CREATE TABLE e2_1 (
+    id_2_1 int,
+    id_2_2 int,
+    id_1 int,
+    ename varchar(10),
+    eprop1 int
+);
+
+CREATE TABLE e3_2 (
+    id_3 int,
+    id_2_1 int,
+    id_2_2 int,
+    ename varchar(10),
+    eprop1 int
+);
+
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e2_1 KEY (id_2_1, id_2_2, id_1)
+            SOURCE KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            DESTINATION KEY (id_1) REFERENCES v1 (id)
+            LABEL el1 PROPERTIES (eprop1, ename)
+            LABEL l1 PROPERTIES (ename AS elname),
+        e3_2 KEY (id_3, id_2_1, id_2_2)
+            SOURCE KEY (id_3) REFERENCES v3 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+
+INSERT INTO e1_2 VALUES (3, 1000, 3, 'e123', 10007);
+INSERT INTO e2_1 VALUES (1000, 1, 2, 'e211', 10006);
+INSERT INTO e2_1 VALUES (1000, 3, 3, 'e212', 10008);
+INSERT INTO e3_2 VALUES (2002, 1000, 2, 'e321', 10009);
+
+-- cyclic pattern using WHERE clause in graph pattern,
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(c) WHERE a.vname = c.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+-- cyclic pattern using element patterns with the same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+-- cyclic pattern with WHERE clauses in element patterns with the same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 < 2000)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 > 20) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE a.vprop1 between 20 and 2000)->(b WHERE b.vprop1 > 20)->(a WHERE a.vprop1 between 20 and 2000) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+-- labels and elements kinds of element patterns with the same variable name
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.ename AS aename, b.ename AS bename)) ORDER BY 1, 2; -- error
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;  -- error
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through;
+
+-- add loop to test edge patterns with same variable name
+CREATE TABLE e3_3 (
+    src_id int,
+    dest_id int,
+    ename varchar(10),
+    eprop1 int
+);
+
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e3_3 KEY (src_id, dest_id)
+            SOURCE KEY (src_id) REFERENCES v3 (id)
+            DESTINATION KEY (dest_id) REFERENCES v3 (id)
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+
+INSERT INTO e3_3 VALUES (2003, 2003, 'e331', 10010);
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS aname, b.ename AS bname, c.vname AS cname, d.vname AS dname)); --error
+-- the looping edge should be reported only once even when edge pattern with any direction is used
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[c]-(a) COLUMNS (a.vname AS self, c.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-(a) COLUMNS (a.vname AS self));
+
+-- test collation specified in the expression
+INSERT INTO e3_3 VALUES (2003, 2003, 'E331', 10011);
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name COLLATE "C" ASC;
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b IS el2 WHERE b.ename > 'E331' COLLATE "C"]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.ename > 'E331' COLLATE "C" COLUMNS (a.vname AS self, b.ename AS loop_name));
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) WHERE loop_name > 'E331' COLLATE "C";
+
+-- property graph with some of the elements, labels and properties same as the
+-- previous one. Test whether components from the specified property graph are
+-- used. Also test explicit collation specification in property.
+CREATE PROPERTY GRAPH g2
+    VERTEX TABLES (
+        v1
+            LABEL l1 PROPERTIES ('g2.' || vname COLLATE "C" AS elname),
+        v2 KEY (id1, id2)
+            LABEL l1 PROPERTIES ('g2.' || vname COLLATE "C" AS elname),
+        v3
+            LABEL l1 PROPERTIES ('g2.' || vname COLLATE "C" AS elname)
+    )
+    EDGE TABLES (
+        e1_2 key (id_1, id_2_1, id_2_2)
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname),
+        e1_3
+            SOURCE KEY (id_1) REFERENCES v1 (id)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname),
+        e2_3 KEY (id_2_1, id_2_2, id_3)
+            SOURCE KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            DESTINATION KEY (id_3) REFERENCES v3 (id)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname),
+        e3_3 KEY (src_id, dest_id)
+            SOURCE KEY (src_id) REFERENCES v3 (id)
+            DESTINATION KEY (src_id) REFERENCES v3 (id)
+            LABEL l1 PROPERTIES ('g2.' || ename COLLATE "C" AS elname)
+    );
+SELECT sn, cn, dn FROM GRAPH_TABLE (g2 MATCH (src IS l1)-[conn IS l1]->(dest IS l1) COLUMNS (src.elname AS sn, conn.elname AS cn, dest.elname AS dn)) ORDER BY 1, 2, 3;
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b WHERE b.elname > 'g2.E331']->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name));
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.elname > 'g2.E331' COLUMNS (a.elname AS self, b.elname AS loop_name));
+SELECT * FROM GRAPH_TABLE (g2 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.elname AS self, b.elname AS loop_name)) WHERE loop_name > 'g2.E331';
+
+-- prepared statements, any changes to the property graph should be reflected in
+-- the already prepared statements
+PREPARE cyclestmt AS SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)->(b IS l1)->(c IS l1) WHERE a.elname = c.elname COLUMNS (a.elname AS self, b.elname AS through)) ORDER BY self, through;
+EXECUTE cyclestmt;
+ALTER PROPERTY GRAPH g1 DROP EDGE TABLES (e3_2, e3_3);
+EXECUTE cyclestmt;
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e3_2 KEY (id_3, id_2_1, id_2_2)
+            SOURCE KEY (id_3) REFERENCES v3 (id)
+            DESTINATION KEY (id_2_1, id_2_2) REFERENCES v2 (id1, id2)
+            LABEL el2 PROPERTIES (ename, eprop1 * 10 AS lprop2)
+            LABEL l1 PROPERTIES (ename AS elname)
+    );
+EXECUTE cyclestmt;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 DROP LABEL l1;
+EXECUTE cyclestmt;
+ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 ADD LABEL l1 PROPERTIES (vname AS elname);
+EXECUTE cyclestmt;
+ALTER PROPERTY GRAPH g1
+    ADD EDGE TABLES (
+        e3_3 KEY (src_id, dest_id)
+            SOURCE KEY (src_id) REFERENCES v3 (id)
+            DESTINATION KEY (src_id) REFERENCES v3 (id)
+            LABEL l2 PROPERTIES (ename AS elname)
+    );
+PREPARE loopstmt AS SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[e IS l2]->(a) COLUMNS (e.elname AS loop)) ORDER BY loop COLLATE "C" ASC;
+EXECUTE loopstmt;
+ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 DROP PROPERTIES (elname);
+EXECUTE loopstmt; -- error
+ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 ADD PROPERTIES ((ename || '_new')::varchar(10) AS elname);
+EXECUTE loopstmt;
+
+-- inheritance and partitioning
+CREATE TABLE pv (id int, val int);
+CREATE TABLE cv1 () INHERITS (pv);
+CREATE TABLE cv2 () INHERITS (pv);
+INSERT INTO pv VALUES (1, 10);
+INSERT INTO cv1 VALUES (2, 20);
+INSERT INTO cv2 VALUES (3, 30);
+CREATE TABLE pe (id int, src int, dest int, val int);
+CREATE TABLE ce1 () INHERITS (pe);
+CREATE TABLE ce2 () INHERITS (pe);
+INSERT INTO pe VALUES (1, 1, 2, 100);
+INSERT INTO ce1 VALUES (2, 2, 3, 200);
+INSERT INTO ce2 VALUES (3, 3, 1, 300);
+CREATE PROPERTY GRAPH g3
+    NODE TABLES (
+        pv KEY (id)
+    )
+    RELATIONSHIP TABLES (
+        pe KEY (id)
+            SOURCE KEY(src) REFERENCES pv(id)
+            DESTINATION KEY(dest) REFERENCES pv(id)
+    );
+SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+-- temporary property graph
+CREATE TEMPORARY PROPERTY GRAPH gtmp
+    VERTEX TABLES (
+        pv KEY (id)
+    )
+    EDGE TABLES (
+        pe KEY (id)
+            SOURCE KEY(src) REFERENCES pv(id)
+            DESTINATION KEY(dest) REFERENCES pv(id)
+    );
+SELECT * FROM GRAPH_TABLE (gtmp MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+
+CREATE TABLE ptnv (id int PRIMARY KEY, val int) PARTITION BY LIST(id);
+CREATE TABLE prtv1 PARTITION OF ptnv FOR VALUES IN (1, 2);
+CREATE TABLE prtv2 PARTITION OF ptnv FOR VALUES IN (3);
+INSERT INTO ptnv VALUES (1, 10), (2, 20), (3, 30);
+CREATE TABLE ptne (id int PRIMARY KEY, src int REFERENCES ptnv(id), dest int REFERENCES ptnv(id), val int) PARTITION BY LIST(id);
+CREATE TABLE ptne1 PARTITION OF ptne FOR VALUES IN (1, 2);
+CREATE TABLE ptne2 PARTITION OF ptne FOR VALUES IN (3);
+INSERT INTO ptne VALUES (1, 1, 2, 100), (2, 2, 3, 200), (3, 3, 1, 300);
+CREATE PROPERTY GRAPH g4
+    VERTEX TABLES (ptnv)
+    EDGE TABLES (
+        ptne
+            SOURCE KEY (src) REFERENCES ptnv(id)
+            DESTINATION KEY (dest) REFERENCES ptnv(id)
+    );
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s IS ptnv)-[e IS ptne]->(d IS ptnv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+-- edges from the same vertex in both directions connecting to other vertexes in the same table
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s)-[e]-(d) WHERE s.id = 3 COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3;
+
+-- ruleutils reverse parsing
+CREATE VIEW customers_us AS SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name)) ORDER BY customer_name, product_name;
+SELECT pg_get_viewdef('customers_us'::regclass);
+
+-- test view/graph nesting
+
+CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers;
+SELECT * FROM customers;
+SELECT * FROM customers_view;
+
+CREATE PROPERTY GRAPH myshop2
+    VERTEX TABLES (
+        products,
+        customers_view KEY (customer_id) LABEL customers,
+        orders
+    )
+    EDGE TABLES (
+        order_items KEY (order_items_id)
+            SOURCE KEY (order_id) REFERENCES orders (order_id)
+            DESTINATION KEY (product_no) REFERENCES products (product_no),
+        customer_orders KEY (customer_orders_id)
+            SOURCE KEY (customer_id) REFERENCES customers_view (customer_id)
+            DESTINATION KEY (order_id) REFERENCES orders (order_id)
+    );
+
+CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE (myshop2 MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name_redacted AS customer_name_redacted));
+
+SELECT * FROM customers_us_redacted;
+
+-- GRAPH_TABLE in UDFs
+CREATE FUNCTION out_degree(sname varchar) RETURNS varchar AS $$
+DECLARE
+    out_degree int;
+BEGIN
+    SELECT count(*) INTO out_degree FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)->() COLUMNS (src.vname));
+    RETURN out_degree;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION direct_connections(sname varchar)
+RETURNS TABLE (cname varchar, dname varchar)
+AS $$
+    SELECT cname, dname FROM GRAPH_TABLE (g1 MATCH (src WHERE src.vname = sname)-[conn]->(dst) COLUMNS (conn.ename AS cname, dst.vname AS dname));
+$$ LANGUAGE SQL;
+
+SELECT sname, out_degree(sname) FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname));
+SELECT sname, cname, dname FROM GRAPH_TABLE (g1 MATCH (src IS vl1) COLUMNS (src.vname AS sname)), LATERAL direct_connections(sname);
+
+-- GRAPH_TABLE joined to a regular table
+SELECT * FROM customers co, GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = co.address)-[IS customer_orders]->(o IS orders) COLUMNS (cg.name_redacted AS customer_name_redacted)) WHERE co.customer_id = 1;
+
+-- graph table in a subquery
+SELECT * FROM customers co WHERE co.customer_id = (SELECT customer_id FROM GRAPH_TABLE (myshop2 MATCH (cg IS customers WHERE cg.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cg.customer_id)));
+
+-- query within graph table
+SELECT sname, dname FROM GRAPH_TABLE (g1 MATCH (src)->(dest) WHERE src.vprop1 > (SELECT max(v1.vprop1) FROM v1) COLUMNS(src.vname AS sname, dest.vname AS dname));
+SELECT sname, dname FROM GRAPH_TABLE (g1 MATCH (src)->(dest) WHERE out_degree(src.vname) > (SELECT max(out_degree(nname)) FROM GRAPH_TABLE (g1 MATCH (node) COLUMNS (node.vname AS nname))) COLUMNS(src.vname AS sname, dest.vname AS dname));
+
+-- leave the objects behind for pg_upgrade/pg_dump tests
diff --git a/src/test/regress/sql/graph_table_rls.sql b/src/test/regress/sql/graph_table_rls.sql
new file mode 100644 (file)
index 0000000..044bc27
--- /dev/null
@@ -0,0 +1,363 @@
+--
+--Test RLS with GRAPH_TABLE
+--
+--This test verifies that Row Level Security (RLS) policies are correctly
+--enforced when querying tables underlying property graphs using GRAPH_TABLE.
+--graph_table.sql has extensive tests covering interaction of GRAPH_TABLE with
+--other query constructs. rowsecurity.sql has extensive coverage of interaction
+--of RLS and other features of PostgreSQL. This test along with those two tests
+--is sufficient to make sure that all combinations of RLS and GRAPH_TABLE will
+--work as expected.
+
+-- Clean up in case a prior regression run failed
+
+-- Suppress NOTICE messages when users/groups don't exist
+SET client_min_messages TO 'warning';
+
+DROP USER IF EXISTS regress_graph_rls_alice;
+DROP USER IF EXISTS regress_graph_rls_bob;
+DROP USER IF EXISTS regress_graph_rls_carol;
+DROP USER IF EXISTS regress_graph_rls_dave;
+DROP USER IF EXISTS regress_graph_rls_exempt_user;
+DROP ROLE IF EXISTS regress_graph_rls_group1;
+DROP ROLE IF EXISTS regress_graph_rls_group2;
+
+DROP SCHEMA IF EXISTS graph_rls_schema CASCADE;
+
+RESET client_min_messages;
+
+-- initial setup
+CREATE USER regress_graph_rls_alice NOLOGIN;
+CREATE USER regress_graph_rls_bob NOLOGIN;
+CREATE USER regress_graph_rls_carol NOLOGIN;
+CREATE USER regress_graph_rls_dave NOLOGIN;
+CREATE USER regress_graph_rls_exempt_user BYPASSRLS NOLOGIN;
+CREATE ROLE regress_graph_rls_group1 NOLOGIN;
+CREATE ROLE regress_graph_rls_group2 NOLOGIN;
+
+GRANT regress_graph_rls_group1 TO regress_graph_rls_dave;
+GRANT regress_graph_rls_group2 TO regress_graph_rls_bob;
+
+CREATE SCHEMA graph_rls_schema;
+GRANT ALL ON SCHEMA graph_rls_schema to public;
+SET search_path = graph_rls_schema;
+
+-- setup for leaky-function tests
+CREATE FUNCTION f_leak(text) RETURNS bool
+    COST 0.0000001 LANGUAGE plpgsql
+    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+
+CREATE TABLE users (uid int PRIMARY KEY, pguser name, seclv int);
+INSERT INTO users VALUES
+    (1, 'regress_graph_rls_alice', 99),
+    (2, 'regress_graph_rls_bob', 1),
+    (3, 'regress_graph_rls_carol', 2),
+    (4, 'regress_graph_rls_dave', 3);
+GRANT SELECT ON users TO public;
+
+CREATE TABLE document_people (
+    did int,
+    dlevel int,
+    dtitle text);
+INSERT INTO document_people VALUES
+    ( 1, 2, 'Politicians'),
+    ( 2, 3, 'Artists'),
+    ( 3, 1, 'Scientists'),
+    ( 4, 100, 'Unspeakables');
+GRANT SELECT ON document_people TO public;
+
+CREATE TABLE accessed (
+    aid int,
+    uid int,
+    did int);
+INSERT INTO accessed VALUES
+    (1, 2, 3),
+    (2, 3, 1),
+    (3, 3, 3),
+    (4, 4, 3),
+    (5, 1, 1),
+    (6, 1, 4),
+    (7, 4, 2);
+GRANT SELECT ON accessed TO public;
+CREATE PROPERTY GRAPH cabinet
+    VERTEX TABLES (users KEY (uid),
+                   document_people AS document KEY (did))
+    EDGE TABLES (accessed KEY (aid)
+                 SOURCE KEY (uid) REFERENCES users (uid)
+                 DESTINATION KEY (did) REFERENCES document (did));
+GRANT SELECT ON cabinet TO public;
+
+--
+-- Basic RLS tests
+--
+ALTER TABLE document_people ENABLE ROW LEVEL SECURITY;
+-- user's security level must be higher than or equal to document's
+CREATE POLICY p1 ON document_people AS PERMISSIVE
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+-- but Dave isn't allowed to see document titled 'Scientists'
+CREATE POLICY p2 ON document_people AS RESTRICTIVE TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+CREATE POLICY p3 ON document_people AS RESTRICTIVE TO regress_graph_rls_group1
+    USING (dlevel < 3);
+CREATE POLICY p4 ON document_people AS PERMISSIVE TO regress_graph_rls_group2
+    USING (dlevel < 3);
+
+SET row_security TO ON;
+
+-- Use the same query in all the test cases below. Prepare it once and
+-- use multiple times. Apart from making the test file shorter and avoiding
+-- duplication, it also tests that a prepared statement correctly reflect changes
+-- to RLS policies, session user or RLS settings.
+PREPARE graph_rls_query AS
+SELECT * FROM GRAPH_TABLE (cabinet
+                           MATCH (u IS users)-[a IS accessed]->(d IS document)
+                           WHERE f_leak(d.dtitle)
+                           COLUMNS (u.pguser, a.aid, d.dtitle, d.dlevel))
+         ORDER BY 1, 2, 3, 4;
+
+-- viewpoint from regress_graph_rls_bob
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+
+-- viewpoint from regress_graph_rls_carol
+SET SESSION AUTHORIZATION regress_graph_rls_carol;
+EXECUTE graph_rls_query;
+
+-- viewpoint from regress_graph_rls_dave
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+
+-- RLS policy does not apply to table owner when RLS enabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+EXECUTE graph_rls_query;
+
+SET row_security TO OFF;
+-- database superuser does bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+
+-- RLS policy does not apply to table owner when RLS disabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP PROPERTY GRAPH cabinet;
+
+--
+-- Table inheritance
+--
+ALTER TABLE document_people ADD COLUMN category text DEFAULT 'People';
+CREATE TABLE document_places (
+    did int,
+    dlevel int,
+    dtitle text,
+    category text DEFAULT 'Places');
+INSERT INTO document_places VALUES
+    ( 5, 1, 'Paris'),
+    ( 6, 2, 'Tokyo'),
+    ( 7, 3, 'New York');
+GRANT SELECT ON document_places TO public;
+-- Setup inheritance
+CREATE TABLE document (
+    did int,
+    dlevel int,
+    dtitle text);
+GRANT SELECT ON document TO public;
+ALTER TABLE document_people INHERIT document;
+ALTER TABLE document_places INHERIT document;
+INSERT INTO accessed VALUES
+    (11, 2, 5),
+    (12, 3, 6),
+    (13, 1, 7),
+    (14, 4, 5),
+    (15, 1, 6);
+
+-- Enable RLS and move policies p1 and p2 to parent table but leave p3 and p4 on
+-- child table. The policies on child table are not applied when querying parent
+-- table.
+ALTER TABLE document ENABLE ROW LEVEL SECURITY;
+DROP POLICY p1 ON document_people;
+DROP POLICY p2 ON document_people;
+CREATE POLICY p1 ON document AS PERMISSIVE
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+CREATE POLICY p2 ON document AS RESTRICTIVE TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+
+CREATE PROPERTY GRAPH cabinet
+    VERTEX TABLES (users KEY (uid), document KEY (did))
+    EDGE TABLES (accessed KEY (aid)
+                 SOURCE KEY (uid) REFERENCES users (uid)
+                 DESTINATION KEY (did) REFERENCES document (did));
+GRANT SELECT ON cabinet TO public;
+
+SET row_security TO ON;
+
+-- viewpoint from regress_graph_rls_bob
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+
+-- viewpoint from regress_graph_rls_carol
+SET SESSION AUTHORIZATION regress_graph_rls_carol;
+EXECUTE graph_rls_query;
+
+-- viewpoint from regress_graph_rls_dave
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+
+-- RLS policy does not apply to table owner when RLS enabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+EXECUTE graph_rls_query;
+
+SET row_security TO OFF;
+-- database superuser does bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+
+-- RLS policy does not apply to table owner when RLS disabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+
+-- cleanup
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP PROPERTY GRAPH cabinet;
+ALTER TABLE document_people NO INHERIT document;
+ALTER TABLE document_places NO INHERIT document;
+DROP TABLE document;
+
+--
+-- Partitioned Tables
+--
+CREATE TABLE document (
+    did int,
+    dlevel int,
+    dtitle text,
+    category text) PARTITION BY LIST (category);
+GRANT SELECT ON document TO public;
+ALTER TABLE document ATTACH PARTITION document_people FOR VALUES IN ('People');
+ALTER TABLE document ATTACH PARTITION document_places FOR VALUES IN ('Places');
+-- Enable RLS on partitioned table
+ALTER TABLE document ENABLE ROW LEVEL SECURITY;
+-- create policies on partitioned table
+CREATE POLICY p1 ON document AS PERMISSIVE
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+CREATE POLICY p2 ON document AS RESTRICTIVE TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+
+CREATE PROPERTY GRAPH cabinet
+    VERTEX TABLES (users KEY (uid), document KEY (did))
+    EDGE TABLES (accessed KEY (aid)
+                 SOURCE KEY (uid) REFERENCES users (uid)
+                 DESTINATION KEY (did) REFERENCES document (did));
+GRANT SELECT ON cabinet TO public;
+SET row_security TO ON;
+
+-- viewpoint from regress_graph_rls_bob
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+
+-- viewpoint from regress_graph_rls_carol
+SET SESSION AUTHORIZATION regress_graph_rls_carol;
+EXECUTE graph_rls_query;
+
+-- viewpoint from regress_graph_rls_dave
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+
+-- RLS policy does not apply to table owner when RLS enabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+EXECUTE graph_rls_query;
+
+SET row_security TO OFF;
+-- database superuser does bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+
+-- RLS policy does not apply to table owner when RLS disabled
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+
+--
+-- Recursion through GRAPH_TABLE also throws error
+--
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+SET row_security TO ON;
+-- Create a policy on document that references document itself via GRAPH_TABLE
+CREATE POLICY pr ON document TO regress_graph_rls_dave
+    USING (EXISTS (SELECT 1 FROM GRAPH_TABLE (cabinet
+                           MATCH (u IS users)-[a IS accessed]->(d IS document)
+                           WHERE u.pguser = current_user
+                           COLUMNS (a.aid))));
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query; -- error
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP POLICY pr ON document;
+
+--
+-- Command specific policy. Since GRAPH_TABLE can be used in only SELECT, test
+-- only FOR SELECT policies.
+--
+DROP POLICY p1 ON document;
+DROP POLICY p2 ON document;
+CREATE POLICY p1 ON document AS PERMISSIVE
+    FOR SELECT
+    USING (dlevel <= (SELECT seclv FROM users WHERE pguser = current_user));
+CREATE POLICY p2 ON document AS RESTRICTIVE
+    FOR SELECT TO regress_graph_rls_dave
+    USING (dtitle <> 'Scientists');
+SET SESSION AUTHORIZATION regress_graph_rls_dave;
+EXECUTE graph_rls_query;
+
+--
+-- Default deny policy, FORCE ROW LEVEL SECURITY
+--
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+DROP POLICY p1 ON document;
+DROP POLICY p2 ON document;
+-- default deny policy applies to non-owners, non-rls-exempt and non-superusers
+SET SESSION AUTHORIZATION regress_graph_rls_bob;
+EXECUTE graph_rls_query;
+-- Deny RLS policy does not apply to table owner, superuser or RLS exempt user
+SET SESSION AUTHORIZATION regress_graph_rls_exempt_user;
+EXECUTE graph_rls_query;
+RESET SESSION AUTHORIZATION;
+EXECUTE graph_rls_query;
+SET SESSION AUTHORIZATION regress_graph_rls_alice;
+EXECUTE graph_rls_query;
+-- FORCE ROW LEVEL SECURITY applies RLS to owners too
+ALTER TABLE document FORCE ROW LEVEL SECURITY;
+EXECUTE graph_rls_query;
+SET row_security TO OFF;
+EXECUTE graph_rls_query; -- error
+
+-- Clean up
+DEALLOCATE graph_rls_query;
+
+-- leave objects behind for pg_upgrade/pg_dump tests
index 1a6c61f49d54395381ed825ebbb46051d1f5c497..4f23ad5046f3c40ac43148c4e5c153b1b7ad802d 100644 (file)
@@ -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;
@@ -65,7 +66,8 @@ DECLARE
     objtype text;
 BEGIN
     FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
-        ('toast table column'), ('view column'), ('materialized view column')
+        ('toast table column'), ('view column'), ('materialized view column'),
+        ('property graph element'), ('property graph label'), ('property graph property')
     LOOP
         BEGIN
             PERFORM pg_get_object_address(objtype, '{one}', '{}');
@@ -90,7 +92,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 +165,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}'),
@@ -278,6 +281,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
     ('pg_parameter_acl'::regclass, 0, 0), -- no parameter ACL
     ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
+    ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
+    ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
     ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
index 540f73ea9b1719d5aa67031bb40b8fcd89b38953..e34c65fc1b2ce868b29bb372b5e9cc9db1076d1f 100644 (file)
@@ -1838,6 +1838,60 @@ revoke select on dep_priv_test from regress_priv_user4 cascade;
 set session role regress_priv_user1;
 drop table dep_priv_test;
 
+--
+-- Property graphs
+--
+set session role regress_priv_user1;
+create property graph ptg1
+       vertex tables (
+               atest5 key (four)
+                       default label properties (four)
+                       label lttc properties (three as lttck),
+               atest1 key (a)
+                       default label
+                       label lttc properties (a as lttck),
+               atest2 key (col1)
+                       default label
+                       label ltv properties (col1 as ltvk));
+-- select privileges on property graph as well as table
+select * from graph_table (ptg1 match (is atest5) COLUMNS (1 as value)) limit 0; -- ok
+grant select on ptg1 to regress_priv_user2;
+set session role regress_priv_user2;
+select * from graph_table (ptg1 match (is atest1) COLUMNS (1 as value)) limit 0; -- ok
+-- select privileges on property graph but not table
+select * from graph_table (ptg1 match (is atest5) COLUMNS (1 as value)) limit 0; -- fails
+select * from graph_table (ptg1 match (is lttc) COLUMNS (1 as value)) limit 0; -- fails
+set session role regress_priv_user3;
+-- select privileges on table but not property graph
+select * from graph_table (ptg1 match (is atest1) COLUMNS (1 as value)) limit 0; -- fails
+-- select privileges on neither
+select * from graph_table (ptg1 match (is atest5) COLUMNS (1 as value)) limit 0; -- fails
+-- column privileges
+set session role regress_priv_user1;
+select * from graph_table (ptg1 match (v is lttc) COLUMNS (v.lttck)) limit 0; -- ok
+grant select on ptg1 to regress_priv_user4;
+set session role regress_priv_user4;
+select * from graph_table (ptg1 match (a is atest5) COLUMNS (a.four)) limit 0; -- ok
+select * from graph_table (ptg1 match (v is lttc) COLUMNS (v.lttck)) limit 0; -- fail
+-- access property graph through security definer view
+set session role regress_priv_user4;
+create view atpgv1 as select * from graph_table (ptg1 match (is atest1) COLUMNS (1 as value)) limit 0;
+grant select on atpgv1 to regress_priv_user3;
+select * from atpgv1; -- ok
+set session role regress_priv_user3;
+select * from atpgv1; -- ok
+set session role regress_priv_user4;
+create view atpgv2 as select * from graph_table (ptg1 match (v is ltv) COLUMNS (v.ltvk)) limit 0;
+-- though the session user is the owner of the view and also has access to the
+-- property graph, it does not have access to a table referenced in the graph
+-- pattern
+select * from atpgv2; -- fail
+grant select on atpgv2 to regress_priv_user2;
+-- The user who otherwise does not have access to the property graph, gets
+-- access to it through a security definer view and uses it successfully since
+-- it has access to the tables referenced in the graph pattern.
+set session role regress_priv_user2;
+select * from atpgv2; -- ok
 
 -- clean up
 
@@ -1845,6 +1899,10 @@ drop table dep_priv_test;
 
 drop sequence x_seq;
 
+drop view atpgv1;
+drop view atpgv2;
+drop property graph ptg1;
+
 DROP AGGREGATE priv_testagg1(int);
 DROP FUNCTION priv_testfunc2(int);
 DROP FUNCTION priv_testfunc4(boolean);
index 7d83c92f3b7961f1d56deae3ec41cc3e42cdb238..52f8603a7be35e90f2c67de474205c883043f5c7 100644 (file)
@@ -91,6 +91,8 @@ AlterOpFamilyStmt
 AlterOperatorStmt
 AlterOwnerStmt
 AlterPolicyStmt
+AlterPropGraphElementKind
+AlterPropGraphStmt
 AlterPublicationAction
 AlterPublicationStmt
 AlterReplicationSlotCmd
@@ -567,6 +569,7 @@ CreateOpClassStmt
 CreateOpFamilyStmt
 CreatePLangStmt
 CreatePolicyStmt
+CreatePropGraphStmt
 CreatePublicationStmt
 CreateRangeStmt
 CreateReplicationSlotCmd
@@ -904,6 +907,11 @@ FormData_pg_opfamily
 FormData_pg_partitioned_table
 FormData_pg_policy
 FormData_pg_proc
+FormData_pg_propgraph_element
+FormData_pg_propgraph_element_label
+FormData_pg_propgraph_label
+FormData_pg_propgraph_label_property
+FormData_pg_propgraph_property
 FormData_pg_publication
 FormData_pg_publication_namespace
 FormData_pg_publication_rel
@@ -1126,6 +1134,12 @@ GrantRoleOptions
 GrantRoleStmt
 GrantStmt
 GrantTargetType
+GraphElementPattern
+GraphElementPatternKind
+GraphLabelRef
+GraphPattern
+GraphPropertyRef
+GraphTableParseState
 Group
 GroupByOrdering
 GroupClause
@@ -2380,6 +2394,10 @@ ProjectSetState
 ProjectionInfo
 ProjectionPath
 PromptInterruptContext
+PropGraphEdge
+PropGraphLabelAndProperties
+PropGraphProperties
+PropGraphVertex
 ProtocolVersion
 PrsStorage
 PruneFreezeParams
@@ -2462,6 +2480,7 @@ Range
 RangeBound
 RangeBox
 RangeFunction
+RangeGraphTable
 RangeIOData
 RangeQueryClause
 RangeSubselect