From ddb0118461e4c49d1da9fa90691263a3c666116c Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 27 Jun 2024 14:10:44 +0200 Subject: [PATCH v1] WIP: SQL Property Graph Queries (SQL/PGQ) Implementation of SQL property graph queries, according to SQL/PGQ standard (ISO 9075-16:2023). Author: Peter Eisentraut Author: Ashutosh Bapat Discussion: https://www.postgresql.org/message-id/flat/a855795d-e697-4fa5-8698-d20122126567@eisentraut.org --- doc/src/sgml/catalogs.sgml | 471 ++++- doc/src/sgml/ddl.sgml | 225 ++- doc/src/sgml/features.sgml | 4 +- doc/src/sgml/func.sgml | 15 + doc/src/sgml/information_schema.sgml | 1092 +++++++++++ .../sgml/keywords/sql2023-16-nonreserved.txt | 27 + doc/src/sgml/keywords/sql2023-16-reserved.txt | 12 + doc/src/sgml/queries.sgml | 168 ++ doc/src/sgml/ref/allfiles.sgml | 3 + doc/src/sgml/ref/alter_extension.sgml | 3 +- doc/src/sgml/ref/alter_property_graph.sgml | 299 +++ doc/src/sgml/ref/comment.sgml | 1 + doc/src/sgml/ref/create_property_graph.sgml | 310 +++ doc/src/sgml/ref/drop_property_graph.sgml | 111 ++ doc/src/sgml/ref/grant.sgml | 7 +- doc/src/sgml/ref/psql-ref.sgml | 13 +- doc/src/sgml/ref/revoke.sgml | 7 + doc/src/sgml/ref/security_label.sgml | 1 + doc/src/sgml/ref/select.sgml | 43 + doc/src/sgml/reference.sgml | 3 + src/backend/catalog/aclchk.c | 24 + src/backend/catalog/dependency.c | 11 + src/backend/catalog/information_schema.sql | 364 ++++ src/backend/catalog/objectaddress.c | 306 +++ src/backend/catalog/pg_class.c | 2 + src/backend/catalog/sql_features.txt | 100 + src/backend/commands/Makefile | 1 + src/backend/commands/alter.c | 28 +- src/backend/commands/dropcmds.c | 1 + src/backend/commands/event_trigger.c | 2 + src/backend/commands/meson.build | 1 + src/backend/commands/propgraphcmds.c | 1706 +++++++++++++++++ src/backend/commands/seclabel.c | 1 + src/backend/commands/tablecmds.c | 16 + src/backend/executor/execMain.c | 15 +- src/backend/nodes/nodeFuncs.c | 72 + src/backend/nodes/outfuncs.c | 5 + src/backend/nodes/readfuncs.c | 5 + src/backend/optimizer/path/allpaths.c | 4 + src/backend/optimizer/prep/prepjointree.c | 8 + src/backend/parser/Makefile | 1 + src/backend/parser/analyze.c | 298 +-- src/backend/parser/gram.y | 704 ++++++- src/backend/parser/meson.build | 1 + src/backend/parser/parse_agg.c | 11 + src/backend/parser/parse_clause.c | 89 + src/backend/parser/parse_collate.c | 7 + src/backend/parser/parse_expr.c | 6 + src/backend/parser/parse_func.c | 3 + src/backend/parser/parse_graphtable.c | 209 ++ src/backend/parser/parse_relation.c | 94 + src/backend/parser/parse_target.c | 5 + src/backend/parser/scan.l | 13 +- src/backend/rewrite/Makefile | 1 + src/backend/rewrite/meson.build | 1 + src/backend/rewrite/rewriteGraphTable.c | 1098 +++++++++++ src/backend/rewrite/rewriteHandler.c | 11 + src/backend/tcop/utility.c | 34 + src/backend/utils/adt/ruleutils.c | 537 ++++++ src/backend/utils/cache/lsyscache.c | 38 + src/bin/pg_dump/common.c | 3 +- src/bin/pg_dump/pg_backup_archiver.c | 1 + src/bin/pg_dump/pg_dump.c | 67 +- src/bin/pg_dump/t/002_pg_dump.pl | 11 + src/bin/psql/command.c | 3 +- src/bin/psql/describe.c | 43 +- src/bin/psql/help.c | 1 + src/bin/psql/tab-complete.c | 67 +- src/fe_utils/psqlscan.l | 8 +- src/include/catalog/Makefile | 7 +- src/include/catalog/meson.build | 5 + src/include/catalog/pg_class.h | 1 + src/include/catalog/pg_proc.dat | 3 + src/include/catalog/pg_propgraph_element.h | 103 + .../catalog/pg_propgraph_element_label.h | 51 + src/include/catalog/pg_propgraph_label.h | 51 + .../catalog/pg_propgraph_label_property.h | 59 + src/include/catalog/pg_propgraph_property.h | 54 + src/include/commands/propgraphcmds.h | 23 + src/include/nodes/parsenodes.h | 139 ++ src/include/nodes/primnodes.h | 22 + src/include/parser/analyze.h | 3 + src/include/parser/kwlist.h | 9 + src/include/parser/parse_graphtable.h | 31 + src/include/parser/parse_node.h | 1 + src/include/parser/parse_relation.h | 8 + src/include/rewrite/rewriteGraphTable.h | 21 + src/include/tcop/cmdtaglist.h | 3 + src/include/utils/acl.h | 1 + src/include/utils/lsyscache.h | 3 + src/interfaces/ecpg/preproc/parse.pl | 9 +- src/interfaces/ecpg/preproc/pgc.l | 12 +- src/pl/plpgsql/src/pl_gram.y | 1 + src/test/regress/expected/alter_generic.out | 51 +- .../expected/create_property_graph.out | 496 +++++ src/test/regress/expected/graph_table.out | 484 +++++ src/test/regress/expected/object_address.out | 14 +- src/test/regress/expected/oidjoins.out | 11 + src/test/regress/parallel_schedule | 4 +- src/test/regress/sql/alter_generic.sql | 34 + .../regress/sql/create_property_graph.sql | 190 ++ src/test/regress/sql/graph_table.sql | 336 ++++ src/test/regress/sql/object_address.sql | 4 +- src/tools/pgindent/typedefs.list | 21 + 104 files changed, 10915 insertions(+), 202 deletions(-) create mode 100644 doc/src/sgml/keywords/sql2023-16-nonreserved.txt create mode 100644 doc/src/sgml/keywords/sql2023-16-reserved.txt create mode 100644 doc/src/sgml/ref/alter_property_graph.sgml create mode 100644 doc/src/sgml/ref/create_property_graph.sgml create mode 100644 doc/src/sgml/ref/drop_property_graph.sgml create mode 100644 src/backend/commands/propgraphcmds.c create mode 100644 src/backend/parser/parse_graphtable.c create mode 100644 src/backend/rewrite/rewriteGraphTable.c create mode 100644 src/include/catalog/pg_propgraph_element.h create mode 100644 src/include/catalog/pg_propgraph_element_label.h create mode 100644 src/include/catalog/pg_propgraph_label.h create mode 100644 src/include/catalog/pg_propgraph_label_property.h create mode 100644 src/include/catalog/pg_propgraph_property.h create mode 100644 src/include/commands/propgraphcmds.h create mode 100644 src/include/parser/parse_graphtable.h create mode 100644 src/include/rewrite/rewriteGraphTable.h create mode 100644 src/test/regress/expected/create_property_graph.out create mode 100644 src/test/regress/expected/graph_table.out create mode 100644 src/test/regress/sql/create_property_graph.sql create mode 100644 src/test/regress/sql/graph_table.sql diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index a63cc71efa2..0734b2d756f 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -240,6 +240,31 @@ System Catalogs functions and procedures + + pg_propgraph_element + property graph elements (vertices and edges) + + + + pg_propgraph_element_label + property graph links between elements and labels + + + + pg_propgraph_label + property graph labels + + + + pg_propgraph_label_property + property graph label-specific property definitions + + + + pg_propgraph_property + property graph properties + + pg_publication publications for logical replication @@ -2121,7 +2146,8 @@ <structname>pg_class</structname> Columns c = composite type, f = foreign table, p = partitioned table, - I = partitioned index + I = partitioned index, + g = property graph @@ -6271,6 +6297,449 @@ <structname>pg_proc</structname> Columns + + <structname>pg_propgraph_element</structname> + + + pg_propgraph_element + + + + The catalog pg_propgraph_element stores + information about the vertices and edges of a property graph, collectively + called the elements of the property graph. + + + + <structname>pg_propgraph_element</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + pgepgid oid + (references pg_class.oid) + + + Reference to the property graph that this element belongs to + + + + + + pgerelid oid + (references pg_class.oid) + + + Reference to the table to contains the data for this property graph element + + + + + + pgealias name + + + 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. + + + + + + pgekind char + + + v for a vertex, e for an edge + + + + + + pgesrcvertexid oid + (references pg_propgraph_element.oid) + + + For an edge, a link to the source vertex. (Zero for a vertex.) + + + + + + pgedestvertexid oid + (references pg_propgraph_element.oid) + + + For an edge, a link to the destination vertex. (Zero for a vertex.) + + + + + + pgekey int2[] + (references pg_attribute.attnum) + + + An array of column numbers in the table referenced by + pgerelid that defines the key to use for this + element table. (This defaults to the primary key when the property + graph is created.) + + + + + + pgesrckey int2[] + (references pg_attribute.attnum) + + + For an edge, an array of column numbers in the table referenced by + pgerelid that defines the source key to use + for this element table. (Null for a vertex.) The combination of + pgesrckey and + pgesrcref creates the link between the edge + and the source vertex. + + + + + + pgesrcref int2[] + (references pg_attribute.attnum) + + + For an edge, an array of column numbers in the table reached via + pgesrcvertexid. (Null for a vertex.) The + combination of pgesrckey and + pgesrcref creates the link between the edge + and the source vertex. + + + + + + pgedestkey int2[] + (references pg_attribute.attnum) + + + For an edge, an array of column numbers in the table referenced by + pgerelid that defines the destination key to use + for this element table. (Null for a vertex.) The combination of + pgedestkey and + pgedestref creates the link between the edge + and the destination vertex. + + + + + + pgedestref int2[] + (references pg_attribute.attnum) + + + For an edge, an array of column numbers in the table reached via + pgedestvertexid. (Null for a vertex.) The + combination of pgedestkey and + pgedestref creates the link between the edge + and the destination vertex. + + + + +
+
+ + + <structname>pg_propgraph_element_label</structname> + + + pg_propgraph_element_label + + + + The catalog pg_propgraph_element_label stores + information about which labels apply to which elements. + + + + <structname>pg_propgraph_element_label</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + pgellabelid oid + (references pg_propgraph_label.oid) + + + Reference to the label + + + + + + pgelelid oid + (references pg_propgraph_element.oid) + + + Reference to the element + + + + +
+
+ + + <structname>pg_propgraph_label</structname> + + + pg_propgraph_label + + + + The catalog pg_propgraph_label stores + information about the labels in a property graph. + + + + <structname>pg_propgraph_label</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + pglpgid oid + (references pg_class.oid) + + + Reference to the property graph that this label belongs to + + + + + + pgllabel name + + + The name of the label. This is unique among the labels in a graph. + + + + +
+
+ + + <structname>pg_propgraph_label_property</structname> + + + pg_propgraph_label_property + + + + The catalog pg_propgraph_label_property 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. + + + + <structname>pg_propgraph_label_property</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + plppropid oid + (references pg_propgraph_property.oid) + + + Reference to the property + + + + + + plpellabelid oid + (references pg_propgraph_element_label.oid) + + + Reference to the label (indirectly via + pg_propgraph_element_label, which then links + to pg_propgraph_label) + + + + + + plpexpr pg_node_tree + + + Expression tree (in nodeToString() representation) + for the property's definition. The expression references the table + reached via pg_propgraph_element_label and + pg_propgraph_element. + + + + +
+
+ + + <structname>pg_propgraph_property</structname> + + + pg_propgraph_property + + + + The catalog pg_propgraph_property 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 pg_propgraph_label_property. + + + + <structname>pg_propgraph_property</structname> Columns + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + pgppgid oid + (references pg_class.oid) + + + Reference to the property graph that this property belongs to + + + + + + pgpname name + + + The name of the property. This is unique among the properties in a + graph. + + + + + + pgptypid oid + (references pg_type.oid) + + + 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.) + + + + +
+
+ <structname>pg_publication</structname> diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 9b71c97bdf1..08bdb6f05ea 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1924,7 +1924,7 @@ Privileges Allows SELECT from any column, or specific column(s), of a table, view, materialized - view, or other table-like object. + view, property graph, or other table-like object. Also allows use of COPY TO. This privilege is also needed to reference existing column values in UPDATE, DELETE, @@ -5297,6 +5297,229 @@ Foreign Data + + Property Graphs + + + property graph + + + + A property graph is a way to represent database contents, instead of using + relational structures such as tables. A property graph can then be queried + using graph pattern matching syntax, instead of join queries typical of + relational databases. PostgreSQL implements SQL/PGQHere, + PGQ stands for property graph query. In the jargon of graph + databases, property graph is normally abbreviated as PG, + which is clearly confusing for practioners of PostgreSQL, also usually + abbreviated as PG., which is part of the SQL standard, + where a property graph is defined as a kind of read-only view over + relational tables. So the actual data is still in tables or table-like + objects, but is exposed as a graph for graph querying operations. (This is + in contrast to native graph databases, where the data is stored directly in + a graph structure.) Underneath, both relational queries and graph queries + use the same query planning and execution infrastucture, and in fact + relational and graph queries can be combined and mixed in single queries. + + + + 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. + + + + Consider the following table definitions: + +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) +); + + 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.) + + + + Here is an example how a property graph could be defined on top of these + tables: + +CREATE PROPERTY GRAPH myshop + VERTEX TABLES ( + products, + customers, + orders + ) + EDGE TABLES ( + order_items SOURCE orders DESTINATION products, + customer_orders SOURCE customers DESTINATION orders + ); + + + + + This graph could then be queried like this: + +-- get list of customers active today +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders]->(o IS orders WHERE ordered_when = current_date) COLUMNS (c.name AS customer_name)); + + corresponding approximately to this relational query: + +-- 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; + + + + + 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: + +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) + ); + + + + + As mentioned above, by default, the names of the tables and columns are + exposed as labels and properties, respectively. The clauses IS + customer, IS order, etc. in the + MATCH clause in fact refer to labels, not table names. + + + + One use of labels is to expose a table through a different name in the + graph. For example, in graphs, vertices typically have singular nouns as + labels and edges typically have verbs as labels, such as is, + has, contains, or something specific like + approves. We can introduce such labels into our example + like this: + +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 + ); + + + + + With this definition, we can write a query like this: + +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c:customer)-[:has]->(o:order WHERE ordered_when = current_date) COLUMNS (c.name AS customer_name)); + + With the new labels and using the colon instead of IS, + which are equivalent, the MATCH clause is now more + compact and intuitive. + + + + Another use is to apply the same label to multiple element tables. For + example, consider this additional table: + +CREATE TABLE employees ( + employee_id integer PRIMARY KEY, + employee_name varchar, + ... +); + +and the following graph definition: + +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 + ); + + (In practice, there ought to be an edge linking the + employees table to something, but it is allowed like + this.) Then we can run a query like this (incomplete): + +SELECT ... FROM GRAPH_TABLE (myshop MATCH (:person WHERE name = '...')-[]->... COLUMNS (...)); + + This would automatically consider both the customers and + the employees tables when looking for an edge with the + person label. + + + + 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. + + + + For more details on the syntax for creating property graphs, see CREATE PROPERTY + GRAPH. More details about the graph query syntax is in + . + + + Other Database Objects diff --git a/doc/src/sgml/features.sgml b/doc/src/sgml/features.sgml index 966fd398827..1abe6ccd3d5 100644 --- a/doc/src/sgml/features.sgml +++ b/doc/src/sgml/features.sgml @@ -70,10 +70,10 @@ SQL Conformance The PostgreSQL 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 PostgreSQL. diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 2609269610b..fc20ccb2e3b 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -25865,6 +25865,21 @@ System Catalog Information Functions + + + + pg_get_propgraphdef + + pg_get_propgraphdef ( propgraph oid ) + text + + + Reconstructs the creating command for a property graph. + (This is a decompiled reconstruction, not the original text + of the command.) + + + diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml index 9442b0718c0..c92b65b03a5 100644 --- a/doc/src/sgml/information_schema.sgml +++ b/doc/src/sgml/information_schema.sgml @@ -4171,6 +4171,1098 @@ <structname>parameters</structname> Columns + + <literal>pg_edge_table_components</literal> + + + The view pg_edge_table_components 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). + + + + The source and destination vertex links of edge tables are specified in + CREATE PROPERTY GRAPH and default to foreign keys in + certain cases. + + + + <structname>pg_edge_table_components</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + edge_table_alias sql_identifier + + + The element table alias of the edge table being described + + + + + + vertex_table_alias sql_identifier + + + The element table alias of the source or destination vertex table being linked to + + + + + + edge_end character_data + + + Either SOURCE or DESTINATION; + specifies which edge link is being described. + + + + + + edge_table_column_name sql_identifier + + + Name of the column that is part of the source or destination vertex key in this edge table + + + + + + vertex_table_column_name sql_identifier + + + Name of the column that is part of the key in the source or destination + vertex table being linked to + + + + + + ordinal_position cardinal_number + + + Ordinal position of the columns within the key (count starts at 1) + + + + +
+
+ + + <literal>pg_element_table_key_columns</literal> + + + The view pg_element_key_columns 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). + + + + The key of an element table uniquely identifies the rows in it. It is + either specified using the KEY clause in CREATE + PROPERTY GRAPH or defaults to the primary key. + + + + <structname>pg_element_table_key_columns</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + element_table_alias sql_identifier + + + Element table alias (unique identifier of an element table within a + property graph) + + + + + + column_name sql_identifier + + + Name of the column that is part of the key + + + + + + ordinal_position cardinal_number + + + Ordinal position of the column within the key (count starts at 1) + + + + +
+
+ + + <literal>pg_element_table_labels</literal> + + + The view pg_element_table_labels 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). + + + + <structname>pg_element_table_labels</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + element_table_alias sql_identifier + + + Element table alias (unique identifier of an element table within a + property graph) + + + + + + label_name sql_identifier + + + Name of the label + + + + +
+
+ + + <literal>pg_element_table_properties</literal> + + + The view pg_element_table_labels 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). + + + + <structname>pg_element_table_properties</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + element_table_alias sql_identifier + + + Element table alias (unique identifier of an element table within a + property graph) + + + + + + property_name sql_identifier + + + Name of the property + + + + + + property_expression character_data + + + Expression of the property definition for this element table + + + + +
+
+ + + <literal>pg_element_tables</literal> + + + The view pg_element_tables 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). + + + + <structname>pg_element_tables</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + element_table_alias sql_identifier + + + Element table alias (unique identifier of an element table within a + property graph) + + + + + + element_table_kind character_data + + + The kind of the element table: EDGE or VERTEX + + + + + + table_catalog sql_identifier + + + Name of the database that contains the referenced table (always the current database) + + + + + + table_schema sql_identifier + + + Name of the schema that contains the referenced table + + + + + + table_name sql_identifier + + + Name of the table being referenced by the element table definition + + + + + + element_table_definition character_data + + + Applies to a feature not available in PostgreSQL + + + + +
+
+ + + <literal>pg_label_properties</literal> + + + The view pg_label_properties 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). + + + + <structname>pg_label_properties</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + label_name sql_identifier + + + Name of the label + + + + + + property_name sql_identifier + + + Name of the property + + + + +
+
+ + + <literal>pg_labels</literal> + + + The view pg_labels 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). + + + + <structname>pg_labels</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + label_name sql_identifier + + + Name of the label + + + + +
+
+ + + <literal>pg_property_data_types</literal> + + + The view pg_property_data_types 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). + + + + <structname>pg_property_data_types</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + + + property_name sql_identifier + + + Name of the property + + + + + + data_type character_data + + + Data type of the property, if it is a built-in type, or + ARRAY if it is some array (in that case, see the + view element_types), else + USER-DEFINED (in that case, the type is identified + in attribute_udt_name and associated columns). + + + + + + character_maximum_length cardinal_number + + + If data_type identifies a character or bit + string type, the declared maximum length; null for all other + data types or if no maximum length was declared. + + + + + + character_octet_length cardinal_number + + + If data_type 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. + + + + + + character_set_catalog sql_identifier + + + Applies to a feature not available in PostgreSQL + + + + + + character_set_schema sql_identifier + + + Applies to a feature not available in PostgreSQL + + + + + + character_set_name sql_identifier + + + Applies to a feature not available in PostgreSQL + + + + + + collation_catalog sql_identifier + + + 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 + + + + + + collation_schema sql_identifier + + + Name of the schema containing the collation of the property, null if + default or the data type of the property is not collatable + + + + + + collation_name sql_identifier + + + Name of the collation of the property, null if default or the data type + of the property is not collatable + + + + + + numeric_precision cardinal_number + + + If data_type 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 + numeric_precision_radix. For all other data + types, this column is null. + + + + + + numeric_precision_radix cardinal_number + + + If data_type identifies a numeric type, this + column indicates in which base the values in the columns + numeric_precision and + numeric_scale are expressed. The value is + either 2 or 10. For all other data types, this column is null. + + + + + + numeric_scale cardinal_number + + + If data_type 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 + numeric_precision_radix. For all other data + types, this column is null. + + + + + + datetime_precision cardinal_number + + + If data_type 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. + + + + + + interval_type character_data + + + If data_type identifies an interval type, + this column contains the specification which fields the + intervals include for this attribute, e.g., YEAR TO + MONTH, DAY TO SECOND, etc. If no + field restrictions were specified (that is, the interval + accepts all fields), and for all other data types, this field + is null. + + + + + + interval_precision cardinal_number + + + Applies to a feature not available + in PostgreSQL + (see datetime_precision for the fractional + seconds precision of interval type properties) + + + + + + user_defined_type_catalog sql_identifier + + + Name of the database that the property data type is defined in + (always the current database) + + + + + + user_defined_type_schema sql_identifier + + + Name of the schema that the property data type is defined in + + + + + + user_defined_type_name sql_identifier + + + Name of the property data type + + + + + + scope_catalog sql_identifier + + + Applies to a feature not available in PostgreSQL + + + + + + scope_schema sql_identifier + + + Applies to a feature not available in PostgreSQL + + + + + + scope_name sql_identifier + + + Applies to a feature not available in PostgreSQL + + + + + + maximum_cardinality cardinal_number + + + Always null, because arrays always have unlimited maximum cardinality in PostgreSQL + + + + + + dtd_identifier sql_identifier + + + 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.) + + + + +
+
+ + + <literal>pg_property_graph_privileges</literal> + + + The view property_graph_privileges 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. + + + + <structname>property_graph_privileges</structname> Columns + + + + + Column Type + + + Description + + + + + + + + grantor sql_identifier + + + Name of the role that granted the privilege + + + + + + grantee sql_identifier + + + Name of the role that the privilege was granted to + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property graph + + + + + + privilege_type character_data + + + Type of the privilege: SELECT is the only privilege + type applicable to property graphs. + + + + + + is_grantable yes_or_no + + + YES if the privilege is grantable, NO if not + + + + +
+
+ + + <literal>property_graphs</literal> + + + The view property_graphs 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). + + + + <structname>property_graphs</structname> Columns + + + + + Column Type + + + Description + + + + + + + + property_graph_catalog sql_identifier + + + Name of the database that contains the property graph (always the current database) + + + + + + property_graph_schema sql_identifier + + + Name of the schema that contains the property graph + + + + + + property_graph_name sql_identifier + + + Name of the property_graph + + + + +
+
+ <literal>referential_constraints</literal> diff --git a/doc/src/sgml/keywords/sql2023-16-nonreserved.txt b/doc/src/sgml/keywords/sql2023-16-nonreserved.txt new file mode 100644 index 00000000000..39756c6067d --- /dev/null +++ b/doc/src/sgml/keywords/sql2023-16-nonreserved.txt @@ -0,0 +1,27 @@ +ACYCLIC +BINDINGS +BOUND +DESTINATION +DIFFERENT +DIRECTED +EDGE +EDGES +ELEMENTS +LABEL +LABELED +NODE +PATHS +PROPERTIES +PROPERTY +PROPERTY_GRAPH_CATALOG +PROPERTY_GRAPH_NAME +PROPERTY_GRAPH_SCHEMA +RELATIONSHIP +RELATIONSHIPS +SHORTEST +SINGLETONS +STEP +TABLES +TRAIL +VERTEX +WALK diff --git a/doc/src/sgml/keywords/sql2023-16-reserved.txt b/doc/src/sgml/keywords/sql2023-16-reserved.txt new file mode 100644 index 00000000000..3bdd7e2b272 --- /dev/null +++ b/doc/src/sgml/keywords/sql2023-16-reserved.txt @@ -0,0 +1,12 @@ +ALL_DIFFERENT +BINDING_COUNT +ELEMENT_ID +ELEMENT_NUMBER +EXPORT +GRAPH +GRAPH_TABLE +MATCHNUM +PATH_LENGTH +PATH_NAME +PROPERTY_EXISTS +SAME diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml index 372cce1a487..41a6f423c64 100644 --- a/doc/src/sgml/queries.sgml +++ b/doc/src/sgml/queries.sgml @@ -863,6 +863,11 @@ <literal>LATERAL</literal> Subqueries to columns provided by preceding FROM items in any case. + + A GRAPH_TABLE FROM item can also + always contain lateral references. + + A LATERAL item can appear at the top level in the FROM list, or within a JOIN tree. In the latter @@ -2745,4 +2750,167 @@ Data-Modifying Statements in <literal>WITH</literal> + + Graph Queries + + + This section describes the sublanguage for querying property graphs, + defined as described in . + + + + Overview + + + Consider this example from : + +-- 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)); + + The graph query part happens inside the GRAPH_TABLE + 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 + FROM 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: + +SELECT ... FROM GRAPH_TABLE (mygraph MATCH ... COLUMNS (...)) AS myresult (a, b, c) JOIN othertable USING (a) WHERE b > 0 ORDER BY c; + + + + + The GRAPH_TABLE clause consists of the graph name, + followed by the keyword MATCH, followed by a graph + pattern expression (see below), followed by the keyword + COLUMNS and a column list. + + + + + Graph Patterns + + + The core of the graph querying functionality is the graph pattern, which + appears after the keyword MATCH. 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. + + + + A path pattern thus matches a sequence of vertices and edges. The + simplest possible path pattern is + +() + + which matches a single vertex. The next simplest pattern would be + +()-[]->() + + which matches a vertex followed by an edge followed by a vertex. The + characters () are a vertex pattern and the characters + -[]-> are an edge pattern. + + + + These characters can also be separated by whitespace, for example: + +( ) - [ ] - > ( ) + + + + + + A way to remember these symbols is that in visual representations of + property graphs, vertices are usually circles (like + ()) and edges have rectangular labels (like + []). + + + + + The above patterns would match any vertex, or any two vertices connected + by any edge, which isn't very interesting. Normally, we want to search + for elements (vertices and edges) that have certain characteristics. + These characteristics are written in between the parentheses or brackets. + (This is also called an element pattern filler.) Typically, we would + search for elements with a certain label. This is written either by + IS labelname or equivalently + :labelname. For example, + this would match all vertices with the label person: + +(IS person) + + or + +(:person) + + (From now on, we will just use the colon syntax, for simplicity. But it + helps to read it as is for understanding.) The next + example would match a vertex with the label person + connected to a vertex with the label account connected + by an edge with the label has. + +(:person)-[:has]->(:account) + + Multiple labels can also be matched, using or semantics: + +(:person)-[:has]->(:account|creditcard) + + + + + Recall that edges are directed. The other direction is also possible in a + path pattern, for example: + +(:account)<-[:has]-(:person) + + It is also possible to match both directions: + +(:person)-[:is_friend_of]-(:person) + + This has a meaning of or: An edge in either direction would + match. + + + + 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: + +(:person)->(:account) +(:acount)<-(:person) +(:person)-(:person) + + As is often the case, abbreviated syntax can make expressions more compact + but also sometimes harder to understand. + + + + 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 COLUMNS clause to + define the tabular result of the GRAPH_TABLE clause. + For example (assuming appropriate definitions of the property graph as + well as the underlying tables): + +GRAPH_TABLE (mygraph MATCH (p:person)-[h:has]->(a:account) + COLUMNS (p.name AS person_name, h.since AS has_account_since, a.num AS account_number) + + WHERE clauses can be used inside element patterns to + filter matches: + +(:person)-[:has]->(a:account WHERE a.type = 'savings') + + + + + + + + + + diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index f5be638867a..efbbf283c49 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -27,6 +27,7 @@ + @@ -79,6 +80,7 @@ + @@ -127,6 +129,7 @@ + diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index c819c7bb4e3..60218fcd01c 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -46,6 +46,7 @@ OPERATOR FAMILY object_name USING index_method | [ PROCEDURAL ] LANGUAGE object_name | PROCEDURE procedure_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] | + PROPERTY GRAPH object_name | ROUTINE routine_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] | SCHEMA object_name | SEQUENCE object_name | @@ -179,7 +180,7 @@ Parameters 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. diff --git a/doc/src/sgml/ref/alter_property_graph.sgml b/doc/src/sgml/ref/alter_property_graph.sgml new file mode 100644 index 00000000000..604c5180117 --- /dev/null +++ b/doc/src/sgml/ref/alter_property_graph.sgml @@ -0,0 +1,299 @@ + + + + + ALTER PROPERTY GRAPH + + + + ALTER PROPERTY GRAPH + 7 + SQL - Language Statements + + + + ALTER PROPERTY GRAPH + change the definition of an SQL-property graph + + + + +ALTER PROPERTY GRAPH name ADD + [ {VERTEX|NODE} TABLES ( vertex_table_definition [, ...] ) ] + [ {EDGE|RELATIONSHIP} TABLES ( edge_table_definition [, ...] ) ] + +ALTER PROPERTY GRAPH name DROP + {VERTEX|NODE} TABLES ( vertex_table_alias [, ...] ) [ CASCADE | RESTRICT ] + +ALTER PROPERTY GRAPH name DROP + {EDGE|RELATIONSHIP} TABLES ( edge_table_alias [, ...] ) [ CASCADE | RESTRICT ] + +ALTER PROPERTY GRAPH name ALTER + {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE element_table_alias + { ADD LABEL label_name [ NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { expression [ AS property_name ] } [, ...] ) ] } [ ... ] + +ALTER PROPERTY GRAPH name ALTER + {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE element_table_alias + DROP LABEL label_name [ CASCADE | RESTRICT ] + +ALTER PROPERTY GRAPH name ALTER + {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE element_table_alias + ALTER LABEL label_name ADD PROPERTIES ( { expression [ AS property_name ] } [, ...] ) + +ALTER PROPERTY GRAPH name ALTER + {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE element_table_alias + ALTER LABEL label_name DROP PROPERTIES ( property_name [, ...] ) [ CASCADE | RESTRICT ] + +ALTER PROPERTY GRAPH name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER PROPERTY GRAPH name RENAME TO new_name +ALTER PROPERTY GRAPH [ IF EXISTS ] name SET SCHEMA new_schema + + + + + Description + + + ALTER PROPERTY GRAPH changes the definition of an + existing property graph. There are several subforms: + + + + ADD {VERTEX|NODE|EDGE|RELATIONSHIP} TABLES + + + This form adds new vertex or edge tables, using the same syntax as + CREATE PROPERTY + GRAPH. + + + + + + DROP {VERTEX|NODE|EDGE|RELATIONSHIP} TABLES + + + This form removes a vertex or edge table from the property graph. + (Only the association of the table with the graph removed. The table + itself is not dropped.) + + + + + + ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ADD LABEL + + + This form adds a new label to an existing vertex or edge table, using + the same syntax as CREATE PROPERTY + GRAPH. + + + + + + ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... DROP LABEL + + + This form removes a new label from an existing vertex or edge table. + + + + + + ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ALTER LABEL ... ADD PROPERTIES + + + This form adds new properties to an existing label on an existing + vertex or edge table. + + + + + + ALTER {VERTEX|NODE|EDGE|RELATIONSHIP} TABLE ... ALTER LABEL ... DROP PROPERTIES + + + This form removes properties from an existing label on an existing + vertex or edge table. + + + + + + OWNER + + + This form changes the owner of the property graph to the specified user. + + + + + + RENAME + + + This form changes the name of a property graph. + + + + + + SET SCHEMA + + + This form moves the property graph into another schema. + + + + + + + + You must own the property graph to use ALTER PROPERTY + GRAPH. To change a property graph's schema, you must also have + CREATE privilege on the new schema. To alter the owner, + you must be able to SET ROLE to the new owning role, and + that role must have CREATE 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.) + + + + + Parameters + + + + name + + + The name (optionally schema-qualified) of a property graph to be altered. + + + + + + IF EXISTS + + + Do not throw an error if the property graph does not exist. A notice is + issued in this case. + + + + + + vertex_table_definition + edge_table_definition + + + See CREATE PROPERTY + GRAPH. + + + + + + vertex_table_alias + edge_table_alias + + + 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 AS + alias.) + + + + + + label_name + property_name + expression + + + See CREATE PROPERTY + GRAPH. + + + + + + new_owner + + + The user name of the new owner of the property graph. + + + + + + new_name + + + The new name for the property graph. + + + + + + new_schema + + + The new schema for the property graph. + + + + + + + + Notes + + + The consistency checks on a property graph described at must be maintained by + ALTER PROPERTY GRAPH operations. In some cases, it + might be necessary to make multiple alterations in a single command to + satisfy the checks. + + + + + Examples + + + +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; + + + + + Compatibility + + + CREATE PROPERTY GRAPH conforms to ISO/IEC 9075-16 + (SQL/PGQ). + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index 5b43c56b133..7b251476e24 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -47,6 +47,7 @@ POLICY policy_name ON table_name | [ PROCEDURAL ] LANGUAGE object_name | PROCEDURE procedure_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] | + PROPERTY GRAPH object_name PUBLICATION object_name | ROLE object_name | ROUTINE routine_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] | diff --git a/doc/src/sgml/ref/create_property_graph.sgml b/doc/src/sgml/ref/create_property_graph.sgml new file mode 100644 index 00000000000..f88d1194cbc --- /dev/null +++ b/doc/src/sgml/ref/create_property_graph.sgml @@ -0,0 +1,310 @@ + + + + + CREATE PROPERTY GRAPH + + + + CREATE PROPERTY GRAPH + 7 + SQL - Language Statements + + + + CREATE PROPERTY GRAPH + define an SQL-property graph + + + + +CREATE [ TEMP | TEMPORARY ] PROPERTY GRAPH name + [ {VERTEX|NODE} TABLES ( vertex_table_definition [, ...] ) ] + [ {EDGE|RELATIONSHIP} TABLES ( edge_table_definition [, ...] ) ] + +where vertex_table_definition is: + + vertex_table_name [ AS alias ] [ KEY ( column_name [, ...] ) ] [ element_table_label_and_properties ] + +and edge_table_definition is: + + edge_table_name [ AS alias ] [ KEY ( column_name [, ...] ) ] + SOURCE [ KEY ( column_name [, ...] ) REFERENCES ] source_table [ ( column_name [, ...] ) ] + DESTINATION [ KEY ( column_name [, ...] ) REFERENCES ] dest_table [ ( column_name [, ...] ) ] + [ element_table_label_and_properties ] + +and element_table_label_and_properties is either: + + NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { expression [ AS property_name ] } [, ...] ) + +or: + + { { LABEL label_name | DEFAULT LABEL } [ NO PROPERTIES | PROPERTIES ALL COLUMNS | PROPERTIES ( { expression [ AS property_name ] } [, ...] ) ] } [...] + + + + + Description + + + CREATE PROPERTY GRAPH 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 + GRAPH_TABLE clause of 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. + + + + CREATE PROPERTY GRAPH does not physically materialize a + graph. It is thus similar to CREATE VIEW in that it + records a structure that is used only when the defined object is queried. + + + + If a schema name is given (for example, CREATE PROPERTY GRAPH + myschema.mygraph ...) 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. + + + + + Parameters + + + + name + + + The name (optionally schema-qualified) of the new property graph. + + + + + + VERTEX/NODE + EDGE/RELATIONSHIP + + + These keywords are synonyms, respectively. + + + + + + vertex_table_name + + + The name of a table that will contain vertices in the new property + graph. + + + + + + edge_table_name + + + The name of a table that will contain edges in the new property graph. + + + + + + alias + + + 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.) + + + + + + KEY ( column_name [, ...] ) + + + A set of columns that uniquely identifies a row in the vertex or edge + table. Defaults to the primary key. + + + + + + source_table + dest_table + + + The vertex tables that the edge table is linked to. These refer to the + aliases of a the vertex table. + + + + + + KEY ( column_name [, ...] ) REFERENCES ... ( column_name [, ...] ) + + + 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. + + + + + + element_table_label_and_properties + + + 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 DEFAULT LABEL. Alternatively, one or more freely + chosen label names can be specified. (Label names do not have to be + unique across a property graph. It is can be useful to assign the same + label to different elements.) Each label has a list (possibly empty) of + properties. By default, all columns of a table are automatically + exposed as properties. This can be specified explicitly as + PROPERTIES ALL COLUMNS. 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. + + + + + + + + Notes + + + The following consistency checks must be satisfied by a property graph definition: + + + + + 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: + +CREATE PROPERTY GRAPH g1 + VERTEX TABLES ( + v1 LABEL foo PROPERTIES (x, y), + v2 LABEL foo PROPERTIES (x, y) + ) ... + + but this would not: + +CREATE PROPERTY GRAPH g1 + VERTEX TABLES ( + v1 LABEL foo PROPERTIES (x, y), + v2 LABEL foo PROPERTIES (z) + ) ... + + + + + + 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: + +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) + ) ... + + but this would not: + +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) + ) ... + + + + + + 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: + +CREATE PROPERTY GRAPH g1 + VERTEX TABLES ( + v1 LABEL foo PROPERTIES (x AS a * 2) LABEL bar PROPERTIES (x AS a * 2) + ) ... + + but this would not: + +CREATE PROPERTY GRAPH g1 + VERTEX TABLES ( + v1 LABEL foo PROPERTIES (x AS a * 2) LABEL bar PROPERTIES (x AS a * 10) + ) ... + + + + + + + + Property graphs are queried using the GRAPH_TABLE clause + of . + + + + + Examples + + + +CREATE PROPERTY GRAPH g1 + VERTEX TABLES (v1, v2, v3) + EDGE TABLES (e1 SOURCE v1 DESTINATION v2, + e2 SOURCE v1 DESTINATION v3); + + + + + Compatibility + + + CREATE PROPERTY GRAPH conforms to ISO/IEC 9075-16 + (SQL/PGQ). + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/drop_property_graph.sgml b/doc/src/sgml/ref/drop_property_graph.sgml new file mode 100644 index 00000000000..31cb77a2af1 --- /dev/null +++ b/doc/src/sgml/ref/drop_property_graph.sgml @@ -0,0 +1,111 @@ + + + + + DROP PROPERTY GRAPH + + + + DROP PROPERTY GRAPH + 7 + SQL - Language Statements + + + + DROP PROPERTY GRAPH + remove an SQL-property graph + + + + +DROP PROPERTY GRAPH [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] + + + + + Description + + + DROP PROPERTY GRAPH drops an existing property graph. + To execute this command you must be the owner of the property graph. + + + + + Parameters + + + + IF EXISTS + + + Do not throw an error if the property graph does not exist. A notice is + issued in this case. + + + + + + name + + + The name (optionally schema-qualified) of the property graph to remove. + + + + + + CASCADE + + + Automatically drop objects that depend on the property graph, and in + turn all objects that depend on those objects (see ). + + + + + + RESTRICT + + + Refuse to drop the property graph if any objects depend on it. This is + the default. + + + + + + + + Examples + + + +DROP PROPERTY GRAPH g1; + + + + + Compatibility + + + DROP PROPERTY GRAPH 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 IF EXISTS + option, which is a PostgreSQL extension.. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index 999f657d5c0..1132c1608ce 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -82,6 +82,11 @@ TO role_specification [, ...] [ WITH GRANT OPTION ] [ GRANTED BY role_specification ] +GRANT { SELECT | ALL [ PRIVILEGES ] } + ON PROPERTY GRAPH graph_name [, ...] + TO role_specification [, ...] [ WITH GRANT OPTION ] + [ GRANTED BY role_specification ] + GRANT { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] } ON SCHEMA schema_name [, ...] TO role_specification [, ...] [ WITH GRANT OPTION ] @@ -119,7 +124,7 @@ Description 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. diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 830306ea1e2..4541f0d1773 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1218,7 +1218,7 @@ Meta-Commands - 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 pattern, show all @@ -1258,9 +1258,9 @@ Meta-Commands If \d is used without a pattern argument, it is - equivalent to \dtvmsE which will show a list of - all visible tables, views, materialized views, sequences and - foreign tables. + equivalent to \dtvmsEG which will show a list of + all visible tables, views, materialized views, sequences, + foreign tables, and property graphs. This is purely a convenience measure. @@ -1528,6 +1528,7 @@ Meta-Commands \dE[S+] [ pattern ] + \dG[S+] [ pattern ] \di[S+] [ pattern ] \dm[S+] [ pattern ] \ds[S+] [ pattern ] @@ -1536,10 +1537,10 @@ Meta-Commands - In this group of commands, the letters E, + In this group of commands, the letters E, G, i, m, s, t, and v - stand for foreign table, index, materialized view, + stand for foreign table, index, property graph, materialized view, sequence, table, and view, respectively. You can specify any or all of diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 8df492281a1..948ac534446 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -104,6 +104,13 @@ [ GRANTED BY role_specification ] [ CASCADE | RESTRICT ] +REVOKE [ GRANT OPTION FOR ] + { SELECT | ALL [ PRIVILEGES ] } + ON PROPERTY GRAPH graph_name [, ...] + FROM role_specification [, ...] + [ GRANTED BY role_specification ] + [ CASCADE | RESTRICT ] + REVOKE [ GRANT OPTION FOR ] { { CREATE | USAGE } [, ...] | ALL [ PRIVILEGES ] } ON SCHEMA schema_name [, ...] diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index e5e5fb483e9..9b97085a3fd 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -35,6 +35,7 @@ MATERIALIZED VIEW object_name | [ PROCEDURAL ] LANGUAGE object_name | PROCEDURE procedure_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] | + PROPERTY GRAPH object_name PUBLICATION object_name | ROLE object_name | ROUTINE routine_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] | diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml index 066aed44e6e..553363bb68f 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -59,6 +59,7 @@ [ LATERAL ] function_name ( [ argument [, ...] ] ) AS ( column_definition [, ...] ) [ LATERAL ] ROWS FROM( function_name ( [ argument [, ...] ] ) [ AS ( column_definition [, ...] ) ] [, ...] ) [ WITH ORDINALITY ] [ [ AS ] alias [ ( column_alias [, ...] ) ] ] + GRAPH_TABLE ( graph_name MATCH graph_pattern COLUMNS ( { expression [ AS name ] } [, ...] ) ) [ [ AS ] alias [ ( column_alias [, ...] ) ] ] from_item join_type from_item { ON join_condition | USING ( join_column [, ...] ) [ AS join_using_alias ] } from_item NATURAL join_type from_item from_item CROSS JOIN from_item @@ -587,6 +588,48 @@ <literal>FROM</literal> Clause + + GRAPH_TABLE ( graph_name MATCH graph_pattern COLUMNS ( { expression [ AS name ] } [, ...] ) ) + + + This clause produces output from matching the specifying graph pattern + against a property graph. See + and for more information. + + + + graph_name is the name + (optionally schema-qualified) of an existing property graph (defined + with ). + + + + graph_pattern is a graph + pattern in a special graph pattern sublanguage. See . + + + + The COLUMNS clause defines the output columns of + the GRAPH_TABLE clause. expression is a scalar expression + using the graph pattern variables defined in the graph_pattern. The name of the output + columns are specified using the AS clauses. If the + expressions are simple property references, the property names are + used as the output names, otherwise an explicit name must be + specified. + + + + Like for other FROM clause items, a table alias + name and column alias names may follow the GRAPH_TABLE + (...) clause. (A column alias list would be redundant with + the COLUMNS clause.) + + + + join_type diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index ff85ace83fc..6de3afa7b16 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -55,6 +55,7 @@ SQL Commands &alterOperatorFamily; &alterPolicy; &alterProcedure; + &alterPropertyGraph; &alterPublication; &alterRole; &alterRoutine; @@ -107,6 +108,7 @@ SQL Commands &createOperatorFamily; &createPolicy; &createProcedure; + &createPropertyGraph; &createPublication; &createRole; &createRule; @@ -155,6 +157,7 @@ SQL Commands &dropOwned; &dropPolicy; &dropProcedure; + &dropPropertyGraph; &dropPublication; &dropRole; &dropRoutine; diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index a44ccee3b68..5c3f00b7265 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -290,6 +290,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, case OBJECT_PARAMETER_ACL: whole_mask = ACL_ALL_RIGHTS_PARAMETER_ACL; break; + case OBJECT_PROPGRAPH: + whole_mask = ACL_ALL_RIGHTS_PROPGRAPH; + break; default: elog(ERROR, "unrecognized object type: %d", objtype); /* not reached, but keep compiler quiet */ @@ -534,6 +537,10 @@ ExecuteGrantStmt(GrantStmt *stmt) all_privileges = ACL_ALL_RIGHTS_PARAMETER_ACL; errormsg = gettext_noop("invalid privilege type %s for parameter"); break; + case OBJECT_PROPGRAPH: + all_privileges = ACL_ALL_RIGHTS_PROPGRAPH; + errormsg = gettext_noop("invalid privilege type %s for property graph"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) stmt->objtype); @@ -604,6 +611,7 @@ ExecGrantStmt_oids(InternalGrant *istmt) { case OBJECT_TABLE: case OBJECT_SEQUENCE: + case OBJECT_PROPGRAPH: ExecGrant_Relation(istmt); break; case OBJECT_DATABASE: @@ -676,6 +684,7 @@ objectNamesToOids(ObjectType objtype, List *objnames, bool is_grant) { case OBJECT_TABLE: case OBJECT_SEQUENCE: + case OBJECT_PROPGRAPH: foreach(cell, objnames) { RangeVar *relvar = (RangeVar *) lfirst(cell); @@ -876,6 +885,10 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames) objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE); objects = list_concat(objects, objs); break; + case OBJECT_PROPGRAPH: + objs = getRelationsInNamespace(namespaceId, RELKIND_PROPGRAPH); + objects = list_concat(objects, objs); + break; case OBJECT_FUNCTION: case OBJECT_PROCEDURE: case OBJECT_ROUTINE: @@ -1077,6 +1090,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s all_privileges = ACL_ALL_RIGHTS_SCHEMA; errormsg = gettext_noop("invalid privilege type %s for schema"); break; + case OBJECT_PROPGRAPH: + all_privileges = ACL_ALL_RIGHTS_PROPGRAPH; + errormsg = gettext_noop("invalid privilege type %s for property graph"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) action->objtype); @@ -2777,6 +2794,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_PROCEDURE: msg = gettext_noop("permission denied for procedure %s"); break; + case OBJECT_PROPGRAPH: + msg = gettext_noop("permission denied for property graph %s"); + break; case OBJECT_PUBLICATION: msg = gettext_noop("permission denied for publication %s"); break; @@ -2903,6 +2923,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_PROCEDURE: msg = gettext_noop("must be owner of procedure %s"); break; + case OBJECT_PROPGRAPH: + msg = gettext_noop("must be owner of property graph %s"); + break; case OBJECT_PUBLICATION: msg = gettext_noop("must be owner of publication %s"); break; @@ -3039,6 +3062,7 @@ pg_aclmask(ObjectType objtype, Oid object_oid, AttrNumber attnum, Oid roleid, pg_attribute_aclmask(object_oid, attnum, roleid, mask, how); case OBJECT_TABLE: case OBJECT_SEQUENCE: + case OBJECT_PROPGRAPH: return pg_class_aclmask(object_oid, roleid, mask, how); case OBJECT_DATABASE: return object_aclmask(DatabaseRelationId, object_oid, roleid, mask, how); diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 0489cbabcb8..d7070d62c11 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -50,6 +50,11 @@ #include "catalog/pg_parameter_acl.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_element_label.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_label_property.h" +#include "catalog/pg_propgraph_property.h" #include "catalog/pg_publication.h" #include "catalog/pg_publication_namespace.h" #include "catalog/pg_publication_rel.h" @@ -1453,6 +1458,11 @@ doDeletion(const ObjectAddress *object, int flags) case AccessMethodRelationId: case AccessMethodOperatorRelationId: case AccessMethodProcedureRelationId: + case PropgraphElementRelationId: + case PropgraphElementLabelRelationId: + case PropgraphLabelRelationId: + case PropgraphLabelPropertyRelationId: + case PropgraphPropertyRelationId: case NamespaceRelationId: case TSParserRelationId: case TSDictionaryRelationId: @@ -2163,6 +2173,7 @@ find_expr_references_walker(Node *node, switch (rte->rtekind) { case RTE_RELATION: + case RTE_GRAPH_TABLE: add_object_address(RelationRelationId, rte->relid, 0, context->addrs); break; diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index c4145131ce4..3dc8f0539fc 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -3044,3 +3044,367 @@ CREATE VIEW user_mappings AS FROM _pg_user_mappings; GRANT SELECT ON user_mappings TO PUBLIC; + + +-- SQL/PGQ views; these use section numbers from part 16 of the standard. + +/* + * 15.2 + * PG_DEFINED_LABEL_SETS view + */ + +-- TODO + + +/* + * 15.3 + * PG_DEFINED_LABEL_SET_LABELS view + */ + +-- TODO + + +/* + * 15.4 + * PG_EDGE_DEFINED_LABEL_SETS view + */ + +-- TODO + + +/* + * 15.5 + * PG_EDGE_TABLE_COMPONENTS view + */ + +CREATE VIEW pg_edge_table_components AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(eg.pgealias AS sql_identifier) AS edge_table_alias, + CAST(v.pgealias AS sql_identifier) AS vertex_table_alias, + CAST(CASE eg.end WHEN 'src' THEN 'SOURCE' WHEN 'dest' THEN 'DESTINATION' END AS character_data) AS edge_end, + CAST(ae.attname AS sql_identifier) AS edge_table_column_name, + CAST(av.attname AS sql_identifier) AS vertex_table_column_name, + CAST((eg.egkey).n AS cardinal_number) AS ordinal_position + FROM pg_namespace npg + JOIN + (SELECT * FROM pg_class WHERE relkind = 'g') AS pg + ON npg.oid = pg.relnamespace + JOIN + (SELECT pgepgid, pgealias, pgerelid, 'src' AS end, pgesrcvertexid AS vertexid, _pg_expandarray(pgesrckey) AS egkey, _pg_expandarray(pgesrcref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e' + UNION ALL + SELECT pgepgid, pgealias, pgerelid, 'dest' AS end, pgedestvertexid AS vertexid, _pg_expandarray(pgedestkey) AS egkey, _pg_expandarray(pgedestref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e' + ) AS eg + ON pg.oid = eg.pgepgid + JOIN + (SELECT * FROM pg_propgraph_element WHERE pgekind = 'v') AS v + ON eg.vertexid = v.oid + JOIN + (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS ae + ON eg.pgerelid = ae.attrelid AND (eg.egkey).x = ae.attnum + JOIN + (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS av + ON v.pgerelid = av.attrelid AND (eg.egref).x = av.attnum + WHERE NOT pg_is_other_temp_schema(npg.oid) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_edge_table_components TO PUBLIC; + + +/* + * 15.6 + * PG_EDGE_TRIPLETS view + */ + +-- TODO + + +/* + * 15.7 + * PG_ELEMENT_TABLE_KEY_COLUMNS view + */ + +CREATE VIEW pg_element_table_key_columns AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(pgealias AS sql_identifier) AS element_table_alias, + CAST(a.attname AS sql_identifier) AS column_name, + CAST((el.ekey).n AS cardinal_number) AS ordinal_position + FROM pg_namespace npg + JOIN + (SELECT * FROM pg_class WHERE relkind = 'g') AS pg + ON npg.oid = pg.relnamespace + JOIN + (SELECT pgepgid, pgealias, pgerelid, _pg_expandarray(pgekey) AS ekey FROM pg_propgraph_element) AS el + ON pg.oid = el.pgepgid + JOIN + (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS a + ON el.pgerelid = a.attrelid AND (el.ekey).x = a.attnum + WHERE NOT pg_is_other_temp_schema(npg.oid) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_table_key_columns TO PUBLIC; + + +/* + * 15.8 + * PG_ELEMENT_TABLE_LABELS view + */ + +CREATE VIEW pg_element_table_labels AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(e.pgealias AS sql_identifier) AS element_table_alias, + CAST(l.pgllabel AS sql_identifier) AS label_name + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_element_label el, pg_propgraph_label l + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND el.pgelelid = e.oid + AND el.pgellabelid = l.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_table_labels TO PUBLIC; + + +/* + * 15.9 + * PG_ELEMENT_TABLE_PROPERTIES view + */ + +CREATE VIEW pg_element_table_properties AS + SELECT DISTINCT + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(e.pgealias AS sql_identifier) AS element_table_alias, + CAST(pr.pgpname AS sql_identifier) AS property_name, + CAST(pg_get_expr(plp.plpexpr, e.pgerelid) AS character_data) AS property_expression + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_element_label el, pg_propgraph_label_property plp, pg_propgraph_property pr + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND el.pgelelid = e.oid + AND plp.plpellabelid = el.oid + AND pr.oid = plp.plppropid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_table_properties TO PUBLIC; + + +/* + * 15.10 + * PG_ELEMENT_TABLES view + */ + +CREATE VIEW pg_element_tables AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(e.pgealias AS sql_identifier) AS element_table_alias, + CAST(CASE e.pgekind WHEN 'e' THEN 'EDGE' WHEN 'v' THEN 'VERTEX' END AS character_data) AS element_table_kind, + CAST(current_database() AS sql_identifier) AS table_catalog, + CAST(nt.nspname AS sql_identifier) AS table_schema, + CAST(t.relname AS sql_identifier) AS table_name, + CAST(NULL AS character_data) AS element_table_definition + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_class t, pg_namespace nt + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND e.pgerelid = t.oid + AND t.relnamespace = nt.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_tables TO PUBLIC; + + +/* + * 15.11 + * PG_LABEL_PROPERTIES view + */ + +CREATE VIEW pg_label_properties AS + SELECT DISTINCT + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(l.pgllabel AS sql_identifier) AS label_name, + CAST(pr.pgpname AS sql_identifier) AS property_name + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l, pg_propgraph_element_label el, pg_propgraph_label_property plp, pg_propgraph_property pr + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND el.pgelelid = e.oid + AND plp.plpellabelid = el.oid + AND pr.oid = plp.plppropid + AND el.pgellabelid = l.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_label_properties TO PUBLIC; + + +/* + * 15.12 + * PG_LABELS view + */ + +CREATE VIEW pg_labels AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(l.pgllabel AS sql_identifier) AS label_name + FROM pg_namespace npg, pg_class pg, pg_propgraph_label l + WHERE pg.relnamespace = npg.oid + AND l.pglpgid = pg.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_labels TO PUBLIC; + + +/* + * 15.13 + * PG_PROPERTY_DATA_TYPES view + */ + +CREATE VIEW pg_property_data_types AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(pgp.pgpname AS sql_identifier) AS property_name, + + CAST( + CASE WHEN t.typtype = 'd' THEN + CASE WHEN bt.typelem <> 0 AND bt.typlen = -1 THEN 'ARRAY' + WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, null) + ELSE 'USER-DEFINED' END + ELSE + CASE WHEN t.typelem <> 0 AND t.typlen = -1 THEN 'ARRAY' + WHEN nt.nspname = 'pg_catalog' THEN format_type(pgp.pgptypid, null) + ELSE 'USER-DEFINED' END + END + AS character_data) + AS data_type, + + CAST(null AS cardinal_number) AS character_maximum_length, + CAST(null AS cardinal_number) AS character_octet_length, + CAST(null AS sql_identifier) AS character_set_catalog, + CAST(null AS sql_identifier) AS character_set_schema, + CAST(null AS sql_identifier) AS character_set_name, + CAST(null AS sql_identifier) AS collation_catalog, -- FIXME + CAST(null AS sql_identifier) AS collation_schema, -- FIXME + CAST(null AS sql_identifier) AS collation_name, -- FIXME + CAST(null AS cardinal_number) AS numeric_precision, + CAST(null AS cardinal_number) AS numeric_precision_radix, + CAST(null AS cardinal_number) AS numeric_scale, + CAST(null AS cardinal_number) AS datetime_precision, + CAST(null AS character_data) AS interval_type, + CAST(null AS cardinal_number) AS interval_precision, + + CAST(current_database() AS sql_identifier) AS user_defined_type_catalog, + CAST(coalesce(nbt.nspname, nt.nspname) AS sql_identifier) AS user_defined_type_schema, + CAST(coalesce(bt.typname, t.typname) AS sql_identifier) AS user_defined_type_name, + + CAST(null AS sql_identifier) AS scope_catalog, + CAST(null AS sql_identifier) AS scope_schema, + CAST(null AS sql_identifier) AS scope_name, + + CAST(null AS cardinal_number) AS maximum_cardinality, + CAST(pgp.pgpname AS sql_identifier) AS dtd_identifier + + FROM pg_propgraph_property pgp + JOIN (pg_class pg JOIN pg_namespace npg ON (pg.relnamespace = npg.oid)) ON pgp.pgppgid = pg.oid + JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON pgp.pgptypid = t.oid + LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON (bt.typnamespace = nbt.oid)) + ON (t.typtype = 'd' AND t.typbasetype = bt.oid) + + WHERE pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_property_data_types TO PUBLIC; + + +/* + * 15.14 + * PG_PROPERTY_GRAPH_PRIVILEGES view + */ + +CREATE VIEW pg_property_graph_privileges AS + SELECT CAST(u_grantor.rolname AS sql_identifier) AS grantor, + CAST(grantee.rolname AS sql_identifier) AS grantee, + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(nc.nspname AS sql_identifier) AS property_graph_schema, + CAST(c.relname AS sql_identifier) AS property_graph_name, + CAST(c.prtype AS character_data) AS privilege_type, + CAST( + CASE WHEN + -- object owner always has grant options + pg_has_role(grantee.oid, c.relowner, 'USAGE') + OR c.grantable + THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_grantable + + FROM ( + SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class + ) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable), + pg_namespace nc, + pg_authid u_grantor, + ( + SELECT oid, rolname FROM pg_authid + UNION ALL + SELECT 0::oid, 'PUBLIC' + ) AS grantee (oid, rolname) + + WHERE c.relnamespace = nc.oid + AND c.relkind IN ('g') + AND c.grantee = grantee.oid + AND c.grantor = u_grantor.oid + AND c.prtype IN ('SELECT') + AND (pg_has_role(u_grantor.oid, 'USAGE') + OR pg_has_role(grantee.oid, 'USAGE') + OR grantee.rolname = 'PUBLIC'); + +GRANT SELECT ON pg_property_graph_privileges TO PUBLIC; + + +/* + * 15.15 + * PG_VERTEX_DEFINED_LABEL_SETS view + */ + +-- TODO + + +/* + * 15.16 + * PROPERTY_GRAPHS view + */ + +CREATE VIEW property_graphs AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(nc.nspname AS sql_identifier) AS property_graph_schema, + CAST(c.relname AS sql_identifier) AS property_graph_name + FROM pg_namespace nc, pg_class c + WHERE c.relnamespace = nc.oid + AND c.relkind = 'g' + AND (NOT pg_is_other_temp_schema(nc.oid)) + AND (pg_has_role(c.relowner, 'USAGE') + OR has_table_privilege(c.oid, 'SELECT')); + +GRANT SELECT ON property_graphs TO PUBLIC; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 7b536ac6fde..d049cf871c2 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -47,6 +47,11 @@ #include "catalog/pg_parameter_acl.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_element_label.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_label_property.h" +#include "catalog/pg_propgraph_property.h" #include "catalog/pg_publication.h" #include "catalog/pg_publication_namespace.h" #include "catalog/pg_publication_rel.h" @@ -370,6 +375,76 @@ static const ObjectPropertyType ObjectProperty[] = OBJECT_OPFAMILY, true }, + { + "property graph element", + PropgraphElementRelationId, + PropgraphElementObjectIndexId, + PROPGRAPHELOID, + PROPGRAPHELALIAS, + Anum_pg_propgraph_element_oid, + Anum_pg_propgraph_element_pgealias, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, + { + "property graph element label", + PropgraphElementLabelRelationId, + PropgraphElementLabelObjectIndexId, + -1, + -1, + Anum_pg_propgraph_element_label_oid, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, + { + "property graph label", + PropgraphLabelRelationId, + PropgraphLabelObjectIndexId, + PROPGRAPHLABELOID, + PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + Anum_pg_propgraph_label_pgllabel, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, + { + "property graph label property", + PropgraphLabelPropertyRelationId, + PropgraphLabelPropertyObjectIndexId, + -1, + -1, + Anum_pg_propgraph_label_property_oid, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, + { + "property graph property", + PropgraphPropertyRelationId, + PropgraphPropertyObjectIndexId, + -1, + PROPGRAPHPROPNAME, + Anum_pg_propgraph_property_oid, + Anum_pg_propgraph_property_pgpname, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, { "role", AuthIdRelationId, @@ -679,6 +754,9 @@ static const struct object_type_map { "foreign table", OBJECT_FOREIGN_TABLE }, + { + "property graph", OBJECT_PROPGRAPH + }, { "table column", OBJECT_COLUMN }, @@ -814,6 +892,15 @@ static const struct object_type_map { "policy", OBJECT_POLICY }, + { + "property graph element", -1 + }, + { + "property graph label", -1 + }, + { + "property graph property", -1 + }, { "publication", OBJECT_PUBLICATION }, @@ -948,6 +1035,7 @@ get_object_address(ObjectType objtype, Node *object, case OBJECT_VIEW: case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: + case OBJECT_PROPGRAPH: address = get_relation_by_qualified_name(objtype, castNode(List, object), &relation, lockmode, @@ -1356,6 +1444,13 @@ get_relation_by_qualified_name(ObjectType objtype, List *object, errmsg("\"%s\" is not an index", RelationGetRelationName(relation)))); break; + case OBJECT_PROPGRAPH: + if (relation->rd_rel->relkind != RELKIND_PROPGRAPH) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a property graph", + RelationGetRelationName(relation)))); + break; case OBJECT_SEQUENCE: if (relation->rd_rel->relkind != RELKIND_SEQUENCE) ereport(ERROR, @@ -2271,6 +2366,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_MATVIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: + case OBJECT_PROPGRAPH: case OBJECT_COLUMN: case OBJECT_ATTRIBUTE: case OBJECT_COLLATION: @@ -2390,6 +2486,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, case OBJECT_VIEW: case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: + case OBJECT_PROPGRAPH: case OBJECT_COLUMN: case OBJECT_RULE: case OBJECT_TRIGGER: @@ -3920,6 +4017,182 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case PropgraphElementRelationId: + { + HeapTuple tup; + Form_pg_propgraph_element pgeform; + + tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for property graph element %u", + object->objectId); + + pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup); + + if (pgeform->pgekind == PGEKIND_VERTEX) + /* translator: followed by, e.g., "property graph %s" */ + appendStringInfo(&buffer, _("vertex %s of "), NameStr(pgeform->pgealias)); + else if (pgeform->pgekind == PGEKIND_EDGE) + /* translator: followed by, e.g., "property graph %s" */ + appendStringInfo(&buffer, _("edge %s of "), NameStr(pgeform->pgealias)); + else + appendStringInfo(&buffer, "??? element %s of ", NameStr(pgeform->pgealias)); + getRelationDescription(&buffer, pgeform->pgepgid, false); + + ReleaseSysCache(tup); + break; + } + + case PropgraphElementLabelRelationId: + { + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + Form_pg_propgraph_element_label pgelform; + ObjectAddress oa; + + rel = table_open(PropgraphElementLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + { + if (!missing_ok) + elog(ERROR, "could not find tuple for element label %u", object->objectId); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + + pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tuple); + + appendStringInfo(&buffer, _("label %s of "), get_propgraph_label_name(pgelform->pgellabelid)); + ObjectAddressSet(oa, PropgraphElementRelationId, pgelform->pgelelid); + appendStringInfoString(&buffer, getObjectDescription(&oa, false)); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + + case PropgraphLabelRelationId: + { + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + Form_pg_propgraph_label pglform; + + rel = table_open(PropgraphLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + scan = systable_beginscan(rel, PropgraphLabelObjectIndexId, true, NULL, 1, key); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + { + if (!missing_ok) + elog(ERROR, "could not find tuple for label %u", object->objectId); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + + pglform = (Form_pg_propgraph_label) GETSTRUCT(tuple); + + /* translator: followed by, e.g., "property graph %s" */ + appendStringInfo(&buffer, _("label %s of "), NameStr(pglform->pgllabel)); + getRelationDescription(&buffer, pglform->pglpgid, false); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + + case PropgraphLabelPropertyRelationId: + { + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + Form_pg_propgraph_label_property plpform; + ObjectAddress oa; + + rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_property_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + scan = systable_beginscan(rel, PropgraphLabelPropertyObjectIndexId, true, NULL, 1, key); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + { + if (!missing_ok) + elog(ERROR, "could not find tuple for label property %u", object->objectId); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + + plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple); + + appendStringInfo(&buffer, _("property %s of "), get_propgraph_property_name(plpform->plppropid)); + ObjectAddressSet(oa, PropgraphElementLabelRelationId, plpform->plpellabelid); + appendStringInfoString(&buffer, getObjectDescription(&oa, false)); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + + case PropgraphPropertyRelationId: + { + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + Form_pg_propgraph_property pgpform; + + rel = table_open(PropgraphPropertyRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_property_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + scan = systable_beginscan(rel, PropgraphPropertyObjectIndexId, true, NULL, 1, key); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + { + if (!missing_ok) + elog(ERROR, "could not find tuple for property %u", object->objectId); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + + pgpform = (Form_pg_propgraph_property) GETSTRUCT(tuple); + + /* translator: followed by, e.g., "property graph %s" */ + appendStringInfo(&buffer, _("property %s of "), NameStr(pgpform->pgpname)); + getRelationDescription(&buffer, pgpform->pgppgid, false); + + systable_endscan(scan); + table_close(rel, AccessShareLock); + break; + } + case PublicationRelationId: { char *pubname = get_publication_name(object->objectId, @@ -4105,6 +4378,10 @@ getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok) appendStringInfo(buffer, _("foreign table %s"), relname); break; + case RELKIND_PROPGRAPH: + appendStringInfo(buffer, _("property graph %s"), + relname); + break; default: /* shouldn't get here */ appendStringInfo(buffer, _("relation %s"), @@ -4525,6 +4802,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; @@ -4606,6 +4895,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"); @@ -5766,6 +6058,18 @@ getObjectIdentityParts(const ObjectAddress *object, break; } + case PropgraphElementRelationId: + appendStringInfo(&buffer, "%u TODO", object->objectId); + break; + + case PropgraphLabelRelationId: + appendStringInfo(&buffer, "%u TODO", object->objectId); + break; + + case PropgraphPropertyRelationId: + appendStringInfo(&buffer, "%u TODO", object->objectId); + break; + case PublicationRelationId: { char *pubname; @@ -6072,6 +6376,8 @@ get_relkind_objtype(char relkind) return OBJECT_MATVIEW; case RELKIND_FOREIGN_TABLE: return OBJECT_FOREIGN_TABLE; + case RELKIND_PROPGRAPH: + return OBJECT_PROPGRAPH; case RELKIND_TOASTVALUE: return OBJECT_TABLE; default: diff --git a/src/backend/catalog/pg_class.c b/src/backend/catalog/pg_class.c index e05b0bbb2e0..748f06b3ff4 100644 --- a/src/backend/catalog/pg_class.c +++ b/src/backend/catalog/pg_class.c @@ -45,6 +45,8 @@ errdetail_relkind_not_supported(char relkind) return errdetail("This operation is not supported for partitioned tables."); case RELKIND_PARTITIONED_INDEX: return errdetail("This operation is not supported for partitioned indexes."); + case RELKIND_PROPGRAPH: + return errdetail("This operation is not supported for property graphs."); default: elog(ERROR, "unrecognized relkind: '%c'", relkind); return 0; diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index c002f37202f..b49aad541a1 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -348,6 +348,106 @@ F866 FETCH FIRST clause: PERCENT option NO F867 FETCH FIRST clause: WITH TIES option YES F868 ORDER BY in grouped table YES F869 SQL implementation info population YES +G000 Graph pattern YES SQL/PGQ required +G001 Repeatable-elements match mode YES SQL/PGQ required +G002 Different-edges match mode NO +G003 Explicit REPEATABLE ELEMENTS keyword NO +G004 Path variables NO +G005 Path search prefix in a path pattern NO +G006 Graph pattern KEEP clause: path mode prefix NO +G007 Graph pattern KEEP clause: path search prefix NO +G008 Graph pattern WHERE clause YES SQL/PGQ required +G010 Explicit WALK keyword NO +G011 Advanced path modes: TRAIL NO +G012 Advanced path modes: SIMPLE NO +G013 Advanced path modes: ACYCLIC NO +G014 Explicit PATH/PATHS keywords NO +G015 All path search: explicit ALL keyword NO +G016 Any path search NO +G017 All shortest path search NO +G018 Any shortest path search NO +G019 Counted shortest path search NO +G020 Counted shortest group search NO +G030 Path multiset alternation NO +G031 Path multiset alternation: variable length path operands NO +G032 Path pattern union NO +G033 Path pattern union: variable length path operands NO +G034 Path concatenation YES SQL/PGQ required +G035 Quantified paths NO +G036 Quantified edges NO +G037 Questioned paths NO +G038 Parenthesized path pattern expression NO +G039 Simplified path pattern expression: full defaulting NO +G040 Vertex pattern YES SQL/PGQ required +G041 Non-local element pattern predicates NO +G042 Basic full edge patterns YES SQL/PGQ required +G043 Complete full edge patterns NO +G044 Basic abbreviated edge patterns YES +G045 Complete abbreviated edge patterns NO +G046 Relaxed topological consistency: adjacent vertex patterns NO +G047 Relaxed topological consistency: concise edge patterns NO +G048 Parenthesized path pattern: subpath variable declaration NO +G049 Parenthesized path pattern: path mode prefix NO +G050 Parenthesized path pattern: WHERE clause NO +G051 Parenthesized path pattern: non-local predicates NO +G060 Bounded graph pattern quantifiers NO +G061 Unbounded graph pattern quantifiers NO +G070 Label expression: label disjunction NO SQL/PGQ required +G071 Label expression: label conjunction NO +G072 Label expression: label negation NO +G073 Label expression: individual label name YES SQL/PGQ required +G074 Label expression: wildcard label NO +G075 Parenthesized label expression NO +G080 Simplified path pattern expression: basic defaulting NO +G081 Simplified path pattern expression: full overrides NO +G082 Simplified path pattern expression: basic overrides NO +G090 Property reference YES SQL/PGQ required +G100 ELEMENT_ID function NO +G110 IS DIRECTED predicate NO +G111 IS LABELED predicate NO +G112 IS SOURCE and IS DESTINATION predicate NO +G113 ALL_DIFFERENT predicate NO +G114 SAME predicate NO +G115 PROPERTY_EXISTS predicate NO +G120 Within-match aggregates NO +G800 PATH_NAME function NO +G801 ELEMENT_NUMBER function NO +G802 PATH_LENGTH function NO +G803 MATCHNUM function NO +G810 IS BOUND predicate NO +G811 IS BOUND predicate: AS option NO +G820 BINDING_COUNT NO +G830 Colon in 'is label' expression YES +G840 Path-ordered aggregates NO +G850 SQL/PGQ Information Schema views YES +G860 GET DIAGNOSTICS enhancements for SQL-property graphs NO +G900 GRAPH_TABLE YES SQL/PGQ required +G901 GRAPH_TABLE: ONE ROW PER VERTEX NO +G902 GRAPH_TABLE: ONE ROW PER STEP NO +G903 GRAPH_TABLE: explicit ONE ROW PER MATCH keywords NO +G904 All properties reference NO +G905 GRAPH_TABLE: optional COLUMNS clause NO +G906 GRAPH_TABLE: explicit EXPORT ALL NO +G907 GRAPH_TABLE: EXPORT ALL EXCEPT NO +G908 GRAPH_TABLE: EXPORT SINGLETONS list NO +G909 GRAPH_TABLE: explicit EXPORT NO SINGLETONS NO +G910 GRAPH_TABLE: 'in paths clause' NO +G920 DDL-based SQL-property graphs YES SQL/PGQ required +G921 Empty SQL-property graph YES +G922 Views as element tables YES +G923 In-line views as element tables NO +G924 Explicit key clause for element tables YES SQL/PGQ required +G925 Explicit label and properties clause for element tables YES SQL/PGQ required +G926 More than one label for vertex tables YES +G927 More than one label for edge tables YES +G928 Value expressions as properties and renaming of properties YES +G929 Labels and properties: EXCEPT list NO +G940 Multi-sourced/destined edges YES +G941 Implicit removal of incomplete edges YES +G950 Alter property graph statement: ADD/DROP element table YES +G960 Alter element table definition: ADD/DROP LABEL YES +G970 Alter element table definition: ALTER LABEL YES +G980 DROP PROPERTY GRAPH: CASCADE drop behavior YES R010 Row pattern recognition: FROM clause NO R020 Row pattern recognition: WINDOW clause NO R030 Row pattern recognition: full aggregate support NO diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 48f7348f91c..6260d6b79ed 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -46,6 +46,7 @@ OBJS = \ portalcmds.o \ prepare.o \ proclang.o \ + propgraphcmds.o \ publicationcmds.o \ schemacmds.o \ seclabel.o \ diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 4f99ebb4470..14deca5f546 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -379,6 +379,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_MATVIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: + case OBJECT_PROPGRAPH: return RenameRelation(stmt); case OBJECT_COLUMN: @@ -535,6 +536,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, case OBJECT_TABLE: case OBJECT_VIEW: case OBJECT_MATVIEW: + case OBJECT_PROPGRAPH: address = AlterTableNamespace(stmt, oldSchemaAddr ? &oldNspOid : NULL); break; @@ -870,6 +872,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt) case OBJECT_OPCLASS: case OBJECT_OPFAMILY: case OBJECT_PROCEDURE: + case OBJECT_PROPGRAPH: case OBJECT_ROUTINE: case OBJECT_STATISTIC_EXT: case OBJECT_TABLESPACE: @@ -879,16 +882,29 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt) Relation relation; ObjectAddress address; - address = get_object_address(stmt->objectType, - stmt->object, - &relation, - AccessExclusiveLock, - false); - Assert(relation == NULL); + if (stmt->relation) + address = get_object_address_rv(stmt->objectType, + stmt->relation, + NIL, + &relation, + AccessExclusiveLock, + false); + else + { + address = get_object_address(stmt->objectType, + stmt->object, + &relation, + AccessExclusiveLock, + false); + Assert(relation == NULL); + } AlterObjectOwner_internal(address.classId, address.objectId, newowner); + if (relation) + relation_close(relation, NoLock); + return address; } break; diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index 85eec7e3947..8d6faf1edd7 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -482,6 +482,7 @@ does_not_exist_skipping(ObjectType objtype, Node *object) case OBJECT_FOREIGN_TABLE: case OBJECT_INDEX: case OBJECT_MATVIEW: + case OBJECT_PROPGRAPH: case OBJECT_ROLE: case OBJECT_SEQUENCE: case OBJECT_SUBSCRIPTION: diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 7a5ed6b9850..16fda783815 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -2177,6 +2177,7 @@ stringify_grant_objtype(ObjectType objtype) case OBJECT_OPERATOR: case OBJECT_OPFAMILY: case OBJECT_POLICY: + case OBJECT_PROPGRAPH: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: @@ -2261,6 +2262,7 @@ stringify_adefprivs_objtype(ObjectType objtype) case OBJECT_OPFAMILY: case OBJECT_PARAMETER_ACL: case OBJECT_POLICY: + case OBJECT_PROPGRAPH: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build index 6dd00a4abde..34dd12d7d56 100644 --- a/src/backend/commands/meson.build +++ b/src/backend/commands/meson.build @@ -34,6 +34,7 @@ backend_sources += files( 'portalcmds.c', 'prepare.c', 'proclang.c', + 'propgraphcmds.c', 'publicationcmds.c', 'schemacmds.c', 'seclabel.c', diff --git a/src/backend/commands/propgraphcmds.c b/src/backend/commands/propgraphcmds.c new file mode 100644 index 00000000000..f204fc3125a --- /dev/null +++ b/src/backend/commands/propgraphcmds.c @@ -0,0 +1,1706 @@ +/*------------------------------------------------------------------------- + * + * propgraphcmds.c + * property graph manipulation + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/commands/propgraphcmds.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_class.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_element_label.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_label_property.h" +#include "catalog/pg_propgraph_property.h" +#include "commands/propgraphcmds.h" +#include "commands/tablecmds.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_relation.h" +#include "parser/parse_target.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + + +struct element_info +{ + Oid elementid; + char kind; + Oid relid; + char *aliasname; + ArrayType *key; + + char *srcvertex; + Oid srcvertexid; + Oid srcrelid; + ArrayType *srckey; + ArrayType *srcref; + + char *destvertex; + Oid destvertexid; + Oid destrelid; + ArrayType *destkey; + ArrayType *destref; + + List *labels; +}; + + +static ArrayType *propgraph_element_get_key(ParseState *pstate, const List *keycols, Relation element_rel, + const char *aliasname, int location); +static void propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols, + Relation edge_rel, Relation ref_rel, + const char *aliasname, int location, const char *type, + ArrayType **outkey, ArrayType **outref); +static ArrayType *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel); +static ArrayType *array_from_attnums(int numattrs, const AttrNumber *attnums); +static Oid insert_element_record(ObjectAddress pgaddress, struct element_info *einfo); +static Oid insert_label_record(Oid graphid, Oid peoid, const char *label); +static void insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties); +static void insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr); +static void check_element_properties(Oid peoid); +static void check_element_label_properties(Oid ellabeloid); +static void check_all_labels_properties(Oid pgrelid); +static Oid get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location); +static Oid get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location); +static Oid get_element_relid(Oid peid); +static List *get_graph_label_ids(Oid graphid); +static List *get_label_element_label_ids(Oid labelid); +static List *get_element_label_property_names(Oid ellabeloid); +static List *get_graph_property_ids(Oid graphid); + + +/* + * CREATE PROPERTY GRAPH + */ +ObjectAddress +CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt) +{ + CreateStmt *cstmt = makeNode(CreateStmt); + char components_persistence; + ListCell *lc; + ObjectAddress pgaddress; + List *vertex_infos = NIL; + List *edge_infos = NIL; + List *element_aliases = NIL; + List *element_oids = NIL; + + if (stmt->pgname->relpersistence == RELPERSISTENCE_UNLOGGED) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("property graphs cannot be unlogged because they do not have storage"))); + + components_persistence = RELPERSISTENCE_PERMANENT; + + foreach(lc, stmt->vertex_tables) + { + PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc); + struct element_info *vinfo; + Relation rel; + + vinfo = palloc0_object(struct element_info); + vinfo->kind = PGEKIND_VERTEX; + + vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(vinfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + components_persistence = RELPERSISTENCE_TEMP; + + if (vertex->vtable->alias) + vinfo->aliasname = vertex->vtable->alias->aliasname; + else + vinfo->aliasname = vertex->vtable->relname; + + if (list_member(element_aliases, makeString(vinfo->aliasname))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("alias \"%s\" used more than once as element table", vinfo->aliasname), + parser_errposition(pstate, vertex->location))); + + vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location); + + vinfo->labels = vertex->labels; + + table_close(rel, NoLock); + + vertex_infos = lappend(vertex_infos, vinfo); + + element_aliases = lappend(element_aliases, makeString(vinfo->aliasname)); + } + + foreach(lc, stmt->edge_tables) + { + PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc); + struct element_info *einfo; + Relation rel; + ListCell *lc2; + Oid srcrelid; + Oid destrelid; + Relation srcrel; + Relation destrel; + + einfo = palloc0_object(struct element_info); + einfo->kind = PGEKIND_EDGE; + + einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(einfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + components_persistence = RELPERSISTENCE_TEMP; + + if (edge->etable->alias) + einfo->aliasname = edge->etable->alias->aliasname; + else + einfo->aliasname = edge->etable->relname; + + if (list_member(element_aliases, makeString(einfo->aliasname))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("alias \"%s\" used more than once as element table", einfo->aliasname), + parser_errposition(pstate, edge->location))); + + einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location); + + einfo->srcvertex = edge->esrcvertex; + einfo->destvertex = edge->edestvertex; + + srcrelid = 0; + destrelid = 0; + foreach(lc2, vertex_infos) + { + struct element_info *vinfo = lfirst(lc2); + + if (strcmp(vinfo->aliasname, edge->esrcvertex) == 0) + srcrelid = vinfo->relid; + + if (strcmp(vinfo->aliasname, edge->edestvertex) == 0) + destrelid = vinfo->relid; + + if (srcrelid && destrelid) + break; + } + if (!srcrelid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("source vertex \"%s\" of edge \"%s\" does not exist", + edge->esrcvertex, einfo->aliasname), + parser_errposition(pstate, edge->location))); + if (!destrelid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("destination vertex \"%s\" of edge \"%s\" does not exist", + edge->edestvertex, einfo->aliasname), + parser_errposition(pstate, edge->location))); + + srcrel = table_open(srcrelid, NoLock); + destrel = table_open(destrelid, NoLock); + + propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel, + einfo->aliasname, edge->location, "SOURCE", + &einfo->srckey, &einfo->srcref); + propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel, + einfo->aliasname, edge->location, "DESTINATION", + &einfo->destkey, &einfo->destref); + + einfo->labels = edge->labels; + + table_close(destrel, NoLock); + table_close(srcrel, NoLock); + + table_close(rel, NoLock); + + edge_infos = lappend(edge_infos, einfo); + + element_aliases = lappend(element_aliases, makeString(einfo->aliasname)); + } + + cstmt->relation = stmt->pgname; + cstmt->oncommit = ONCOMMIT_NOOP; + + /* + * Automatically make it temporary if any component tables are temporary + * (see also DefineView()). + */ + if (stmt->pgname->relpersistence == RELPERSISTENCE_PERMANENT + && components_persistence == RELPERSISTENCE_TEMP) + { + cstmt->relation = copyObject(cstmt->relation); + cstmt->relation->relpersistence = RELPERSISTENCE_TEMP; + ereport(NOTICE, + (errmsg("property graph \"%s\" will be temporary", + stmt->pgname->relname))); + } + + pgaddress = DefineRelation(cstmt, RELKIND_PROPGRAPH, InvalidOid, NULL, NULL); + + foreach(lc, vertex_infos) + { + struct element_info *vinfo = lfirst(lc); + Oid peoid; + + peoid = insert_element_record(pgaddress, vinfo); + element_oids = lappend_oid(element_oids, peoid); + } + + foreach(lc, edge_infos) + { + struct element_info *einfo = lfirst(lc); + Oid peoid; + ListCell *lc2; + + /* + * Look up the vertices again. Now the vertices have OIDs assigned, + * which we need. + */ + foreach(lc2, vertex_infos) + { + struct element_info *vinfo = lfirst(lc2); + + if (strcmp(vinfo->aliasname, einfo->srcvertex) == 0) + { + einfo->srcvertexid = vinfo->elementid; + einfo->srcrelid = vinfo->relid; + } + if (strcmp(vinfo->aliasname, einfo->destvertex) == 0) + { + einfo->destvertexid = vinfo->elementid; + einfo->destrelid = vinfo->relid; + } + if (einfo->srcvertexid && einfo->destvertexid) + break; + } + Assert(einfo->srcvertexid); + Assert(einfo->destvertexid); + Assert(einfo->srcrelid); + Assert(einfo->destrelid); + peoid = insert_element_record(pgaddress, einfo); + element_oids = lappend_oid(element_oids, peoid); + } + + CommandCounterIncrement(); + + foreach_oid(peoid, element_oids) + check_element_properties(peoid); + check_all_labels_properties(pgaddress.objectId); + + return pgaddress; +} + +/* + * Process the key clause specified for an element. If key_clause is non-NIL, + * then it is a list of column names. Otherwise, the primary key of the + * relation is used. The return value is an array of column numbers. + */ +static ArrayType * +propgraph_element_get_key(ParseState *pstate, const List *key_clause, Relation element_rel, const char *aliasname, int location) +{ + ArrayType *a; + + if (key_clause == NIL) + { + Oid pkidx = RelationGetPrimaryKeyIndex(element_rel); + + if (!pkidx) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("no key specified and no suitable primary key exists for definition of element \"%s\"", aliasname), + parser_errposition(pstate, location)); + else + { + Relation indexDesc; + + indexDesc = index_open(pkidx, AccessShareLock); + a = array_from_attnums(indexDesc->rd_index->indkey.dim1, indexDesc->rd_index->indkey.values); + index_close(indexDesc, NoLock); + } + } + else + { + a = array_from_column_list(pstate, key_clause, location, element_rel); + } + + return a; +} + +/* + * Process the source or destination link of an edge. + * + * keycols and refcols are column names representing the local and referenced + * (vertex) columns. If they are both NIL, a matching foreign key is looked + * up. + * + * edge_rel and ref_rel are the local and referenced element tables. + * + * aliasname, location, and type are for error messages. type is either + * "SOURCE" or "DESTINATION". + * + * The outputs are arrays of column numbers in outkey and outref. + */ +static void +propgraph_edge_get_ref_keys(ParseState *pstate, const List *keycols, const List *refcols, + Relation edge_rel, Relation ref_rel, + const char *aliasname, int location, const char *type, + ArrayType **outkey, ArrayType **outref) +{ + Assert((keycols && refcols) || (!keycols && !refcols)); + + if (keycols) + { + if (list_length(keycols) != list_length(refcols)) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("mismatching number of columns in %s vertex definition of edge \"%s\"", type, aliasname), + parser_errposition(pstate, location)); + + *outkey = array_from_column_list(pstate, keycols, location, edge_rel); + *outref = array_from_column_list(pstate, refcols, location, ref_rel); + } + else + { + List *fkeys; + ListCell *lc; + int count = 0; + ForeignKeyCacheInfo *fk = NULL; + + fkeys = RelationGetFKeyList(edge_rel); + foreach(lc, fkeys) + { + fk = lfirst_node(ForeignKeyCacheInfo, lc); + + if (fk->confrelid == RelationGetRelid(ref_rel)) + count++; + } + + if (count == 0) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("no %s key specified and no suitable foreign key exists for definition of edge \"%s\"", type, aliasname), + parser_errposition(pstate, location)); + else if (count > 1) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("more than one suitable foreign key exists for %s key of edge \"%s\"", type, aliasname), + parser_errposition(pstate, location)); + + Assert(fk); + + *outkey = array_from_attnums(fk->nkeys, fk->conkey); + *outref = array_from_attnums(fk->nkeys, fk->confkey); + } +} + +/* + * Convert list of column names in the specified relation into an array of + * column numbers. + */ +static ArrayType * +array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel) +{ + int numattrs; + Datum *attnumsd; + int i; + ListCell *lc; + + numattrs = list_length(colnames); + attnumsd = palloc_array(Datum, numattrs); + + i = 0; + foreach(lc, colnames) + { + char *colname = strVal(lfirst(lc)); + Oid relid = RelationGetRelid(element_rel); + AttrNumber attnum; + + attnum = get_attnum(relid, colname); + if (!attnum) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colname, get_rel_name(relid)), + parser_errposition(pstate, location))); + attnumsd[i++] = Int16GetDatum(attnum); + } + + for (int j = 0; j < numattrs; j++) + { + for (int k = j + 1; k < numattrs; k++) + { + if (DatumGetInt16(attnumsd[j]) == DatumGetInt16(attnumsd[k])) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("graph key columns list must not contain duplicates"), + parser_errposition(pstate, location))); + } + } + + return construct_array_builtin(attnumsd, numattrs, INT2OID); +} + +static ArrayType * +array_from_attnums(int numattrs, const AttrNumber *attnums) +{ + Datum *attnumsd; + + attnumsd = palloc_array(Datum, numattrs); + + for (int i = 0; i < numattrs; i++) + attnumsd[i] = Int16GetDatum(attnums[i]); + + return construct_array_builtin(attnumsd, numattrs, INT2OID); +} + +static void +array_of_attnums_to_objectaddrs(Oid relid, ArrayType *arr, ObjectAddresses *addrs) +{ + Datum *attnumsd; + int numattrs; + + deconstruct_array_builtin(arr, INT2OID, &attnumsd, NULL, &numattrs); + + for (int i = 0; i < numattrs; i++) + { + ObjectAddress referenced; + + ObjectAddressSubSet(referenced, RelationRelationId, relid, DatumGetInt16(attnumsd[i])); + add_exact_object_address(&referenced, addrs); + } +} + +/* + * Insert a record for an element into the pg_propgraph_element catalog. Also + * inserts labels and properties into their respective catalogs. + */ +static Oid +insert_element_record(ObjectAddress pgaddress, struct element_info *einfo) +{ + Oid graphid = pgaddress.objectId; + Relation rel; + NameData aliasname; + Oid peoid; + Datum values[Natts_pg_propgraph_element] = {0}; + bool nulls[Natts_pg_propgraph_element] = {0}; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + ObjectAddresses *addrs; + + rel = table_open(PropgraphElementRelationId, RowExclusiveLock); + + peoid = GetNewOidWithIndex(rel, PropgraphElementObjectIndexId, Anum_pg_propgraph_element_oid); + einfo->elementid = peoid; + values[Anum_pg_propgraph_element_oid - 1] = ObjectIdGetDatum(peoid); + values[Anum_pg_propgraph_element_pgepgid - 1] = ObjectIdGetDatum(graphid); + values[Anum_pg_propgraph_element_pgerelid - 1] = ObjectIdGetDatum(einfo->relid); + namestrcpy(&aliasname, einfo->aliasname); + values[Anum_pg_propgraph_element_pgealias - 1] = NameGetDatum(&aliasname); + values[Anum_pg_propgraph_element_pgekind - 1] = CharGetDatum(einfo->kind); + values[Anum_pg_propgraph_element_pgesrcvertexid - 1] = ObjectIdGetDatum(einfo->srcvertexid); + values[Anum_pg_propgraph_element_pgedestvertexid - 1] = ObjectIdGetDatum(einfo->destvertexid); + values[Anum_pg_propgraph_element_pgekey - 1] = PointerGetDatum(einfo->key); + + if (einfo->srckey) + values[Anum_pg_propgraph_element_pgesrckey - 1] = PointerGetDatum(einfo->srckey); + else + nulls[Anum_pg_propgraph_element_pgesrckey - 1] = true; + if (einfo->srcref) + values[Anum_pg_propgraph_element_pgesrcref - 1] = PointerGetDatum(einfo->srcref); + else + nulls[Anum_pg_propgraph_element_pgesrcref - 1] = true; + if (einfo->destkey) + values[Anum_pg_propgraph_element_pgedestkey - 1] = PointerGetDatum(einfo->destkey); + else + nulls[Anum_pg_propgraph_element_pgedestkey - 1] = true; + if (einfo->destref) + values[Anum_pg_propgraph_element_pgedestref - 1] = PointerGetDatum(einfo->destref); + else + nulls[Anum_pg_propgraph_element_pgedestref - 1] = true; + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphElementRelationId, peoid); + + /* Add dependency on the property graph */ + recordDependencyOn(&myself, &pgaddress, DEPENDENCY_AUTO); + + addrs = new_object_addresses(); + + /* Add dependency on the relation */ + ObjectAddressSet(referenced, RelationRelationId, einfo->relid); + add_exact_object_address(&referenced, addrs); + array_of_attnums_to_objectaddrs(einfo->relid, einfo->key, addrs); + + /* Add dependencies on vertices */ + if (einfo->srcvertexid) + { + ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid); + add_exact_object_address(&referenced, addrs); + array_of_attnums_to_objectaddrs(einfo->relid, einfo->srckey, addrs); + array_of_attnums_to_objectaddrs(einfo->srcrelid, einfo->srcref, addrs); + } + if (einfo->destvertexid) + { + ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->destvertexid); + add_exact_object_address(&referenced, addrs); + array_of_attnums_to_objectaddrs(einfo->relid, einfo->destkey, addrs); + array_of_attnums_to_objectaddrs(einfo->destrelid, einfo->destref, addrs); + } + + /* TODO: dependencies on equality operators, like for foreign keys */ + + record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + + table_close(rel, NoLock); + + if (einfo->labels) + { + ListCell *lc; + + foreach(lc, einfo->labels) + { + PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc); + Oid ellabeloid; + + if (lp->label) + ellabeloid = insert_label_record(graphid, peoid, lp->label); + else + ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname); + insert_property_records(graphid, ellabeloid, einfo->relid, lp->properties); + + CommandCounterIncrement(); + } + } + else + { + Oid ellabeloid; + PropGraphProperties *pr = makeNode(PropGraphProperties); + + pr->all = true; + pr->location = -1; + + ellabeloid = insert_label_record(graphid, peoid, einfo->aliasname); + insert_property_records(graphid, ellabeloid, einfo->relid, pr); + } + + return peoid; +} + +/* + * Insert records for a label into the pg_propgraph_label and + * pg_propgraph_element_label catalogs, and register dependencies. + * + * Returns the OID of the new pg_propgraph_element_label record. + */ +static Oid +insert_label_record(Oid graphid, Oid peoid, const char *label) +{ + Oid labeloid; + Oid ellabeloid; + + /* + * Insert into pg_propgraph_label if not already existing. + */ + labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(graphid), CStringGetDatum(label)); + if (!labeloid) + { + Relation rel; + Datum values[Natts_pg_propgraph_label] = {0}; + bool nulls[Natts_pg_propgraph_label] = {0}; + NameData labelname; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + rel = table_open(PropgraphLabelRelationId, RowExclusiveLock); + + labeloid = GetNewOidWithIndex(rel, PropgraphLabelObjectIndexId, Anum_pg_propgraph_label_oid); + values[Anum_pg_propgraph_label_oid - 1] = ObjectIdGetDatum(labeloid); + values[Anum_pg_propgraph_label_pglpgid - 1] = ObjectIdGetDatum(graphid); + namestrcpy(&labelname, label); + values[Anum_pg_propgraph_label_pgllabel - 1] = NameGetDatum(&labelname); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphLabelRelationId, labeloid); + + ObjectAddressSet(referenced, RelationRelationId, graphid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + table_close(rel, NoLock); + } + + /* + * Insert into pg_propgraph_element_label + */ + { + Relation rel; + Datum values[Natts_pg_propgraph_element_label] = {0}; + bool nulls[Natts_pg_propgraph_element_label] = {0}; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + rel = table_open(PropgraphElementLabelRelationId, RowExclusiveLock); + + ellabeloid = GetNewOidWithIndex(rel, PropgraphElementLabelObjectIndexId, Anum_pg_propgraph_element_label_oid); + values[Anum_pg_propgraph_element_label_oid - 1] = ObjectIdGetDatum(ellabeloid); + values[Anum_pg_propgraph_element_label_pgellabelid - 1] = ObjectIdGetDatum(labeloid); + values[Anum_pg_propgraph_element_label_pgelelid - 1] = ObjectIdGetDatum(peoid); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphElementLabelRelationId, ellabeloid); + + ObjectAddressSet(referenced, PropgraphLabelRelationId, labeloid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + ObjectAddressSet(referenced, PropgraphElementRelationId, peoid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + table_close(rel, NoLock); + } + + return ellabeloid; +} + +/* + * Insert records for properties into the pg_propgraph_property catalog. + */ +static void +insert_property_records(Oid graphid, Oid ellabeloid, Oid pgerelid, const PropGraphProperties *properties) +{ + List *proplist = NIL; + ParseState *pstate; + ParseNamespaceItem *nsitem; + List *tp; + Relation rel; + ListCell *lc; + + if (properties->all) + { + Relation attRelation; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple attributeTuple; + + attRelation = table_open(AttributeRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(pgerelid)); + scan = systable_beginscan(attRelation, AttributeRelidNumIndexId, + true, NULL, 1, key); + while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) + { + Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple); + ColumnRef *cr; + ResTarget *rt; + + if (att->attnum <= 0 || att->attisdropped) + continue; + + cr = makeNode(ColumnRef); + rt = makeNode(ResTarget); + + cr->fields = list_make1(makeString(NameStr(att->attname))); + cr->location = -1; + + rt->name = pstrdup(NameStr(att->attname)); + rt->val = (Node *) cr; + rt->location = -1; + + proplist = lappend(proplist, rt); + } + systable_endscan(scan); + table_close(attRelation, RowShareLock); + } + else + { + proplist = properties->properties; + + foreach(lc, proplist) + { + ResTarget *rt = lfirst_node(ResTarget, lc); + + if (!rt->name && !IsA(rt->val, ColumnRef)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("property name required"), + parser_errposition(NULL, rt->location)); + } + } + + rel = table_open(pgerelid, AccessShareLock); + + pstate = make_parsestate(NULL); + nsitem = addRangeTableEntryForRelation(pstate, + rel, + AccessShareLock, + NULL, + false, + true); + addNSItemToQuery(pstate, nsitem, true, true, true); + + table_close(rel, NoLock); + + tp = transformTargetList(pstate, proplist, EXPR_KIND_PROPGRAPH_PROPERTY); + + foreach(lc, tp) + { + TargetEntry *te = lfirst_node(TargetEntry, lc); + + insert_property_record(graphid, ellabeloid, pgerelid, te->resname, te->expr); + } +} + +/* + * Insert records for a property into the pg_propgraph_property and + * pg_propgraph_label_property catalogs, and register dependencies. + */ +static void +insert_property_record(Oid graphid, Oid ellabeloid, Oid pgerelid, const char *propname, const Expr *expr) +{ + Oid propoid; + Oid exprtypid; + Oid proptypid; + + exprtypid = exprType((const Node *) expr); + + /* + * Insert into pg_propgraph_property if not already existing. + */ + propoid = GetSysCacheOid2(PROPGRAPHPROPNAME, Anum_pg_propgraph_property_oid, ObjectIdGetDatum(graphid), CStringGetDatum(propname)); + if (!propoid) + { + Relation rel; + NameData propnamedata; + Datum values[Natts_pg_propgraph_property] = {0}; + bool nulls[Natts_pg_propgraph_property] = {0}; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + proptypid = exprtypid; + + rel = table_open(PropgraphPropertyRelationId, RowExclusiveLock); + + propoid = GetNewOidWithIndex(rel, PropgraphPropertyObjectIndexId, Anum_pg_propgraph_property_oid); + values[Anum_pg_propgraph_property_oid - 1] = ObjectIdGetDatum(propoid); + values[Anum_pg_propgraph_property_pgppgid - 1] = ObjectIdGetDatum(graphid); + namestrcpy(&propnamedata, propname); + values[Anum_pg_propgraph_property_pgpname - 1] = NameGetDatum(&propnamedata); + values[Anum_pg_propgraph_property_pgptypid - 1] = ObjectIdGetDatum(proptypid); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphPropertyRelationId, propoid); + + ObjectAddressSet(referenced, RelationRelationId, graphid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + ObjectAddressSet(referenced, TypeRelationId, proptypid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + table_close(rel, NoLock); + } + else + { + proptypid = GetSysCacheOid1(PROPGRAPHPROPOID, Anum_pg_propgraph_property_pgptypid, ObjectIdGetDatum(propoid)); + } + + /* + * Check that in the graph, all properties with the same name have the + * same type (independent of which label they are on). (See SQL/PGQ + * subclause "Consistency check of a tabular property graph descriptor".) + */ + if (proptypid != exprtypid) + { + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("property \"%s\" data type mismatch: %s vs. %s", + propname, format_type_be(proptypid), format_type_be(exprtypid)), + errdetail("In a property graph, a property of the same name has to have the same data type in each label.")); + } + + /* + * Insert into pg_propgraph_label_property + */ + { + Relation rel; + Datum values[Natts_pg_propgraph_label_property] = {0}; + bool nulls[Natts_pg_propgraph_label_property] = {0}; + Oid plpoid; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + rel = table_open(PropgraphLabelPropertyRelationId, RowExclusiveLock); + + plpoid = GetNewOidWithIndex(rel, PropgraphLabelPropertyObjectIndexId, Anum_pg_propgraph_label_property_oid); + values[Anum_pg_propgraph_label_property_oid - 1] = ObjectIdGetDatum(plpoid); + values[Anum_pg_propgraph_label_property_plppropid - 1] = ObjectIdGetDatum(propoid); + values[Anum_pg_propgraph_label_property_plpellabelid - 1] = ObjectIdGetDatum(ellabeloid); + values[Anum_pg_propgraph_label_property_plpexpr - 1] = CStringGetTextDatum(nodeToString(expr)); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphLabelPropertyRelationId, plpoid); + + ObjectAddressSet(referenced, PropgraphPropertyRelationId, propoid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + ObjectAddressSet(referenced, PropgraphElementLabelRelationId, ellabeloid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + recordDependencyOnSingleRelExpr(&myself, (Node *) copyObject(expr), pgerelid, DEPENDENCY_NORMAL, DEPENDENCY_NORMAL, false); + + table_close(rel, NoLock); + } +} + +/* + * Check that for the given graph element, all properties with the same name + * have the same expression for each label. (See SQL/PGQ subclause "Creation + * of an element table descriptor".) + * + * We check this after all the catalog records are already inserted. This + * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER + * PROPERTY GRAPH. We pass in the element OID so that ALTER PROPERTY GRAPH + * only has to check the element it has just operated on. CREATE PROPERTY + * GROUP checks all elements it has created. + */ +static void +check_element_properties(Oid peoid) +{ + Relation rel1; + ScanKeyData key1[1]; + SysScanDesc scan1; + HeapTuple tuple1; + List *propoids = NIL; + List *propexprs = NIL; + + rel1 = table_open(PropgraphElementLabelRelationId, AccessShareLock); + ScanKeyInit(&key1[0], + Anum_pg_propgraph_element_label_pgelelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(peoid)); + + scan1 = systable_beginscan(rel1, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, key1); + while (HeapTupleIsValid(tuple1 = systable_getnext(scan1))) + { + Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple1); + Relation rel2; + ScanKeyData key2[1]; + SysScanDesc scan2; + HeapTuple tuple2; + + rel2 = table_open(PropgraphLabelPropertyRelationId, AccessShareLock); + ScanKeyInit(&key2[0], + Anum_pg_propgraph_label_property_plpellabelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ellabel->oid)); + + scan2 = systable_beginscan(rel2, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key2); + while (HeapTupleIsValid(tuple2 = systable_getnext(scan2))) + { + Form_pg_propgraph_label_property lprop = (Form_pg_propgraph_label_property) GETSTRUCT(tuple2); + Oid propoid; + Datum datum; + bool isnull; + char *propexpr; + ListCell *lc1, + *lc2; + bool found; + + propoid = lprop->plppropid; + datum = heap_getattr(tuple2, Anum_pg_propgraph_label_property_plpexpr, RelationGetDescr(rel2), &isnull); + Assert(!isnull); + propexpr = TextDatumGetCString(datum); + + found = false; + forboth(lc1, propoids, lc2, propexprs) + { + if (propoid == lfirst_oid(lc1)) + { + Node *na, + *nb; + + na = stringToNode(propexpr); + nb = stringToNode(lfirst(lc2)); + + found = true; + + if (!equal(na, nb)) + { + HeapTuple tuple3; + Form_pg_propgraph_element elform; + List *dpcontext; + char *dpa, + *dpb; + + tuple3 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peoid)); + if (!tuple3) + elog(ERROR, "cache lookup failed for property graph element %u", peoid); + elform = (Form_pg_propgraph_element) GETSTRUCT(tuple3); + dpcontext = deparse_context_for(get_rel_name(elform->pgerelid), elform->pgerelid); + + dpa = deparse_expression(na, dpcontext, false, false); + dpb = deparse_expression(nb, dpcontext, false, false); + + /* + * show in sorted order to keep output independent of + * index order + */ + if (strcmp(dpa, dpb) > 0) + { + char *tmp; + + tmp = dpa; + dpa = dpb; + dpb = tmp; + } + + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("element \"%s\" property \"%s\" expression mismatch: %s vs. %s", + NameStr(elform->pgealias), get_propgraph_property_name(propoid), dpa, dpb), + errdetail("In a property graph element, a property of the same name has to have the same expression in each label.")); + + ReleaseSysCache(tuple3); + } + + break; + } + } + + if (!found) + { + propoids = lappend_oid(propoids, propoid); + propexprs = lappend(propexprs, propexpr); + } + } + systable_endscan(scan2); + table_close(rel2, AccessShareLock); + } + + systable_endscan(scan1); + table_close(rel1, AccessShareLock); +} + +/* + * Check that for the given element label, all labels of the same name in the + * graph have the same number and names of properties (independent of which + * element they are on). (See SQL/PGQ subclause "Consistency check of a + * tabular property graph descriptor".) + * + * We check this after all the catalog records are already inserted. This + * makes it easier to share this code between CREATE PROPERTY GRAPH and ALTER + * PROPERTY GRAPH. We pass in the element label OID so that some variants of + * ALTER PROPERTY GRAPH only have to check the element label it has just + * operated on. CREATE PROPERTY GROUP and other ALTER PROPERTY GRAPH variants + * check all labels. + */ +static void +check_element_label_properties(Oid ellabeloid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + Oid labelid = InvalidOid; + Oid ref_ellabeloid = InvalidOid; + List *myprops, + *refprops; + List *diff1, + *diff2; + + rel = table_open(PropgraphElementLabelRelationId, AccessShareLock); + + /* + * Get element label info + */ + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_oid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(ellabeloid)); + scan = systable_beginscan(rel, PropgraphElementLabelObjectIndexId, true, NULL, 1, key); + if (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_propgraph_element_label ellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple); + + labelid = ellabel->pgellabelid; + } + systable_endscan(scan); + if (!labelid) + elog(ERROR, "element label %u not found", ellabeloid); + + /* + * Find a reference element label to fetch label properties. The + * reference element label has to have the label OID as the one being + * checked but be distinct from the one being checked. + */ + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_pgellabelid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(labelid)); + scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_propgraph_element_label otherellabel = (Form_pg_propgraph_element_label) GETSTRUCT(tuple); + + if (otherellabel->oid != ellabeloid) + { + ref_ellabeloid = otherellabel->oid; + break; + } + } + systable_endscan(scan); + + table_close(rel, AccessShareLock); + + /* + * If there is not previous definition of this label, then we are done. + */ + if (!ref_ellabeloid) + return; + + /* + * Now check number and names. + * + * XXX We could provide more detail in the error messages, but that would + * probably only be useful for some ALTER commands, because otherwise it's + * not really clear which label definition is the wrong one, and so you'd + * have to construct a rather verbose report to be of any use. Let's keep + * it simple for now. + */ + + myprops = get_element_label_property_names(ellabeloid); + refprops = get_element_label_property_names(ref_ellabeloid); + + if (list_length(refprops) != list_length(myprops)) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("mismatching number of properties in definition of label \"%s\"", get_propgraph_label_name(labelid))); + + diff1 = list_difference(myprops, refprops); + diff2 = list_difference(refprops, myprops); + + if (diff1 || diff2) + ereport(ERROR, + errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("mismatching properties names in definition of label \"%s\"", get_propgraph_label_name(labelid))); +} + +/* + * As above, but check all labels of a graph. + */ +static void +check_all_labels_properties(Oid pgrelid) +{ + foreach_oid(labeloid, get_graph_label_ids(pgrelid)) + { + foreach_oid(ellabeloid, get_label_element_label_ids(labeloid)) + { + check_element_label_properties(ellabeloid); + } + } +} + +/* + * ALTER PROPERTY GRAPH + */ +ObjectAddress +AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt) +{ + Oid pgrelid; + ListCell *lc; + ObjectAddress pgaddress; + + pgrelid = RangeVarGetRelidExtended(stmt->pgname, + ShareRowExclusiveLock, + stmt->missing_ok ? RVR_MISSING_OK : 0, + RangeVarCallbackOwnsRelation, + NULL); + if (pgrelid == InvalidOid) + { + ereport(NOTICE, + (errmsg("relation \"%s\" does not exist, skipping", + stmt->pgname->relname))); + return InvalidObjectAddress; + } + + ObjectAddressSet(pgaddress, RelationRelationId, pgrelid); + + foreach(lc, stmt->add_vertex_tables) + { + PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc); + struct element_info *vinfo; + Relation rel; + Oid peoid; + + vinfo = palloc0_object(struct element_info); + vinfo->kind = PGEKIND_VERTEX; + + vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(vinfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot add temporary element table to non-temporary property graph"), + errdetail("Table \"%s\" is a temporary table.", get_rel_name(vinfo->relid)), + parser_errposition(pstate, vertex->vtable->location))); + + if (vertex->vtable->alias) + vinfo->aliasname = vertex->vtable->alias->aliasname; + else + vinfo->aliasname = vertex->vtable->relname; + + vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, rel, vinfo->aliasname, vertex->location); + + vinfo->labels = vertex->labels; + + table_close(rel, NoLock); + + peoid = insert_element_record(pgaddress, vinfo); + + CommandCounterIncrement(); + check_element_properties(peoid); + check_all_labels_properties(pgrelid); + } + + foreach(lc, stmt->add_edge_tables) + { + PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc); + struct element_info *einfo; + Relation rel; + Relation srcrel; + Relation destrel; + Oid peoid; + + einfo = palloc0_object(struct element_info); + einfo->kind = PGEKIND_EDGE; + + einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(einfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && get_rel_persistence(pgrelid) != RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot add temporary element table to non-temporary property graph"), + errdetail("Table \"%s\" is a temporary table.", get_rel_name(einfo->relid)), + parser_errposition(pstate, edge->etable->location))); + + if (edge->etable->alias) + einfo->aliasname = edge->etable->alias->aliasname; + else + einfo->aliasname = edge->etable->relname; + + einfo->key = propgraph_element_get_key(pstate, edge->ekey, rel, einfo->aliasname, edge->location); + + einfo->srcvertexid = get_vertex_oid(pstate, pgrelid, edge->esrcvertex, edge->location); + einfo->destvertexid = get_vertex_oid(pstate, pgrelid, edge->edestvertex, edge->location); + + einfo->srcrelid = get_element_relid(einfo->srcvertexid); + einfo->destrelid = get_element_relid(einfo->destvertexid); + + srcrel = table_open(einfo->srcrelid, AccessShareLock); + destrel = table_open(einfo->destrelid, AccessShareLock); + + propgraph_edge_get_ref_keys(pstate, edge->esrckey, edge->esrcvertexcols, rel, srcrel, + einfo->aliasname, edge->location, "SOURCE", + &einfo->srckey, &einfo->srcref); + propgraph_edge_get_ref_keys(pstate, edge->edestkey, edge->edestvertexcols, rel, destrel, + einfo->aliasname, edge->location, "DESTINATION", + &einfo->destkey, &einfo->destref); + + einfo->labels = edge->labels; + + table_close(destrel, NoLock); + table_close(srcrel, NoLock); + + table_close(rel, NoLock); + + peoid = insert_element_record(pgaddress, einfo); + + CommandCounterIncrement(); + check_element_properties(peoid); + check_all_labels_properties(pgrelid); + } + + foreach(lc, stmt->drop_vertex_tables) + { + char *alias = strVal(lfirst(lc)); + Oid peoid; + ObjectAddress obj; + + peoid = get_vertex_oid(pstate, pgrelid, alias, -1); + ObjectAddressSet(obj, PropgraphElementRelationId, peoid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + + foreach(lc, stmt->drop_edge_tables) + { + char *alias = strVal(lfirst(lc)); + Oid peoid; + ObjectAddress obj; + + peoid = get_edge_oid(pstate, pgrelid, alias, -1); + ObjectAddressSet(obj, PropgraphElementRelationId, peoid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + + /* Remove any orphaned pg_propgraph_label entries */ + if (stmt->drop_vertex_tables || stmt->drop_edge_tables) + { + foreach_oid(labeloid, get_graph_label_ids(pgrelid)) + { + if (!get_label_element_label_ids(labeloid)) + { + ObjectAddress obj; + + ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + } + } + + foreach(lc, stmt->add_labels) + { + PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc); + Oid peoid; + Oid pgerelid; + Oid ellabeloid; + + Assert(lp->label); + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + pgerelid = get_element_relid(peoid); + + ellabeloid = insert_label_record(pgrelid, peoid, lp->label); + insert_property_records(pgrelid, ellabeloid, pgerelid, lp->properties); + + CommandCounterIncrement(); + check_element_properties(peoid); + check_element_label_properties(ellabeloid); + } + + if (stmt->drop_label) + { + Oid peoid; + Oid labeloid; + Oid ellabeloid; + ObjectAddress obj; + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + ObjectIdGetDatum(pgrelid), + CStringGetDatum(stmt->drop_label)); + if (!labeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label), + parser_errposition(pstate, -1)); + + ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL, + Anum_pg_propgraph_element_label_oid, + ObjectIdGetDatum(peoid), + ObjectIdGetDatum(labeloid)); + + if (!ellabeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label), + parser_errposition(pstate, -1)); + + ObjectAddressSet(obj, PropgraphElementLabelRelationId, ellabeloid); + performDeletion(&obj, stmt->drop_behavior, 0); + + /* Remove any orphaned pg_propgraph_label entries */ + if (!get_label_element_label_ids(labeloid)) + { + ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + } + + if (stmt->add_properties) + { + Oid peoid; + Oid pgerelid; + Oid labeloid; + Oid ellabeloid; + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + ObjectIdGetDatum(pgrelid), + CStringGetDatum(stmt->alter_label)); + if (!labeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label), + parser_errposition(pstate, -1)); + + ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL, + Anum_pg_propgraph_element_label_oid, + ObjectIdGetDatum(peoid), + ObjectIdGetDatum(labeloid)); + if (!ellabeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label), + parser_errposition(pstate, -1)); + + pgerelid = get_element_relid(peoid); + + insert_property_records(pgrelid, ellabeloid, pgerelid, stmt->add_properties); + + CommandCounterIncrement(); + check_element_properties(peoid); + check_element_label_properties(ellabeloid); + } + + if (stmt->drop_properties) + { + Oid peoid; + Oid labeloid; + Oid ellabeloid; + ObjectAddress obj; + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + ObjectIdGetDatum(pgrelid), + CStringGetDatum(stmt->alter_label)); + if (!labeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label), + parser_errposition(pstate, -1)); + + ellabeloid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL, + Anum_pg_propgraph_element_label_oid, + ObjectIdGetDatum(peoid), + ObjectIdGetDatum(labeloid)); + + if (!ellabeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label), + parser_errposition(pstate, -1)); + + foreach(lc, stmt->drop_properties) + { + char *propname = strVal(lfirst(lc)); + Oid propoid; + Oid plpoid; + + propoid = GetSysCacheOid2(PROPGRAPHPROPNAME, + Anum_pg_propgraph_property_oid, + ObjectIdGetDatum(pgrelid), + CStringGetDatum(propname)); + if (!propoid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" label \"%s\" has no property \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label, propname), + parser_errposition(pstate, -1)); + + plpoid = GetSysCacheOid2(PROPGRAPHLABELPROP, Anum_pg_propgraph_label_property_oid, ObjectIdGetDatum(ellabeloid), ObjectIdGetDatum(propoid)); + + ObjectAddressSet(obj, PropgraphLabelPropertyRelationId, plpoid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + + check_element_label_properties(ellabeloid); + } + + /* Remove any orphaned pg_propgraph_property entries */ + if (stmt->drop_properties || stmt->drop_vertex_tables || stmt->drop_edge_tables) + { + foreach_oid(propoid, get_graph_property_ids(pgrelid)) + { + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + + rel = table_open(PropgraphLabelPropertyRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_property_plppropid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(propoid)); + scan = systable_beginscan(rel, InvalidOid /* FIXME */ , + true, NULL, 1, key); + if (!systable_getnext(scan)) + { + ObjectAddress obj; + + ObjectAddressSet(obj, PropgraphPropertyRelationId, propoid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + + systable_endscan(scan); + table_close(rel, RowShareLock); + } + } + + return pgaddress; +} + +/* + * Get OID of vertex from graph OID and element alias. Element must be a + * vertex, otherwise error. + */ +static Oid +get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location) +{ + HeapTuple tuple; + Oid peoid; + + tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias)); + if (!tuple) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" has no element with alias \"%s\"", + get_rel_name(pgrelid), alias), + parser_errposition(pstate, location)); + + if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_VERTEX) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("element \"%s\" of property graph \"%s\" is not a vertex", + alias, get_rel_name(pgrelid)), + parser_errposition(pstate, location)); + + peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid; + + ReleaseSysCache(tuple); + + return peoid; +} + +/* + * Get OID of edge from graph OID and element alias. Element must be an edge, + * otherwise error. + */ +static Oid +get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location) +{ + HeapTuple tuple; + Oid peoid; + + tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias)); + if (!tuple) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" has no element with alias \"%s\"", + get_rel_name(pgrelid), alias), + parser_errposition(pstate, location)); + + if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_EDGE) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("element \"%s\" of property graph \"%s\" is not an edge", + alias, get_rel_name(pgrelid)), + parser_errposition(pstate, location)); + + peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid; + + ReleaseSysCache(tuple); + + return peoid; +} + +/* + * Get the element table relation OID from the OID of the element. + */ +static Oid +get_element_relid(Oid peid) +{ + HeapTuple tuple; + Oid pgerelid; + + tuple = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peid)); + if (!tuple) + elog(ERROR, "cache lookup failed for property graph element %u", peid); + + pgerelid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgerelid; + + ReleaseSysCache(tuple); + + return pgerelid; +} + +/* + * Get a list of all label OIDs of a graph. + */ +static List * +get_graph_label_ids(Oid graphid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + List *result = NIL; + + rel = table_open(PropgraphLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_pglpgid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graphid)); + scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, true, NULL, 1, key); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + result = lappend_oid(result, ((Form_pg_propgraph_label) GETSTRUCT(tuple))->oid); + } + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return result; +} + +/* + * Get a list of all element label OIDs for a label. + */ +static List * +get_label_element_label_ids(Oid labelid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + List *result = NIL; + + rel = table_open(PropgraphElementLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_pgellabelid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(labelid)); + scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, NULL, 1, key); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + result = lappend_oid(result, ((Form_pg_propgraph_element_label) GETSTRUCT(tuple))->oid); + } + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return result; +} + +/* + * Get the names of properties associated with the given element label OID. + * + * The result is a list of String nodes (so we can use list functions to + * detect differences). + */ +static List * +get_element_label_property_names(Oid ellabeloid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + List *result = NIL; + + rel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_property_plpellabelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ellabeloid)); + + scan = systable_beginscan(rel, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, key); + + while ((tuple = systable_getnext(scan))) + { + Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tuple); + + result = lappend(result, makeString(get_propgraph_property_name(plpform->plppropid))); + } + + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return result; +} + +/* + * Get a list of all property OIDs of a graph. + */ +static List * +get_graph_property_ids(Oid graphid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tuple; + List *result = NIL; + + rel = table_open(PropgraphPropertyRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_property_pgppgid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graphid)); + scan = systable_beginscan(rel, PropgraphPropertyNameIndexId, true, NULL, 1, key); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + result = lappend_oid(result, ((Form_pg_propgraph_property) GETSTRUCT(tuple))->oid); + } + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return result; +} diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 5607273bf9f..1933bd1ca3f 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -80,6 +80,7 @@ SecLabelSupportsObjectType(ObjectType objtype) case OBJECT_OPFAMILY: case OBJECT_PARAMETER_ACL: case OBJECT_POLICY: + case OBJECT_PROPGRAPH: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: case OBJECT_RULE: diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 66cda26a25f..47b03e7f38b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -304,6 +304,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = { gettext_noop("index \"%s\" does not exist, skipping"), gettext_noop("\"%s\" is not an index"), gettext_noop("Use DROP INDEX to remove an index.")}, + {RELKIND_PROPGRAPH, + ERRCODE_UNDEFINED_OBJECT, + gettext_noop("property graph \"%s\" does not exist"), + gettext_noop("property graph \"%s\" does not exist, skipping"), + gettext_noop("\"%s\" is not a property graph"), + gettext_noop("Use DROP PROPERTY GRAPH to remove a property graph.")}, {'\0', 0, NULL, NULL, NULL, NULL} }; @@ -1514,6 +1520,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); @@ -14339,6 +14349,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: @@ -17758,6 +17769,11 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a composite type", rv->relname))); + if (reltype == OBJECT_PROPGRAPH && relkind != RELKIND_PROPGRAPH) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a property graph", rv->relname))); + if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX && relkind != RELKIND_PARTITIONED_INDEX && !IsA(stmt, RenameStmt)) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4d7c92d63c1..4c46537480f 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -600,7 +600,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos, */ Assert(rte->rtekind == RTE_RELATION || (rte->rtekind == RTE_SUBQUERY && - rte->relkind == RELKIND_VIEW)); + (rte->relkind == RELKIND_VIEW || rte->relkind == RELKIND_PROPGRAPH))); (void) getRTEPermissionInfo(rteperminfos, rte); /* Many-to-one mapping not allowed */ @@ -1115,6 +1115,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), @@ -1179,6 +1185,13 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType) errmsg("cannot lock rows in foreign table \"%s\"", RelationGetRelationName(rel)))); break; + case RELKIND_PROPGRAPH: + /* Should not get here; rewriter should have expanded the graph */ + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot lock rows in property graph \"%s\"", + RelationGetRelationName(rel)))); + break; default: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 89ee4b61f2f..2c4e9b96c16 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -281,6 +281,9 @@ exprType(const Node *expr) case T_PlaceHolderVar: type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_GraphPropertyRef: + type = ((const GraphPropertyRef *) expr)->typeId; + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -531,6 +534,9 @@ exprTypmod(const Node *expr) return ((const SetToDefault *) expr)->typeMod; case T_PlaceHolderVar: return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr); + case T_GraphPropertyRef: + /* TODO */ + return -1; default: break; } @@ -1053,6 +1059,9 @@ exprCollation(const Node *expr) case T_PlaceHolderVar: coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_GraphPropertyRef: + coll = DEFAULT_COLLATION_OID; /* FIXME */ + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); coll = InvalidOid; /* keep compiler quiet */ @@ -2124,6 +2133,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; @@ -2664,6 +2674,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)); @@ -2857,6 +2887,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: @@ -3895,6 +3931,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: @@ -4509,6 +4549,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; @@ -4667,6 +4719,26 @@ raw_expression_tree_walker_impl(Node *node, return true; } break; + case T_GraphElementPattern: + { + GraphElementPattern *gep = (GraphElementPattern *) node; + + if (WALK(gep->subexpr)) + return true; + if (WALK(gep->whereClause)) + return true; + } + break; + case T_GraphPattern: + { + GraphPattern *gp = (GraphPattern *) node; + + if (WALK(gp->path_pattern_list)) + return true; + if (WALK(gp->whereClause)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3337b77ae6d..bfce452ded6 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -559,6 +559,11 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) /* we re-use these RELATION fields, too: */ WRITE_OID_FIELD(relid); break; + case RTE_GRAPH_TABLE: + WRITE_OID_FIELD(relid); + WRITE_NODE_FIELD(graph_pattern); + WRITE_NODE_FIELD(graph_table_columns); + break; case RTE_RESULT: /* no extra fields */ break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index c4d01a441a0..19f3e028aba 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -419,6 +419,11 @@ _readRangeTblEntry(void) /* we re-use these RELATION fields, too: */ READ_OID_FIELD(relid); break; + case RTE_GRAPH_TABLE: + READ_OID_FIELD(relid); + READ_NODE_FIELD(graph_pattern); + READ_NODE_FIELD(graph_table_columns); + break; case RTE_RESULT: /* no extra fields */ break; diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4895cee9944..ffb5b60288e 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -728,6 +728,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, */ return; + case RTE_GRAPH_TABLE: + /* shouldn't happen here */ + break; + case RTE_RESULT: /* RESULT RTEs, in themselves, are no problem. */ break; diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 5482ab85a76..4e940a567ed 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1237,6 +1237,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, case RTE_RESULT: /* these can't contain any lateral references */ break; + case RTE_GRAPH_TABLE: + /* shouldn't happen here */ + Assert(false); + break; } } } @@ -2296,6 +2300,10 @@ replace_vars_in_jointree(Node *jtnode, /* these shouldn't be marked LATERAL */ Assert(false); break; + case RTE_GRAPH_TABLE: + /* shouldn't happen here */ + Assert(false); + break; } } } diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile index 3162a01f302..0fa237a4a90 100644 --- a/src/backend/parser/Makefile +++ b/src/backend/parser/Makefile @@ -23,6 +23,7 @@ OBJS = \ parse_enr.o \ parse_expr.o \ parse_func.o \ + parse_graphtable.o \ parse_jsontable.o \ parse_merge.o \ parse_node.o \ diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 28fed9d87f6..0388b2ee9af 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2058,7 +2058,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, char selectName[32]; ParseNamespaceItem *nsitem; RangeTblRef *rtr; - ListCell *tl; /* * Transform SelectStmt into a Query. @@ -2098,6 +2097,8 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, */ if (targetlist) { + ListCell *tl; + *targetlist = NIL; foreach(tl, selectQuery->targetList) { @@ -2132,8 +2133,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, SetOperationStmt *op = makeNode(SetOperationStmt); List *ltargetlist; List *rtargetlist; - ListCell *ltl; - ListCell *rtl; const char *context; bool recursive = (pstate->p_parent_cte && pstate->p_parent_cte->cterecursive); @@ -2168,161 +2167,170 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, false, &rtargetlist); - /* - * Verify that the two children have the same number of non-junk - * columns, and determine the types of the merged output columns. - */ - if (list_length(ltargetlist) != list_length(rtargetlist)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("each %s query must have the same number of columns", - context), - parser_errposition(pstate, - exprLocation((Node *) rtargetlist)))); + constructSetOpTargetlist(op, ltargetlist, rtargetlist, targetlist, + context, pstate, recursive); - if (targetlist) - *targetlist = NIL; - op->colTypes = NIL; - op->colTypmods = NIL; - op->colCollations = NIL; - op->groupClauses = NIL; - forboth(ltl, ltargetlist, rtl, rtargetlist) - { - TargetEntry *ltle = (TargetEntry *) lfirst(ltl); - TargetEntry *rtle = (TargetEntry *) lfirst(rtl); - Node *lcolnode = (Node *) ltle->expr; - Node *rcolnode = (Node *) rtle->expr; - Oid lcoltype = exprType(lcolnode); - Oid rcoltype = exprType(rcolnode); - Node *bestexpr; - int bestlocation; - Oid rescoltype; - int32 rescoltypmod; - Oid rescolcoll; - - /* select common type, same as CASE et al */ - rescoltype = select_common_type(pstate, - list_make2(lcolnode, rcolnode), - context, - &bestexpr); - bestlocation = exprLocation(bestexpr); + return (Node *) op; + } +} - /* - * Verify the coercions are actually possible. If not, we'd fail - * later anyway, but we want to fail now while we have sufficient - * context to produce an error cursor position. - * - * For all non-UNKNOWN-type cases, we verify coercibility but we - * don't modify the child's expression, for fear of changing the - * child query's semantics. - * - * If a child expression is an UNKNOWN-type Const or Param, we - * want to replace it with the coerced expression. This can only - * happen when the child is a leaf set-op node. It's safe to - * replace the expression because if the child query's semantics - * depended on the type of this output column, it'd have already - * coerced the UNKNOWN to something else. We want to do this - * because (a) we want to verify that a Const is valid for the - * target type, or resolve the actual type of an UNKNOWN Param, - * and (b) we want to avoid unnecessary discrepancies between the - * output type of the child query and the resolved target type. - * Such a discrepancy would disable optimization in the planner. - * - * If it's some other UNKNOWN-type node, eg a Var, we do nothing - * (knowing that coerce_to_common_type would fail). The planner - * is sometimes able to fold an UNKNOWN Var to a constant before - * it has to coerce the type, so failing now would just break - * cases that might work. - */ - if (lcoltype != UNKNOWNOID) - lcolnode = coerce_to_common_type(pstate, lcolnode, - rescoltype, context); - else if (IsA(lcolnode, Const) || - IsA(lcolnode, Param)) - { - lcolnode = coerce_to_common_type(pstate, lcolnode, - rescoltype, context); - ltle->expr = (Expr *) lcolnode; - } +void +constructSetOpTargetlist(SetOperationStmt *op, List *ltargetlist, List *rtargetlist, + List **targetlist, const char *context, ParseState *pstate, + bool recursive) +{ + ListCell *ltl; + ListCell *rtl; - if (rcoltype != UNKNOWNOID) - rcolnode = coerce_to_common_type(pstate, rcolnode, - rescoltype, context); - else if (IsA(rcolnode, Const) || - IsA(rcolnode, Param)) - { - rcolnode = coerce_to_common_type(pstate, rcolnode, - rescoltype, context); - rtle->expr = (Expr *) rcolnode; - } + /* + * Verify that the two children have the same number of non-junk columns, + * and determine the types of the merged output columns. + */ + if (list_length(ltargetlist) != list_length(rtargetlist)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("each %s query must have the same number of columns", + context), + parser_errposition(pstate, + exprLocation((Node *) rtargetlist)))); - rescoltypmod = select_common_typmod(pstate, - list_make2(lcolnode, rcolnode), - rescoltype); + if (targetlist) + *targetlist = NIL; + op->colTypes = NIL; + op->colTypmods = NIL; + op->colCollations = NIL; + op->groupClauses = NIL; - /* - * Select common collation. A common collation is required for - * all set operators except UNION ALL; see SQL:2008 7.13 Syntax Rule 15c. (If we fail to identify a common - * collation for a UNION ALL column, the colCollations element - * will be set to InvalidOid, which may result in a runtime error - * if something at a higher query level wants to use the column's - * collation.) - */ - rescolcoll = select_common_collation(pstate, - list_make2(lcolnode, rcolnode), - (op->op == SETOP_UNION && op->all)); + forboth(ltl, ltargetlist, rtl, rtargetlist) + { + TargetEntry *ltle = (TargetEntry *) lfirst(ltl); + TargetEntry *rtle = (TargetEntry *) lfirst(rtl); + Node *lcolnode = (Node *) ltle->expr; + Node *rcolnode = (Node *) rtle->expr; + Oid lcoltype = exprType(lcolnode); + Oid rcoltype = exprType(rcolnode); + Node *bestexpr; + int bestlocation; + Oid rescoltype; + int32 rescoltypmod; + Oid rescolcoll; + + /* select common type, same as CASE et al */ + rescoltype = select_common_type(pstate, + list_make2(lcolnode, rcolnode), + context, + &bestexpr); + bestlocation = exprLocation(bestexpr); - /* emit results */ - op->colTypes = lappend_oid(op->colTypes, rescoltype); - op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); - op->colCollations = lappend_oid(op->colCollations, rescolcoll); + /* + * Verify the coercions are actually possible. If not, we'd fail + * later anyway, but we want to fail now while we have sufficient + * context to produce an error cursor position. + * + * For all non-UNKNOWN-type cases, we verify coercibility but we don't + * modify the child's expression, for fear of changing the child + * query's semantics. + * + * If a child expression is an UNKNOWN-type Const or Param, we want to + * replace it with the coerced expression. This can only happen when + * the child is a leaf set-op node. It's safe to replace the + * expression because if the child query's semantics depended on the + * type of this output column, it'd have already coerced the UNKNOWN + * to something else. We want to do this because (a) we want to + * verify that a Const is valid for the target type, or resolve the + * actual type of an UNKNOWN Param, and (b) we want to avoid + * unnecessary discrepancies between the output type of the child + * query and the resolved target type. Such a discrepancy would + * disable optimization in the planner. + * + * If it's some other UNKNOWN-type node, eg a Var, we do nothing + * (knowing that coerce_to_common_type would fail). The planner is + * sometimes able to fold an UNKNOWN Var to a constant before it has + * to coerce the type, so failing now would just break cases that + * might work. + */ + if (lcoltype != UNKNOWNOID) + lcolnode = coerce_to_common_type(pstate, lcolnode, + rescoltype, context); + else if (IsA(lcolnode, Const) || + IsA(lcolnode, Param)) + { + lcolnode = coerce_to_common_type(pstate, lcolnode, + rescoltype, context); + ltle->expr = (Expr *) lcolnode; + } - /* - * For all cases except UNION ALL, identify the grouping operators - * (and, if available, sorting operators) that will be used to - * eliminate duplicates. - */ - if (op->op != SETOP_UNION || !op->all) - { - ParseCallbackState pcbstate; + if (rcoltype != UNKNOWNOID) + rcolnode = coerce_to_common_type(pstate, rcolnode, + rescoltype, context); + else if (IsA(rcolnode, Const) || + IsA(rcolnode, Param)) + { + rcolnode = coerce_to_common_type(pstate, rcolnode, + rescoltype, context); + rtle->expr = (Expr *) rcolnode; + } - setup_parser_errposition_callback(&pcbstate, pstate, - bestlocation); + rescoltypmod = select_common_typmod(pstate, + list_make2(lcolnode, rcolnode), + rescoltype); - /* - * If it's a recursive union, we need to require hashing - * support. - */ - op->groupClauses = lappend(op->groupClauses, - makeSortGroupClauseForSetOp(rescoltype, recursive)); + /* + * Select common collation. A common collation is required for all + * set operators except UNION ALL; see SQL:2008 7.13 Syntax Rule 15c. (If we fail to identify a common + * collation for a UNION ALL column, the colCollations element will be + * set to InvalidOid, which may result in a runtime error if something + * at a higher query level wants to use the column's collation.) + */ + rescolcoll = select_common_collation(pstate, + list_make2(lcolnode, rcolnode), + (op->op == SETOP_UNION && op->all)); - cancel_parser_errposition_callback(&pcbstate); - } + /* emit results */ + op->colTypes = lappend_oid(op->colTypes, rescoltype); + op->colTypmods = lappend_int(op->colTypmods, rescoltypmod); + op->colCollations = lappend_oid(op->colCollations, rescolcoll); - /* - * Construct a dummy tlist entry to return. We use a SetToDefault - * node for the expression, since it carries exactly the fields - * needed, but any other expression node type would do as well. - */ - if (targetlist) - { - SetToDefault *rescolnode = makeNode(SetToDefault); - TargetEntry *restle; - - rescolnode->typeId = rescoltype; - rescolnode->typeMod = rescoltypmod; - rescolnode->collation = rescolcoll; - rescolnode->location = bestlocation; - restle = makeTargetEntry((Expr *) rescolnode, - 0, /* no need to set resno */ - NULL, - false); - *targetlist = lappend(*targetlist, restle); - } + /* + * For all cases except UNION ALL, identify the grouping operators + * (and, if available, sorting operators) that will be used to + * eliminate duplicates. + */ + if (op->op != SETOP_UNION || !op->all) + { + ParseCallbackState pcbstate; + + setup_parser_errposition_callback(&pcbstate, pstate, + bestlocation); + + /* If it's a recursive union, we need to require hashing support. */ + op->groupClauses = lappend(op->groupClauses, + makeSortGroupClauseForSetOp(rescoltype, recursive)); + + cancel_parser_errposition_callback(&pcbstate); } - return (Node *) op; + /* + * Construct a dummy tlist entry to return. We use a SetToDefault + * node for the expression, since it carries exactly the fields + * needed, but any other expression node type would do as well. + */ + if (targetlist) + { + SetToDefault *rescolnode = makeNode(SetToDefault); + TargetEntry *restle; + + rescolnode->typeId = rescoltype; + rescolnode->typeMod = rescoltypmod; + rescolnode->collation = rescolcoll; + rescolnode->location = bestlocation; + restle = makeTargetEntry((Expr *) rescolnode, + 0, /* no need to set resno */ + NULL, + false); + *targetlist = lappend(*targetlist, restle); + } } } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 4a4b47ca501..66eb1d5ce69 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -298,6 +298,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 @@ -679,6 +680,36 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_object_constructor_null_clause_opt json_array_constructor_null_clause_opt +%type 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 vertex_table_definition edge_table_definition +%type opt_propgraph_table_alias +%type element_table_label_clause +%type label_and_properties element_table_properties + add_label +%type vertex_or_edge + +%type opt_graph_pattern_quantifier + path_pattern_list + path_pattern + path_pattern_expression + path_term +%type graph_pattern + path_factor + path_primary + opt_is_label_expression + label_expression + label_disjunction + label_term +%type opt_colid + /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -722,18 +753,18 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS - DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC + DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC DESTINATION DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP - EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE + EACH EDGE ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS - GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS + GENERATED GLOBAL GRANT GRANTED GRAPH GRAPH_TABLE GREATEST GROUP_P GROUPING GROUPS HANDLER HAVING HEADER_P HOLD HOUR_P @@ -754,7 +785,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 @@ -766,12 +797,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); PARALLEL PARAMETER PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PATH PLACING PLAN PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY - PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION + PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PROPERTIES PROPERTY PUBLICATION QUOTE QUOTES RANGE READ REAL REASSIGN RECHECK RECURSIVE REF_P REFERENCES REFERENCING - REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA + REFRESH REINDEX RELATIONSHIP RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE @@ -791,7 +822,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); UNLISTEN UNLOGGED UNTIL UPDATE USER USING VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING - VERBOSE VERSION_P VIEW VIEWS VOLATILE + VERBOSE VERSION_P VERTEX VIEW VIEWS VOLATILE WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE @@ -889,7 +920,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 '^' @@ -1020,6 +1051,7 @@ stmt: | AlterOperatorStmt | AlterTypeStmt | AlterPolicyStmt + | AlterPropGraphStmt | AlterSeqStmt | AlterSystemStmt | AlterTableStmt @@ -1060,6 +1092,7 @@ stmt: | AlterOpFamilyStmt | CreatePolicyStmt | CreatePLangStmt + | CreatePropGraphStmt | CreateSchemaStmt | CreateSeqStmt | CreateStmt @@ -6999,6 +7032,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; } @@ -7855,6 +7889,15 @@ privilege_target: n->objs = $2; $$ = n; } + | PROPERTY GRAPH qualified_name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + + n->targtype = ACL_TARGET_OBJECT; + n->objtype = OBJECT_PROPGRAPH; + n->objs = $3; + $$ = n; + } | SCHEMA name_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); @@ -9172,6 +9215,366 @@ opt_if_exists: IF_P EXISTS { $$ = true; } ; +/***************************************************************************** + * + * CREATE PROPERTY GRAPH + * ALTER PROPERTY GRAPH + * + *****************************************************************************/ + +CreatePropGraphStmt: CREATE OptTemp PROPERTY GRAPH qualified_name opt_vertex_tables_clause opt_edge_tables_clause + { + CreatePropGraphStmt *n = makeNode(CreatePropGraphStmt); + + n->pgname = $5; + n->pgname->relpersistence = $2; + n->vertex_tables = $6; + n->edge_tables = $7; + + $$ = (Node *)n; + } + ; + +opt_vertex_tables_clause: + vertex_tables_clause { $$ = $1; } + | /*EMPTY*/ { $$ = NIL; } + ; + +vertex_tables_clause: + vertex_synonym TABLES '(' vertex_table_list ')' { $$ = $4; } + ; + +vertex_synonym: NODE | VERTEX + ; + +vertex_table_list: vertex_table_definition { $$ = list_make1($1); } + | vertex_table_list ',' vertex_table_definition { $$ = lappend($1, $3); } + ; + +vertex_table_definition: qualified_name opt_propgraph_table_alias opt_graph_table_key_clause + opt_element_table_label_and_properties + { + PropGraphVertex *n = makeNode(PropGraphVertex); + + $1->alias = $2; + n->vtable = $1; + n->vkey = $3; + n->labels = $4; + n->location = @1; + + $$ = (Node *) n; + } + ; + +opt_propgraph_table_alias: + AS name + { + $$ = makeNode(Alias); + $$->aliasname = $2; + } + | /*EMPTY*/ { $$ = NULL; } + ; + +opt_graph_table_key_clause: + KEY '(' columnList ')' { $$ = $3; } + | /*EMPTY*/ { $$ = NIL; } + ; + +opt_edge_tables_clause: + edge_tables_clause { $$ = $1; } + | /*EMPTY*/ { $$ = NIL; } + ; + +edge_tables_clause: + edge_synonym TABLES '(' edge_table_list ')' { $$ = $4; } + ; + +edge_synonym: EDGE | RELATIONSHIP + ; + +edge_table_list: edge_table_definition { $$ = list_make1($1); } + | edge_table_list ',' edge_table_definition { $$ = lappend($1, $3); } + ; + +edge_table_definition: qualified_name opt_propgraph_table_alias opt_graph_table_key_clause + source_vertex_table destination_vertex_table opt_element_table_label_and_properties + { + PropGraphEdge *n = makeNode(PropGraphEdge); + + $1->alias = $2; + n->etable = $1; + n->ekey = $3; + n->esrckey = linitial($4); + n->esrcvertex = lsecond($4); + n->esrcvertexcols = lthird($4); + n->edestkey = linitial($5); + n->edestvertex = lsecond($5); + n->edestvertexcols = lthird($5); + n->labels = $6; + n->location = @1; + + $$ = (Node *) n; + } + ; + +source_vertex_table: SOURCE name + { + $$ = list_make3(NULL, $2, NULL); + } + | SOURCE KEY '(' columnList ')' REFERENCES name '(' columnList ')' + { + $$ = list_make3($4, $7, $9); + } + ; + +destination_vertex_table: DESTINATION name + { + $$ = list_make3(NULL, $2, NULL); + } + | DESTINATION KEY '(' columnList ')' REFERENCES name '(' columnList ')' + { + $$ = list_make3($4, $7, $9); + } + ; + +opt_element_table_label_and_properties: + element_table_properties + { + PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties); + + lp->properties = (PropGraphProperties *) $1; + lp->location = @1; + + $$ = list_make1(lp); + } + | label_and_properties_list + { + $$ = $1; + } + | /*EMPTY*/ + { + PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties); + PropGraphProperties *pr = makeNode(PropGraphProperties); + + pr->all = true; + pr->location = -1; + lp->properties = pr; + lp->location = -1; + + $$ = list_make1(lp); + } + ; + +element_table_properties: + NO PROPERTIES + { + PropGraphProperties *pr = makeNode(PropGraphProperties); + + pr->properties = NIL; + pr->location = @1; + + $$ = (Node *) pr; + } + | PROPERTIES ALL COLUMNS + /*| PROPERTIES ARE ALL COLUMNS */ + { + PropGraphProperties *pr = makeNode(PropGraphProperties); + + pr->all = true; + pr->location = @1; + + $$ = (Node *) pr; + } + | PROPERTIES '(' xml_attribute_list ')' + { + PropGraphProperties *pr = makeNode(PropGraphProperties); + + pr->properties = $3; + pr->location = @1; + + $$ = (Node *) pr; + } + ; + +label_and_properties_list: + label_and_properties + { + $$ = list_make1($1); + } + | label_and_properties_list label_and_properties + { + $$ = lappend($1, $2); + } + ; + +label_and_properties: + element_table_label_clause + { + PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties); + PropGraphProperties *pr = makeNode(PropGraphProperties); + + pr->all = true; + pr->location = -1; + + lp->label = $1; + lp->properties = pr; + lp->location = @1; + + $$ = (Node *) lp; + } + | element_table_label_clause element_table_properties + { + PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties); + + lp->label = $1; + lp->properties = (PropGraphProperties *) $2; + lp->location = @1; + + $$ = (Node *) lp; + } + ; + +element_table_label_clause: + LABEL name + { + $$ = $2; + } + | DEFAULT LABEL + { + $$ = NULL; + } + ; + +AlterPropGraphStmt: + ALTER PROPERTY GRAPH qualified_name ADD_P vertex_tables_clause + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->add_vertex_tables = $6; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name ADD_P vertex_tables_clause ADD_P edge_tables_clause + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->add_vertex_tables = $6; + n->add_edge_tables = $8; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name ADD_P edge_tables_clause + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->add_edge_tables = $6; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name DROP vertex_synonym TABLES '(' name_list ')' opt_drop_behavior + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->drop_vertex_tables = $9; + n->drop_behavior = $11; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name DROP edge_synonym TABLES '(' name_list ')' opt_drop_behavior + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->drop_edge_tables = $9; + n->drop_behavior = $11; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name + add_label_list + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->element_kind = $6; + n->element_alias = $8; + n->add_labels = $9; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name + DROP LABEL name opt_drop_behavior + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->element_kind = $6; + n->element_alias = $8; + n->drop_label = $11; + n->drop_behavior = $12; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name + ALTER LABEL name ADD_P PROPERTIES '(' xml_attribute_list ')' + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + PropGraphProperties *pr = makeNode(PropGraphProperties); + + n->pgname = $4; + n->element_kind = $6; + n->element_alias = $8; + n->alter_label = $11; + + pr->properties = $15; + pr->location = @13; + n->add_properties = pr; + + $$ = (Node *) n; + } + | ALTER PROPERTY GRAPH qualified_name ALTER vertex_or_edge TABLE name + ALTER LABEL name DROP PROPERTIES '(' name_list ')' opt_drop_behavior + { + AlterPropGraphStmt *n = makeNode(AlterPropGraphStmt); + + n->pgname = $4; + n->element_kind = $6; + n->element_alias = $8; + n->alter_label = $11; + n->drop_properties = $15; + n->drop_behavior = $17; + + $$ = (Node *) n; + } + ; + +vertex_or_edge: + vertex_synonym { $$ = PROPGRAPH_ELEMENT_KIND_VERTEX; } + | edge_synonym { $$ = PROPGRAPH_ELEMENT_KIND_EDGE; } + ; + +add_label_list: + add_label { $$ = list_make1($1); } + | add_label_list add_label { $$ = lappend($1, $2); } + ; + +add_label: ADD_P LABEL name element_table_properties + { + PropGraphLabelAndProperties *lp = makeNode(PropGraphLabelAndProperties); + + lp->label = $3; + lp->properties = (PropGraphProperties *) $4; + lp->location = @1; + + $$ = (Node *) lp; + } + ; + + /***************************************************************************** * * CREATE TRANSFORM / DROP TRANSFORM @@ -9472,6 +9875,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); @@ -10097,6 +10510,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); @@ -10440,6 +10873,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); @@ -13534,6 +13976,17 @@ table_ref: relation_expr opt_alias_clause n->alias = $3; $$ = (Node *) n; } + | GRAPH_TABLE '(' qualified_name MATCH graph_pattern COLUMNS '(' xml_attribute_list ')' ')' opt_alias_clause + { + RangeGraphTable *n = makeNode(RangeGraphTable); + + n->graph_name = $3; + n->graph_pattern = castNode(GraphPattern, $5); + n->columns = $8; + n->alias = $11; + n->location = @1; + $$ = (Node *) n; + } | select_with_parens opt_alias_clause { RangeSubselect *n = makeNode(RangeSubselect); @@ -14878,6 +15331,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); } @@ -15357,6 +15814,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 @@ -16525,6 +16986,8 @@ MathOp: '+' { $$ = "+"; } | LESS_EQUALS { $$ = "<="; } | GREATER_EQUALS { $$ = ">="; } | NOT_EQUALS { $$ = "<>"; } + | RIGHT_ARROW { $$ = "->"; } + | '|' { $$ = "|"; } ; qual_Op: Op @@ -17115,6 +17578,213 @@ json_array_aggregate_order_by_clause_opt: | /* EMPTY */ { $$ = NIL; } ; + +/***************************************************************************** + * + * graph patterns + * + *****************************************************************************/ + +graph_pattern: + path_pattern_list where_clause + { + GraphPattern *gp = makeNode(GraphPattern); + + gp->path_pattern_list = $1; + gp->whereClause = $2; + $$ = (Node *) gp; + } + ; + +path_pattern_list: + path_pattern { $$ = list_make1($1); } + | path_pattern_list ',' path_pattern { $$ = lappend($1, $3); } + ; + +path_pattern: + path_pattern_expression { $$ = $1; } + ; + +/* + * path pattern expression + */ + +path_pattern_expression: + path_term { $$ = $1; } + /* | path_multiset_alternation */ + /* | path_pattern_union */ + ; + +path_term: + path_factor { $$ = list_make1($1); } + | path_term path_factor { $$ = lappend($1, $2); } + ; + +path_factor: + path_primary opt_graph_pattern_quantifier + { + GraphElementPattern *gep = (GraphElementPattern *) $1; + + gep->quantifier = $2; + } + ; + +path_primary: + '(' opt_colid opt_is_label_expression where_clause ')' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = VERTEX_PATTERN; + gep->variable = $2; + gep->labelexpr = $3; + gep->whereClause = $4; + gep->location = @1; + + $$ = (Node *) gep; + } + /* full edge pointing left: <-[ xxx ]- */ + | '<' '-' '[' opt_colid opt_is_label_expression where_clause ']' '-' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_LEFT; + gep->variable = $4; + gep->labelexpr = $5; + gep->whereClause = $6; + gep->location = @1; + + $$ = (Node *) gep; + } + /* full edge pointing right: -[ xxx ]-> */ + | '-' '[' opt_colid opt_is_label_expression where_clause ']' '-' '>' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_RIGHT; + gep->variable = $3; + gep->labelexpr = $4; + gep->whereClause = $5; + gep->location = @1; + + $$ = (Node *) gep; + } + | '-' '[' opt_colid opt_is_label_expression where_clause ']' RIGHT_ARROW + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_RIGHT; + gep->variable = $3; + gep->labelexpr = $4; + gep->whereClause = $5; + gep->location = @1; + + $$ = (Node *) gep; + } + /* full edge any direction: -[ xxx ]- */ + | '-' '[' opt_colid opt_is_label_expression where_clause ']' '-' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_ANY; + gep->variable = $3; + gep->labelexpr = $4; + gep->whereClause = $5; + gep->location = @1; + + $$ = (Node *) gep; + } + /* abbreviated edge patterns */ + | '<' '-' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_LEFT; + gep->location = @1; + + $$ = (Node *) gep; + } + | '-' '>' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_RIGHT; + gep->location = @1; + + $$ = (Node *) gep; + } + | RIGHT_ARROW + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_RIGHT; + gep->location = @1; + + $$ = (Node *) gep; + } + | '-' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_ANY; + gep->location = @1; + + $$ = (Node *) gep; + } + | '(' path_pattern_expression where_clause ')' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = PAREN_EXPR; + gep->subexpr = $2; + gep->whereClause = $3; + gep->location = @1; + + $$ = (Node *) gep; + } + ; + +opt_colid: + ColId { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + ; + +opt_is_label_expression: + IS label_expression { $$ = $2; } + | ':' label_expression { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + +/* + * graph pattern quantifier + */ + +opt_graph_pattern_quantifier: + '{' Iconst '}' { $$ = list_make2_int($2, $2); } + | '{' ',' Iconst '}' { $$ = list_make2_int(0, $3); } + | '{' Iconst ',' Iconst '}' { $$ = list_make2_int($2, $4); } + | /*EMPTY*/ { $$ = NULL; } + ; + +/* + * label expression + */ + +label_expression: + label_term + | label_disjunction + ; + +label_disjunction: + label_expression '|' label_term + { $$ = makeOrExpr($1, $3, @2); } + ; + +label_term: + name + { $$ = makeColumnRef($1, NIL, @1, yyscanner); } + ; + + /***************************************************************************** * * target list for SELECT @@ -17634,6 +18304,7 @@ unreserved_keyword: | DELIMITERS | DEPENDS | DEPTH + | DESTINATION | DETACH | DICTIONARY | DISABLE_P @@ -17643,6 +18314,7 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EDGE | EMPTY_P | ENABLE_P | ENCODING @@ -17672,6 +18344,7 @@ unreserved_keyword: | GENERATED | GLOBAL | GRANTED + | GRAPH | GROUPS | HANDLER | HEADER_P @@ -17736,6 +18409,7 @@ unreserved_keyword: | NFKC | NFKD | NO + | NODE | NORMALIZED | NOTHING | NOTIFY @@ -17778,6 +18452,8 @@ unreserved_keyword: | PROCEDURE | PROCEDURES | PROGRAM + | PROPERTIES + | PROPERTY | PUBLICATION | QUOTE | QUOTES @@ -17790,6 +18466,7 @@ unreserved_keyword: | REFERENCING | REFRESH | REINDEX + | RELATIONSHIP | RELATIVE_P | RELEASE | RENAME @@ -17880,6 +18557,7 @@ unreserved_keyword: | VALUE_P | VARYING | VERSION_P + | VERTEX | VIEW | VIEWS | VOLATILE @@ -17918,6 +18596,7 @@ col_name_keyword: | EXISTS | EXTRACT | FLOAT_P + | GRAPH_TABLE | GREATEST | GROUPING | INOUT @@ -18209,6 +18888,7 @@ bare_label_keyword: | DEPENDS | DEPTH | DESC + | DESTINATION | DETACH | DICTIONARY | DISABLE_P @@ -18220,6 +18900,7 @@ bare_label_keyword: | DOUBLE_P | DROP | EACH + | EDGE | ELSE | EMPTY_P | ENABLE_P @@ -18257,6 +18938,8 @@ bare_label_keyword: | GENERATED | GLOBAL | GRANTED + | GRAPH + | GRAPH_TABLE | GREATEST | GROUPING | GROUPS @@ -18352,6 +19035,7 @@ bare_label_keyword: | NFKC | NFKD | NO + | NODE | NONE | NORMALIZE | NORMALIZED @@ -18407,6 +19091,8 @@ bare_label_keyword: | PROCEDURE | PROCEDURES | PROGRAM + | PROPERTIES + | PROPERTY | PUBLICATION | QUOTE | QUOTES @@ -18421,6 +19107,7 @@ bare_label_keyword: | REFERENCING | REFRESH | REINDEX + | RELATIONSHIP | RELATIVE_P | RELEASE | RENAME @@ -18536,6 +19223,7 @@ bare_label_keyword: | VARIADIC | VERBOSE | VERSION_P + | VERTEX | VIEW | VIEWS | VOLATILE diff --git a/src/backend/parser/meson.build b/src/backend/parser/meson.build index 573d70b3d1b..cbcfa07b405 100644 --- a/src/backend/parser/meson.build +++ b/src/backend/parser/meson.build @@ -10,6 +10,7 @@ backend_sources += files( 'parse_enr.c', 'parse_expr.c', 'parse_func.c', + 'parse_graphtable.c', 'parse_jsontable.c', 'parse_merge.c', 'parse_node.c', diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index bee7d8346a3..bf3ed604496 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -577,6 +577,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) errkind = true; break; + case EXPR_KIND_PROPGRAPH_PROPERTY: + if (isAgg) + err = _("aggregate functions are not allowed in property definition expressions"); + else + err = _("grouping operations are not allowed in property definition expressions"); + + break; + /* * There is intentionally no default: case here, so that the * compiler will warn if we add a new ParseExprKind without @@ -967,6 +975,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_CYCLE_MARK: errkind = true; break; + case EXPR_KIND_PROPGRAPH_PROPERTY: + err = _("window functions are not allowed in property definition expressions"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 8118036495b..63f5dd5556f 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -35,6 +35,7 @@ #include "parser/parse_collate.h" #include "parser/parse_expr.h" #include "parser/parse_func.h" +#include "parser/parse_graphtable.h" #include "parser/parse_oper.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" @@ -67,6 +68,8 @@ static ParseNamespaceItem *transformRangeFunction(ParseState *pstate, RangeFunction *r); static ParseNamespaceItem *transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf); +static ParseNamespaceItem *transformRangeGraphTable(ParseState *pstate, + RangeGraphTable *rgt); static TableSampleClause *transformRangeTableSample(ParseState *pstate, RangeTableSample *rts); static ParseNamespaceItem *getNSItemForSpecialRelationTypes(ParseState *pstate, @@ -900,6 +903,80 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf) tf, rtf->alias, is_lateral, true); } +/* + * transformRangeGraphTable -- transform a GRAPH_TABLE clause + */ +static ParseNamespaceItem * +transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt) +{ + Relation rel; + Oid graphid; + GraphTableParseState *gpstate = palloc0_object(GraphTableParseState); + Node *gp; + List *columns = NIL; + List *colnames = NIL; + ListCell *lc; + int resno = 0; + + rel = parserOpenTable(pstate, rgt->graph_name, AccessShareLock); + if (rel->rd_rel->relkind != RELKIND_PROPGRAPH) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a property graph", + RelationGetRelationName(rel)), + parser_errposition(pstate, rgt->graph_name->location)); + + graphid = RelationGetRelid(rel); + + gpstate->graphid = graphid; + + pstate->p_post_columnref_hook = graph_table_property_reference; + pstate->p_ref_hook_state = gpstate; + Assert(!pstate->p_lateral_active); + pstate->p_lateral_active = true; + + gp = transformGraphPattern(pstate, gpstate, rgt->graph_pattern); + + foreach(lc, rgt->columns) + { + ResTarget *rt = lfirst_node(ResTarget, lc); + Node *colexpr; + TargetEntry *te; + char *colname; + + colexpr = transformExpr(pstate, rt->val, EXPR_KIND_SELECT_TARGET); + + if (rt->name) + colname = rt->name; + else + { + if (IsA(colexpr, GraphPropertyRef)) + colname = get_propgraph_property_name(castNode(GraphPropertyRef, colexpr)->propid); + else + { + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("complex graph table column must specify an explicit column name"), + parser_errposition(pstate, rt->location)); + colname = NULL; + } + } + + colnames = lappend(colnames, makeString(colname)); + + te = makeTargetEntry((Expr *) colexpr, ++resno, colname, false); + columns = lappend(columns, te); + } + + table_close(rel, NoLock); + + pstate->p_pre_columnref_hook = NULL; + pstate->p_ref_hook_state = NULL; + pstate->p_lateral_active = false; + + return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true); +} + /* * transformRangeTableSample --- transform a TABLESAMPLE clause * @@ -1123,6 +1200,18 @@ transformFromClauseItem(ParseState *pstate, Node *n, rtr->rtindex = nsitem->p_rtindex; return (Node *) rtr; } + else if (IsA(n, RangeGraphTable)) + { + RangeTblRef *rtr; + ParseNamespaceItem *nsitem; + + nsitem = transformRangeGraphTable(pstate, (RangeGraphTable *) n); + *top_nsitem = nsitem; + *namespace = list_make1(nsitem); + rtr = makeNode(RangeTblRef); + rtr->rtindex = nsitem->p_rtindex; + return (Node *) rtr; + } else if (IsA(n, RangeTableSample)) { /* TABLESAMPLE clause (wrapping some other valid FROM node) */ diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index 44529bb49e6..33a1f3f2413 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -571,6 +571,13 @@ assign_collations_walker(Node *node, assign_collations_context *context) location = exprLocation(node); break; + case T_GraphPropertyRef: + /* FIXME */ + collation = DEFAULT_COLLATION_OID; + strength = COLLATE_IMPLICIT; + location = -1; + break; + default: { /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 00cd7358ebb..9be9142a52d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -578,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; @@ -1860,6 +1861,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_GENERATED_COLUMN: err = _("cannot use subquery in column generation expression"); break; + case EXPR_KIND_PROPGRAPH_PROPERTY: + err = _("cannot use subquery in property definition expression"); + break; /* * There is intentionally no default: case here, so that the @@ -3197,6 +3201,8 @@ ParseExprKindName(ParseExprKind exprKind) return "GENERATED AS"; case EXPR_KIND_CYCLE_MARK: return "CYCLE"; + case EXPR_KIND_PROPGRAPH_PROPERTY: + return "property definition expression"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 9b23344a3b1..6409ddf1816 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2658,6 +2658,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) case EXPR_KIND_CYCLE_MARK: errkind = true; break; + case EXPR_KIND_PROPGRAPH_PROPERTY: + err = _("set-returning functions are not allowed in property definition expressions"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c new file mode 100644 index 00000000000..1a971687024 --- /dev/null +++ b/src/backend/parser/parse_graphtable.c @@ -0,0 +1,209 @@ +/*------------------------------------------------------------------------- + * + * parse_graphtable.c + * parsing of GRAPH_TABLE + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/parser/parse_graphtable.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_property.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_graphtable.h" +#include "parser/parse_node.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/relcache.h" +#include "utils/syscache.h" + + +/* + * Resolve a property reference. + */ +Node * +graph_table_property_reference(ParseState *pstate, ColumnRef *cref, Node *var) +{ + GraphTableParseState *gpstate = pstate->p_ref_hook_state; + + if (list_length(cref->fields) == 2) + { + Node *field1 = linitial(cref->fields); + Node *field2 = lsecond(cref->fields); + char *elvarname; + char *propname; + + elvarname = strVal(field1); + propname = strVal(field2); + + if (list_member(gpstate->variables, field1)) + { + GraphPropertyRef *gpr = makeNode(GraphPropertyRef); + Oid propid; + + propid = GetSysCacheOid2(PROPGRAPHPROPNAME, Anum_pg_propgraph_property_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname)); + if (!propid) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("property \"%s\" does not exist", propname)); + + gpr->location = cref->location; + gpr->elvarname = elvarname; + gpr->propid = propid; + gpr->typeId = GetSysCacheOid1(PROPGRAPHPROPOID, Anum_pg_propgraph_property_pgptypid, ObjectIdGetDatum(propid)); + + return (Node *) gpr; + } + } + + return NULL; +} + +/* + * Transform a label expression. + */ +static Node * +transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr) +{ + Node *result; + + if (labelexpr == NULL) + return NULL; + + check_stack_depth(); + + switch (nodeTag(labelexpr)) + { + case T_ColumnRef: + { + ColumnRef *cref = (ColumnRef *) labelexpr; + const char *labelname; + Oid labelid; + GraphLabelRef *lref; + + Assert(list_length(cref->fields) == 1); + labelname = strVal(linitial(cref->fields)); + + labelid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(labelname)); + if (!labelid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("label \"%s\" does not exist in property graph \"%s\"", labelname, get_rel_name(gpstate->graphid))); + + lref = makeNode(GraphLabelRef); + lref->labelid = labelid; + lref->location = cref->location; + + result = (Node *) lref; + break; + } + + case T_BoolExpr: + { + BoolExpr *be = (BoolExpr *) labelexpr; + ListCell *lc; + List *args = NIL; + + foreach(lc, be->args) + { + Node *arg = (Node *) lfirst(lc); + + arg = transformLabelExpr(gpstate, arg); + args = lappend(args, arg); + } + + result = (Node *) makeBoolExpr(be->boolop, args, be->location); + break; + } + + default: + /* should not reach here */ + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(labelexpr)); + result = NULL; /* keep compiler quiet */ + break; + } + + return result; +} + +/* + * Transform a GraphElementPattern. + */ +static Node * +transformGraphElementPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphElementPattern *gep) +{ + if (gep->variable) + gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable))); + + gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr); + + gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE); + assign_expr_collations(pstate, gep->whereClause); + + return (Node *) gep; +} + +/* + * Transform a path term (list of GraphElementPattern's). + */ +static Node * +transformPathTerm(ParseState *pstate, GraphTableParseState *gpstate, List *path_term) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, path_term) + { + Node *n = transformGraphElementPattern(pstate, gpstate, lfirst_node(GraphElementPattern, lc)); + + result = lappend(result, n); + } + + return (Node *) result; +} + +/* + * Transform a path pattern list (list of path terms). + */ +static Node * +transformPathPatternList(ParseState *pstate, GraphTableParseState *gpstate, List *path_pattern) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, path_pattern) + { + Node *n = transformPathTerm(pstate, gpstate, lfirst(lc)); + + result = lappend(result, n); + } + + return (Node *) result; +} + +/* + * Transform a GraphPattern. + */ +Node * +transformGraphPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphPattern *graph_pattern) +{ + graph_pattern->path_pattern_list = (List *) transformPathPatternList(pstate, gpstate, graph_pattern->path_pattern_list); + graph_pattern->whereClause = transformExpr(pstate, graph_pattern->whereClause, EXPR_KIND_WHERE); + assign_expr_collations(pstate, graph_pattern->whereClause); + + return (Node *) graph_pattern; +} diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 2f64eaf0e37..5ea9fa993b2 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2124,6 +2124,98 @@ addRangeTableEntryForTableFunc(ParseState *pstate, rte->colcollations); } +ParseNamespaceItem * +addRangeTableEntryForGraphTable(ParseState *pstate, + Oid graphid, + GraphPattern *graph_pattern, + List *columns, + List *colnames, + Alias *alias, + bool lateral, + bool inFromCl) +{ + RangeTblEntry *rte = makeNode(RangeTblEntry); + char *refname = alias ? alias->aliasname : pstrdup("graph_table"); + Alias *eref; + int numaliases; + int varattno; + ListCell *lc; + List *coltypes = NIL; + List *coltypmods = NIL; + List *colcollations = NIL; + RTEPermissionInfo *perminfo; + ParseNamespaceItem *nsitem; + + Assert(pstate != NULL); + + rte->rtekind = RTE_GRAPH_TABLE; + rte->relid = graphid; + rte->relkind = RELKIND_PROPGRAPH; + rte->graph_pattern = graph_pattern; + rte->graph_table_columns = columns; + rte->alias = alias; + + eref = alias ? copyObject(alias) : makeAlias(refname, NIL); + + if (!eref->colnames) + eref->colnames = colnames; + + numaliases = list_length(eref->colnames); + + /* fill in any unspecified alias columns */ + varattno = 0; + foreach(lc, colnames) + { + varattno++; + if (varattno > numaliases) + eref->colnames = lappend(eref->colnames, lfirst(lc)); + } + if (varattno < numaliases) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("GRAPH_TABLE \"%s\" has %d columns available but %d columns specified", + refname, varattno, numaliases))); + + rte->eref = eref; + + foreach(lc, columns) + { + TargetEntry *te = lfirst_node(TargetEntry, lc); + Node *colexpr = (Node *) te->expr; + + coltypes = lappend_oid(coltypes, exprType(colexpr)); + coltypmods = lappend_int(coltypmods, exprTypmod(colexpr)); + colcollations = lappend_oid(colcollations, exprCollation(colexpr)); + } + + /* + * Set flags and access permissions. + */ + rte->lateral = lateral; + rte->inFromCl = inFromCl; + + perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte); + perminfo->requiredPerms = ACL_SELECT; + + /* + * Add completed RTE to pstate's range table list, so that we know its + * index. But we don't add it to the join list --- caller must do that if + * appropriate. + */ + pstate->p_rtable = lappend(pstate->p_rtable, rte); + + /* + * Build a ParseNamespaceItem, but don't add it to the pstate's namespace + * list --- caller must do that if appropriate. + */ + nsitem = buildNSItemFromLists(rte, list_length(pstate->p_rtable), + coltypes, coltypmods, colcollations); + + nsitem->p_perminfo = perminfo; + + return nsitem; +} + /* * Add an entry for a VALUES list to the pstate's range table (p_rtable). * Then, construct and return a ParseNamespaceItem for the new RTE. @@ -2940,6 +3032,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, case RTE_VALUES: case RTE_CTE: case RTE_NAMEDTUPLESTORE: + case RTE_GRAPH_TABLE: { /* Tablefunc, Values, CTE, or ENR RTE */ ListCell *aliasp_item = list_head(rte->eref->colnames); @@ -3317,6 +3410,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) case RTE_TABLEFUNC: case RTE_VALUES: case RTE_CTE: + case RTE_GRAPH_TABLE: /* * Subselect, Table Functions, Values, CTE RTEs never have dropped diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index ee6fcd0503a..a47db5ada18 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -360,6 +360,10 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, tle->resorigtbl = rte->relid; tle->resorigcol = attnum; break; + case RTE_GRAPH_TABLE: + tle->resorigtbl = rte->relid; + tle->resorigcol = InvalidAttrNumber; + break; case RTE_SUBQUERY: /* Subselect-in-FROM: copy up from the subselect */ if (attnum != InvalidAttrNumber) @@ -1578,6 +1582,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) case RTE_RELATION: case RTE_VALUES: case RTE_NAMEDTUPLESTORE: + case RTE_GRAPH_TABLE: case RTE_RESULT: /* diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index 3248fb51080..1cd584ddb48 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -366,6 +366,8 @@ less_equals "<=" greater_equals ">=" less_greater "<>" not_equals "!=" +/* Note there is no need for left_arrow, since "<-" is not a single operator. */ +right_arrow "->" /* * "self" is the set of chars that should be returned as single-character @@ -377,7 +379,7 @@ not_equals "!=" * If you change either set, adjust the character lists appearing in the * rule for "operator"! */ -self [,()\[\].;\:\+\-\*\/\%\^\<\>\=] +self [,()\[\].;\:\|\+\-\*\/\%\^\<\>\=] op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=] operator {op_chars}+ @@ -878,6 +880,11 @@ other . return NOT_EQUALS; } +{right_arrow} { + SET_YYLLOC(); + return RIGHT_ARROW; + } + {self} { SET_YYLLOC(); return yytext[0]; @@ -955,7 +962,7 @@ other . * that the "self" rule would have. */ if (nchars == 1 && - strchr(",()[].;:+-*/%^<>=", yytext[0])) + strchr(",()[].;:|+-*/%^<>=", yytext[0])) return yytext[0]; /* * Likewise, if what we have left is two chars, and @@ -975,6 +982,8 @@ other . return NOT_EQUALS; if (yytext[0] == '!' && yytext[1] == '=') return NOT_EQUALS; + if (yytext[0] == '-' && yytext[1] == '>') + return RIGHT_ARROW; } } diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile index 4680752e6a7..09070047b7e 100644 --- a/src/backend/rewrite/Makefile +++ b/src/backend/rewrite/Makefile @@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = \ rewriteDefine.o \ + rewriteGraphTable.o \ rewriteHandler.o \ rewriteManip.o \ rewriteRemove.o \ diff --git a/src/backend/rewrite/meson.build b/src/backend/rewrite/meson.build index 23043ca6e56..2bea20233a4 100644 --- a/src/backend/rewrite/meson.build +++ b/src/backend/rewrite/meson.build @@ -2,6 +2,7 @@ backend_sources += files( 'rewriteDefine.c', + 'rewriteGraphTable.c', 'rewriteHandler.c', 'rewriteManip.c', 'rewriteRemove.c', diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c new file mode 100644 index 00000000000..ee11594d2cf --- /dev/null +++ b/src/backend/rewrite/rewriteGraphTable.c @@ -0,0 +1,1098 @@ +/*------------------------------------------------------------------------- + * + * rewriteGraphTable.c + * Support for rewriting GRAPH_TABLE clauses. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/rewrite/rewriteGraphTable.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/table.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_element_label.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_label_property.h" +#include "catalog/pg_propgraph_property.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "parser/analyze.h" +#include "parser/parse_node.h" +#include "parser/parse_relation.h" +#include "parser/parsetree.h" +#include "parser/parse_relation.h" +#include "parser/parse_graphtable.h" +#include "rewrite/rewriteGraphTable.h" +#include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + + +/* + * Represents one property graph element (vertex or edge) in the path. + * + * Label expression in an element pattern resolves into a set of elements. For + * each of those elements we create one graph_path_element object. + */ +struct graph_path_element +{ + Oid elemoid; + Oid reloid; + int rtindex; + List *labeloids; + Oid srcvertexid; + int srcelem_pos; + Oid destvertexid; + int destelem_pos; + List *qual_exprs; + GraphElementPattern *parent_gep; +}; + +static Node *replace_property_refs(Oid propgraphid, Node *node, const List *mappings); +static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum); +static List *generate_queries_for_path_pattern(RangeTblEntry *rte, List *element_patterns); +static Query *generate_query_for_graph_path(RangeTblEntry *rte, List *path); +static Node *generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist); +static List *generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_pattern_lists, int elempos); +static Query *generate_query_for_empty_path_pattern(RangeTblEntry *rte); +static Query *generate_union_from_pathqueries(List **pathqueries); +static const char *get_gep_kind_name(GraphElementPatternKind gepkind); +static List *get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos); +static bool is_property_associated_with_label(Oid labeloid, Oid propoid); +static const char *get_graph_elem_kind_name(GraphElementPatternKind gepkind); +static Node *get_element_property_expr(Oid elemoid, Oid propoid, int rtindex); + +/* + * Convert GRAPH_TABLE clause into a subquery using relational + * operators. + */ +Query * +rewriteGraphTable(Query *parsetree, int rt_index) +{ + RangeTblEntry *rte; + Query *graph_table_query; + List *path_pattern; + List *pathqueries = NIL; + + rte = rt_fetch(rt_index, parsetree->rtable); + + if (list_length(rte->graph_pattern->path_pattern_list) != 1) + elog(ERROR, "unsupported path pattern list length"); + + path_pattern = linitial(rte->graph_pattern->path_pattern_list); + pathqueries = generate_queries_for_path_pattern(rte, path_pattern); + graph_table_query = generate_union_from_pathqueries(&pathqueries); + + AcquireRewriteLocks(graph_table_query, true, false); + + rte->rtekind = RTE_SUBQUERY; + rte->subquery = graph_table_query; + rte->lateral = true; + + /* + * Reset no longer applicable fields, to appease + * WRITE_READ_PARSE_PLAN_TREES. + */ + rte->graph_pattern = NULL; + rte->graph_table_columns = NIL; + +#if 0 + elog(INFO, "rewritten:\n%s", pg_get_querydef(copyObject(parsetree), false)); +#endif + + return parsetree; +} + +/* + * Generate queries represeting the given path pattern applied to the given + * property graph. + * + * A path pattern consists of one or more element patterns. Each of the element + * patterns may be satisfied by multiple elements. A path satisfying the given + * path pattern consists of one element from each element pattern. Each of these + * paths is converted into a query connecting all the elements in that path. + * There can be as many paths as the number of combinations of the elements. + * Compute all these paths and convert each of them into a query. Set of these + * queries is returned. + * + * Assuming that the numbering starts at 0, every element pattern at an even + * numbered position in the path is a vertex pattern. Every element in even + * numbered position is an edge pattern. Thus every even numbered element is a + * vertex table and odd numbered element is an edge table. An edge connects two + * vertices identified by the source and destination keys respectively. The + * connections between vertex rows from different vertex tables can be computed + * by applying relational join between edge table and the adjacent vertex tables + * respectively. Hence a query representing a path consists of JOIN between + * adjacent vertex and edge tables. + * + * A path pattern in itself is a K-partite graph where K = number of element + * patterns in the path pattern. The possible paths are computed by performing a + * DFS in this graph. The DFS is implemented as recursion. A path is converted + * into the corresponding query as soon as the last vertex table is reached. + * + * generate_queries_for_path_pattern() starts the recursion but actual work is + * done by generate_queries_for_path_pattern_recurse(). + * generate_query_for_graph_path() constructs a query for a given path. + * + * A path pattern may result into no path if any of the element pattern yields + * no elements or edge patterns yield no edges connecting adjacent vertex + * patterns. In such a case a query which returns no result is returned + * (generate_query_for_empty_path_pattern()). + * + * 'path_pattern' is given path pattern + * 'rte' references the property graph in the GRAPH_TABLE clause + */ +static List * +generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern) +{ + List *pathqueries = NIL; + List *path_elem_lists = NIL; + ListCell *lc; + int elempos = 0; + + Assert(list_length(path_pattern) > 0); + + /* + * For every element pattern in the given path pattern collect all the + * graph elements that satisfy the element pattern. + */ + foreach(lc, path_pattern) + { + GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc); + + if (gep->kind != VERTEX_PATTERN && + gep->kind != EDGE_PATTERN_LEFT && gep->kind != EDGE_PATTERN_RIGHT) + elog(ERROR, "unsupported element pattern kind: %s", get_gep_kind_name(gep->kind)); + + if (gep->quantifier) + elog(ERROR, "element pattern quantifier not supported yet"); + + path_elem_lists = lappend(path_elem_lists, + get_elements_for_gep(rte->relid, gep, elempos++)); + } + + pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries, + NIL, path_elem_lists, 0); + + if (!pathqueries) + pathqueries = list_make1(generate_query_for_empty_path_pattern(rte)); + + return pathqueries; +} + +/* + * Recursive workhorse function of generate_queries_for_path_pattern(). + */ +static List * +generate_queries_for_path_pattern_recurse(RangeTblEntry *rte, List *pathqueries, List *cur_path, List *path_elem_lists, int elempos) +{ + List *gep_elems = list_nth_node(List, path_elem_lists, elempos); + ListCell *lc; + + foreach(lc, gep_elems) + { + struct graph_path_element *elem = lfirst(lc); + + /* Update current path being built with current element. */ + cur_path = lappend(cur_path, elem); + + /* + * If this is the last element in the path, generate query for the + * completed path. Else recurse processing the next element. + */ + if (list_length(path_elem_lists) == list_length(cur_path)) + { + Query *pathquery = generate_query_for_graph_path(rte, cur_path); + + Assert(elempos == list_length(path_elem_lists) - 1); + if (pathquery) + pathqueries = lappend(pathqueries, pathquery); + } + else + pathqueries = generate_queries_for_path_pattern_recurse(rte, pathqueries, + cur_path, + path_elem_lists, + elempos + 1); + /* Make way for the next element at the same position. */ + cur_path = list_delete_last(cur_path); + } + + return pathqueries; +} + +/* + * Construct a query representing given graph path. + * + * More details in the prologue of generate_queries_for_path_pattern(). + */ +static Query * +generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path) +{ + ListCell *lc; + Query *path_query = makeNode(Query); + List *fromlist = NIL; + List *qual_exprs = NIL; + + path_query->commandType = CMD_SELECT; + + foreach(lc, graph_path) + { + struct graph_path_element *gpe = (struct graph_path_element *) lfirst(lc); + GraphElementPattern *gep = gpe->parent_gep; + RangeTblRef *rtr; + Relation rel; + ParseNamespaceItem *pni; + + Assert(gep->kind == VERTEX_PATTERN || + gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT); + Assert(!gep->quantifier); + + if (gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT) + { + struct graph_path_element *src_gpe = list_nth(graph_path, gpe->srcelem_pos); + struct graph_path_element *dest_gpe = list_nth(graph_path, gpe->destelem_pos); + + /* + * Make sure that the source and destination elements of this edge + * are placed at the expected position and have the corret + * RangeTblEntry indexes as setup by create_gpe_for_element(). + */ + Assert(gpe->srcelem_pos == src_gpe->rtindex - 1 && + gpe->destelem_pos == dest_gpe->rtindex - 1); + + /* + * If the given edge element does not connect the adjacent vertex + * elements in this path, the path is broken. Abandon this path as + * it won't return any rows. + */ + if (src_gpe->elemoid != gpe->srcvertexid || + dest_gpe->elemoid != gpe->destvertexid) + return NULL; + } + + /* Create RangeTblEntry for this element table. */ + rel = table_open(gpe->reloid, AccessShareLock); + pni = addRangeTableEntryForRelation(make_parsestate(NULL), rel, AccessShareLock, + NULL, true, false); + table_close(rel, NoLock); + path_query->rtable = lappend(path_query->rtable, pni->p_rte); + path_query->rteperminfos = lappend(path_query->rteperminfos, pni->p_perminfo); + pni->p_rte->perminfoindex = list_length(path_query->rteperminfos); + rtr = makeNode(RangeTblRef); + rtr->rtindex = list_length(path_query->rtable); + fromlist = lappend(fromlist, rtr); + + /* + * Make sure that the assumption mentioned in create_gpe_for_element() + * holds true. That the elements' RangeTblEntrys are added in the + * order in which they appear in the path. + */ + Assert(gpe->rtindex == rtr->rtindex); + + if (gep->whereClause) + { + Node *tr; + + tr = replace_property_refs(rte->relid, gep->whereClause, list_make1(gpe)); + + qual_exprs = lappend(qual_exprs, tr); + } + qual_exprs = list_concat(qual_exprs, gpe->qual_exprs); + } + + path_query->jointree = makeFromExpr(fromlist, + (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1)); + + /* Each path query projects the columns specified in the GRAH_TABLE clause */ + path_query->targetList = castNode(List, + replace_property_refs(rte->relid, + (Node *) rte->graph_table_columns, + graph_path)); + return path_query; +} + +/* + * Construct a query which would not return any rows. + * + * More details in the prologue of generate_queries_for_path_pattern(). + */ +static Query * +generate_query_for_empty_path_pattern(RangeTblEntry *rte) +{ + Query *query = makeNode(Query); + ListCell *lc; + + query->commandType = CMD_SELECT; + + + query->rtable = NIL; + query->rteperminfos = NIL; + + + query->jointree = makeFromExpr(NIL, (Node *) makeBoolConst(false, false)); + + /* + * Even though no rows are returned, the result still projects the same + * columns as projected by GRAPH_TABLE clause. Do this by constructing a + * target list full of NULL values. + */ + foreach(lc, rte->graph_table_columns) + { + TargetEntry *te = copyObject(lfirst_node(TargetEntry, lc)); + Node *nte = (Node *) te->expr; + + te->expr = (Expr *) makeNullConst(exprType(nte), exprTypmod(nte), exprCollation(nte)); + query->targetList = lappend(query->targetList, te); + } + + return query; +} + +/* + * Construct a query which is UNION of given path queries. + * + * The function destroys given pathqueries list while constructing + * SetOperationStmt recrursively. Hence the function always returns with + * `pathqueries` set to NIL. + */ +static Query * +generate_union_from_pathqueries(List **pathqueries) +{ + List *rtable = NIL; + Query *sampleQuery = linitial_node(Query, *pathqueries); + SetOperationStmt *sostmt; + Query *union_query; + int resno; + ListCell *lctl, + *lct, + *lcm, + *lcc; + + Assert(list_length(*pathqueries) > 0); + + /* If there's only one pathquery, no need to construct a UNION query. */ + if (list_length(*pathqueries) == 1) + { + *pathqueries = NIL; + return sampleQuery; + } + + sostmt = castNode(SetOperationStmt, + generate_setop_from_pathqueries(*pathqueries, &rtable, NULL)); + + /* Encapsulate the set operation statement into a Query. */ + union_query = makeNode(Query); + union_query->commandType = CMD_SELECT; + union_query->rtable = rtable; + union_query->setOperations = (Node *) sostmt; + union_query->rteperminfos = NIL; + union_query->jointree = makeFromExpr(NIL, NULL); + + /* + * Generate dummy targetlist for outer query using column names from one + * of the queries and common datatypes/collations of topmost set + * operation. It shouldn't matter which query. Also it shouldn't matter + * which RT index is used as varno in the target list entries, as long as + * it corresponds to a real RT entry; else funny things may happen when + * the tree is mashed by rule rewriting. So we use 1 since there's always + * one RT entry at least. + */ + Assert(rt_fetch(1, rtable)); + union_query->targetList = NULL; + resno = 1; + forfour(lct, sostmt->colTypes, + lcm, sostmt->colTypmods, + lcc, sostmt->colCollations, + lctl, sampleQuery->targetList) + { + Oid colType = lfirst_oid(lct); + int32 colTypmod = lfirst_int(lcm); + Oid colCollation = lfirst_oid(lcc); + TargetEntry *sample_tle = (TargetEntry *) lfirst(lctl); + char *colName; + TargetEntry *tle; + Var *var; + + Assert(!sample_tle->resjunk); + colName = pstrdup(sample_tle->resname); + var = makeVar(1, sample_tle->resno, colType, colTypmod, colCollation, 0); + var->location = exprLocation((Node *) sample_tle->expr); + tle = makeTargetEntry((Expr *) var, (AttrNumber) resno++, colName, false); + union_query->targetList = lappend(union_query->targetList, tle); + } + + *pathqueries = NIL; + return union_query; +} + +/* + * Construct a query which is UNION of all the given path queries. + * + * The function destroys given pathqueries list while constructing + * SetOperationStmt recursively. + */ +static Node * +generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetlist) +{ + SetOperationStmt *sostmt; + Query *lquery; + Node *rarg; + RangeTblRef *lrtr = makeNode(RangeTblRef); + List *rtargetlist; + ParseNamespaceItem *pni; + + /* Recursion termination condition. */ + if (list_length(pathqueries) == 0) + { + *targetlist = NIL; + return NULL; + } + + lquery = linitial_node(Query, pathqueries); + + pni = addRangeTableEntryForSubquery(make_parsestate(NULL), lquery, NULL, + false, false); + *rtable = lappend(*rtable, pni->p_rte); + lrtr->rtindex = list_length(*rtable); + rarg = generate_setop_from_pathqueries(list_delete_first(pathqueries), rtable, &rtargetlist); + if (rarg == NULL) + { + /* + * No further path queries in the list. Convert the last query into an + * RangeTblRef as expected by SetOperationStmt. Extract a list of the + * non-junk TLEs for upper-level processing. + */ + if (targetlist) + { + ListCell *tl; + + *targetlist = NIL; + foreach(tl, lquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(tl); + + if (!tle->resjunk) + *targetlist = lappend(*targetlist, tle); + } + } + return (Node *) lrtr; + } + + sostmt = makeNode(SetOperationStmt); + sostmt->op = SETOP_UNION; + sostmt->all = true; + sostmt->larg = (Node *) lrtr; + sostmt->rarg = rarg; + constructSetOpTargetlist(sostmt, lquery->targetList, rtargetlist, targetlist, "UNION", NULL, false); + + return (Node *) sostmt; +} + +/* + * Construct a graph_path_element object for the graph element given by `elemoid` + * statisfied by the graph element pattern `gep`. + * + * 'elempos` is the position of given element pattern in the path pattern. + * + * If the type of graph element does not fit the element pattern kind, the + * function returns NULL. + */ +static struct graph_path_element * +create_gpe_for_element(GraphElementPattern *gep, Oid elemoid, int elempos) +{ + HeapTuple eletup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elemoid)); + Form_pg_propgraph_element pgeform; + struct graph_path_element *gpe; + + if (!eletup) + elog(ERROR, "cache lookup failed for property graph element %u", elemoid); + pgeform = ((Form_pg_propgraph_element) GETSTRUCT(eletup)); + + if ((pgeform->pgekind == PGEKIND_VERTEX && gep->kind != VERTEX_PATTERN) || + (pgeform->pgekind == PGEKIND_EDGE && !IS_EDGE_PATTERN(gep->kind))) + { + ReleaseSysCache(eletup); + return NULL; + } + + gpe = palloc0_object(struct graph_path_element); + gpe->parent_gep = gep; + gpe->elemoid = elemoid; + gpe->reloid = pgeform->pgerelid; + gpe->qual_exprs = NIL; + + /* + * When the path containing this element will be converted into a query + * (generate_query_for_graph_path()) this element will be converted into a + * RangeTblEntry. The RangeTblEntrys are created in the same order in + * which elements appear in the path and thus get consecutive rtindexes. + * Knowing those rtindexes here makes it possible to craft elements' qual + * expressions only once. Otherwise they need to be crafted as many times + * as the number of paths this element appears in. Hence save the assumed + * rtindex so that it can be verified later. + */ + gpe->rtindex = elempos + 1; + + if (IS_EDGE_PATTERN(gep->kind)) + { + int src_rtindex; + int dest_rtindex; + List *edge_qual; + + gpe->srcvertexid = pgeform->pgesrcvertexid; + gpe->destvertexid = pgeform->pgedestvertexid; + + if (gep->kind == EDGE_PATTERN_RIGHT) + { + gpe->srcelem_pos = elempos - 1; + gpe->destelem_pos = elempos + 1; + src_rtindex = gpe->rtindex - 1; + dest_rtindex = gpe->rtindex + 1; + } + else if (gep->kind == EDGE_PATTERN_LEFT) + { + gpe->srcelem_pos = elempos + 1; + gpe->destelem_pos = elempos - 1; + src_rtindex = gpe->rtindex + 1; + dest_rtindex = gpe->rtindex - 1; + } + else + { + /* We don't support undirected edges yet. */ + Assert(false); + gpe->srcelem_pos = elempos; + gpe->destelem_pos = elempos; + src_rtindex = gpe->rtindex; + dest_rtindex = gpe->rtindex; + } + + edge_qual = build_edge_vertex_link_quals(eletup, gpe->rtindex, src_rtindex, + Anum_pg_propgraph_element_pgesrckey, + Anum_pg_propgraph_element_pgesrcref); + gpe->qual_exprs = list_concat(gpe->qual_exprs, edge_qual); + edge_qual = build_edge_vertex_link_quals(eletup, gpe->rtindex, dest_rtindex, + Anum_pg_propgraph_element_pgedestkey, + Anum_pg_propgraph_element_pgedestref); + gpe->qual_exprs = list_concat(gpe->qual_exprs, edge_qual); + } + + ReleaseSysCache(eletup); + + return gpe; +} + +static const char * +get_gep_kind_name(GraphElementPatternKind gepkind) +{ + switch (gepkind) + { + case VERTEX_PATTERN: + return "vertex"; + case EDGE_PATTERN_LEFT: + return "edge pointing left"; + case EDGE_PATTERN_RIGHT: + return "edge pointing right"; + case EDGE_PATTERN_ANY: + return "undirected edge"; + case PAREN_EXPR: + return "nested path pattern"; + } + + pg_unreachable(); +} + +/* + * Returns the list of OIDs of graph labels which the given label expression + * resolves to in the given property graph. + */ +static List * +get_labels_for_expr(Oid propgraphid, Node *labelexpr) +{ + List *label_oids; + + if (!labelexpr) + { + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tup; + + /* + * According to section 9.2 "Contextual inference of a set of labels" + * subclause 2.a.ii of SQL/PGQ standard, element pattern which does + * not have a label expression is considered to have label expression + * equivalent to '%|!%' which is set of all labels. + */ + label_oids = NIL; + rel = table_open(PropgraphLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_pglpgid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(propgraphid)); + scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, + true, NULL, 1, key); + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup); + + label_oids = lappend_oid(label_oids, label->oid); + } + systable_endscan(scan); + table_close(rel, AccessShareLock); + } + else if (IsA(labelexpr, GraphLabelRef)) + { + GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr); + + label_oids = list_make1_oid(glr->labelid); + } + else if (IsA(labelexpr, BoolExpr)) + { + BoolExpr *be = castNode(BoolExpr, labelexpr); + List *label_exprs = be->args; + ListCell *llc; + + label_oids = NIL; + foreach(llc, label_exprs) + { + GraphLabelRef *glr = lfirst_node(GraphLabelRef, llc); + + label_oids = lappend_oid(label_oids, glr->labelid); + } + } + else + elog(ERROR, "unsupported label expression type: %d", (int) nodeTag(labelexpr)); + + return label_oids; +} + +/* + * Given a graph element pattern `gep`, return a list of all the graph elements + * that satisfy the graph pattern. + * + * First we find all the graph labels that satisfy the label expression in + * graph element pattern. Each label has associated with one or more graph + * elements. A union of all such elements satisfies the element pattern. The + * returned list contains one graph_path_element object representing each of + * these elements respectively. + * + * `elempos` is position of the element pattern in the path pattern. + */ +static List * +get_elements_for_gep(Oid propgraphid, GraphElementPattern *gep, int elempos) +{ + List *label_oids = get_labels_for_expr(propgraphid, gep->labelexpr); + List *elem_oids_seen = NIL; + List *elem_gpe_oids = NIL; + List *elem_gpes = NIL; + ListCell *lc; + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tup; + + rel = table_open(PropgraphElementLabelRelationId, AccessShareLock); + foreach(lc, label_oids) + { + Oid labeloid = lfirst_oid(lc); + bool found = false; + + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_pgellabelid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(labeloid)); + scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, true, + NULL, 1, key); + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_propgraph_element_label label_elem = (Form_pg_propgraph_element_label) GETSTRUCT(tup); + Oid elem_oid = label_elem->pgelelid; + + if (!list_member_oid(elem_oids_seen, elem_oid)) + { + /* + * Found a new element that is associated with labels in the + * given element pattern. If it fits the element pattern kind + * we will create GraphPathPattern object for it and flag that + * the current label has at least one element, that satisfies + * the given element pattern, associated with it. + */ + struct graph_path_element *gpe = create_gpe_for_element(gep, elem_oid, elempos); + + if (gpe) + { + elem_gpes = lappend(elem_gpes, gpe); + elem_gpe_oids = lappend_oid(elem_gpe_oids, elem_oid); + found = true; + } + + /* + * Add the graph element to the elements considered so far to + * avoid processing same element associated with multiple + * labels multiple times. Also avoids creating duplicate + * GraphPathElements. + */ + elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid); + } + else if (list_member_oid(elem_gpe_oids, elem_oid)) + { + /* + * The graph element is known to qualify the given element + * pattern. Flag that the current label has at least one + * element, that satisfies the given element pattern, + * associated with it. + */ + found = true; + } + } + + if (!found) + { + /* + * We did not find any element, that fits given element pattern + * kind, associated with this label. The label or its properties + * can not be associated with the given element pattern. Throw an + * error if the label was explicitly specified in the element + * pattern. Otherwise just Remove it from the list. + */ + if (!gep->labelexpr) + foreach_delete_current(label_oids, lc); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("can not find label \"%s\" in property graph \"%s\" for element type \"%s\"", + get_propgraph_label_name(labeloid), + get_rel_name(propgraphid), + get_graph_elem_kind_name(gep->kind)))); + } + + systable_endscan(scan); + } + table_close(rel, AccessShareLock); + + /* Update the filtered label list in each graph_path_element. */ + foreach(lc, elem_gpes) + { + struct graph_path_element *gpe = lfirst(lc); + + gpe->labeloids = label_oids; + } + + return elem_gpes; +} + +static const char * +get_graph_elem_kind_name(GraphElementPatternKind gepkind) +{ + if (gepkind == VERTEX_PATTERN) + return "vertex"; + else if (IS_EDGE_PATTERN(gepkind)) + return "edge"; + else if (gepkind == PAREN_EXPR) + return "nested path pattern"; + + return "unknown"; +} + +/* + * Mutating property references into table variables + */ + +struct replace_property_refs_context +{ + Oid propgraphid; + const List *mappings; +}; + +static Node * +replace_property_refs_mutator(Node *node, struct replace_property_refs_context *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + Var *newvar = copyObject(var); + + /* + * If it's already a Var, then it was a lateral reference. Since we + * are in a subquery after the rewrite, we have to increase the level + * by one. + */ + newvar->varlevelsup++; + + return (Node *) newvar; + } + else if (IsA(node, GraphPropertyRef)) + { + GraphPropertyRef *gpr = (GraphPropertyRef *) node; + Node *n = NULL; + ListCell *lc; + struct graph_path_element *found_mapping = NULL; + List *unrelated_labels = NIL; + + foreach(lc, context->mappings) + { + struct graph_path_element *m = lfirst(lc); + + if (m->parent_gep->variable && strcmp(gpr->elvarname, m->parent_gep->variable) == 0) + { + found_mapping = m; + break; + } + } + if (!found_mapping) + elog(ERROR, "undefined element variable \"%s\"", gpr->elvarname); + + /* + * Find property definition for given element through any of the + * associated labels. + */ + foreach(lc, found_mapping->labeloids) + { + Oid labeloid = lfirst_oid(lc); + Oid elem_labelid = GetSysCacheOid2(PROPGRAPHELEMENTLABELELEMENTLABEL, + Anum_pg_propgraph_element_label_oid, + ObjectIdGetDatum(found_mapping->elemoid), + ObjectIdGetDatum(labeloid)); + + if (OidIsValid(elem_labelid)) + { + HeapTuple tup = SearchSysCache2(PROPGRAPHLABELPROP, ObjectIdGetDatum(elem_labelid), + ObjectIdGetDatum(gpr->propid)); + + if (!tup) + { + /* + * The label is associated with the given element but it + * is not associated with the required property. Check + * next label. + */ + continue; + } + + n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP, + tup, Anum_pg_propgraph_label_property_plpexpr))); + ChangeVarNodes(n, 1, found_mapping->rtindex, 0); + + ReleaseSysCache(tup); + } + else + { + /* + * Label is not associated with the element but it may be + * associated with the property through some other element. + * Save it for later use. + */ + unrelated_labels = lappend_oid(unrelated_labels, labeloid); + } + } + + /* See if we can resolve the property in some other way. */ + if (!n) + { + ListCell *lcu; + bool prop_associated = false; + + foreach(lcu, unrelated_labels) + { + if (is_property_associated_with_label(lfirst_oid(lcu), gpr->propid)) + { + prop_associated = true; + break; + } + } + + if (prop_associated) + { + /* + * The property is associated with at least one of the labels + * that satisfy given element pattern. If it's associated with + * the given element through some any of the labels, use + * correspondig value expression. Otherwise NULL. Ref. SQL/PGQ + * standard section 6.5 Property Referece, General Rule 2.b. + * + * NOTE: An element path pattern may resolve to multiple + * elements. The above section does not seem to describe this + * case. But it depends upon how the term ER is interpreted. + * For a given path there's only one element bound to a given + * ER. Hence the above stated rule can be applied here. The + * section also states the case when no element binds to ER. + * We consider such paths as broken and do not contribute any + * rows to the GRAPH_TABLE. + */ + n = get_element_property_expr(found_mapping->elemoid, gpr->propid, + found_mapping->rtindex); + + if (!n) + { + /* XXX: Does collation of NULL value matter? */ + n = (Node *) makeNullConst(gpr->typeId, -1, InvalidOid); + } + } + + } + + if (!n) + elog(ERROR, "property \"%s\" of element variable \"%s\" not found", + get_propgraph_property_name(gpr->propid), found_mapping->parent_gep->variable); + + return n; + } + + return expression_tree_mutator(node, replace_property_refs_mutator, context); +} + +static Node * +replace_property_refs(Oid propgraphid, Node *node, const List *mappings) +{ + struct replace_property_refs_context context; + + context.mappings = mappings; + context.propgraphid = propgraphid; + + return expression_tree_mutator(node, replace_property_refs_mutator, &context); +} + +/* + * Build join qualification expressions between edge and vertex tables. + */ +static List * +build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum) +{ + List *quals = NIL; + Form_pg_propgraph_element pgeform; + Datum datum; + Datum *d1, + *d2; + int n1, + n2; + + pgeform = (Form_pg_propgraph_element) GETSTRUCT(edgetup); + + datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_key_attnum); + deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d1, NULL, &n1); + + datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_ref_attnum); + deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d2, NULL, &n2); + + if (n1 != n2) + elog(ERROR, "array size key (%d) vs ref (%d) mismatch for element ID %u", catalog_key_attnum, catalog_ref_attnum, pgeform->oid); + + for (int i = 0; i < n1; i++) + { + AttrNumber keyattn = DatumGetInt16(d1[i]); + AttrNumber refattn = DatumGetInt16(d2[i]); + Oid atttypid; + TypeCacheEntry *typentry; + OpExpr *op; + + /* + * TODO: Assumes types the same on both sides; no collations yet. Some + * of this could probably be shared with foreign key triggers. + */ + atttypid = get_atttype(pgeform->pgerelid, keyattn); + typentry = lookup_type_cache(atttypid, TYPECACHE_EQ_OPR); + + op = makeNode(OpExpr); + op->location = -1; + op->opno = typentry->eq_opr; + op->opresulttype = BOOLOID; + op->args = list_make2(makeVar(edgerti, keyattn, atttypid, -1, 0, 0), + makeVar(refrti, refattn, atttypid, -1, 0, 0)); + quals = lappend(quals, op); + } + + return quals; +} + +/* + * Check if the given property is associated with the given label. + * + * A label projects the same set of properties through every element it is + * associated with. Find any of the elements and return true if that element is + * associated with the given property. False otherwise. + */ +static bool +is_property_associated_with_label(Oid labeloid, Oid propoid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tup; + bool associated = false; + + rel = table_open(PropgraphElementLabelRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_pgellabelid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(labeloid)); + scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, + true, NULL, 1, key); + + if (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup); + + associated = SearchSysCacheExists2(PROPGRAPHLABELPROP, + ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid)); + } + systable_endscan(scan); + table_close(rel, RowShareLock); + + return associated; +} + +/* + * If given element has the given property associated with it, through any of + * the associated labels, return value expression of the property. Otherwise + * NULL. + */ +static Node * +get_element_property_expr(Oid elemoid, Oid propoid, int rtindex) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple labeltup; + Node *n = NULL; + + rel = table_open(PropgraphElementLabelRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_pgelelid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(elemoid)); + scan = systable_beginscan(rel, PropgraphElementLabelElementLabelIndexId, + true, NULL, 1, key); + + while (HeapTupleIsValid(labeltup = systable_getnext(scan))) + { + Form_pg_propgraph_element_label ele_label = (Form_pg_propgraph_element_label) GETSTRUCT(labeltup); + + HeapTuple proptup = SearchSysCache2(PROPGRAPHLABELPROP, + ObjectIdGetDatum(ele_label->oid), ObjectIdGetDatum(propoid)); + + if (!proptup) + continue; + n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHLABELPROP, + proptup, Anum_pg_propgraph_label_property_plpexpr))); + ChangeVarNodes(n, 1, rtindex, 0); + + ReleaseSysCache(proptup); + break; + } + systable_endscan(scan); + table_close(rel, RowShareLock); + + return n; +} diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 8a29fbbc465..1b437615c14 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -36,6 +36,7 @@ #include "parser/parse_relation.h" #include "parser/parsetree.h" #include "rewrite/rewriteDefine.h" +#include "rewrite/rewriteGraphTable.h" #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" #include "rewrite/rewriteSearchCycle.h" @@ -2015,6 +2016,16 @@ fireRIRrules(Query *parsetree, List *activeRIRs) rte = rt_fetch(rt_index, parsetree->rtable); + /* + * Convert GRAPH_TABLE clause into a subquery using relational + * operators. (This will change the rtekind to subquery, so it must + * be done before the subquery handling below.) + */ + if (rte->rtekind == RTE_GRAPH_TABLE) + { + parsetree = rewriteGraphTable(parsetree, rt_index); + } + /* * A subquery RTE can't have associated rules, so there's nothing to * do to this level of the query, but we must recurse into the diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index fa66b8017ed..e2120753ef9 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -44,6 +44,7 @@ #include "commands/portalcmds.h" #include "commands/prepare.h" #include "commands/proclang.h" +#include "commands/propgraphcmds.h" #include "commands/publicationcmds.h" #include "commands/schemacmds.h" #include "commands/seclabel.h" @@ -148,6 +149,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) case T_AlterOperatorStmt: case T_AlterOwnerStmt: case T_AlterPolicyStmt: + case T_AlterPropGraphStmt: case T_AlterPublicationStmt: case T_AlterRoleSetStmt: case T_AlterRoleStmt: @@ -178,6 +180,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) case T_CreateOpFamilyStmt: case T_CreatePLangStmt: case T_CreatePolicyStmt: + case T_CreatePropGraphStmt: case T_CreatePublicationStmt: case T_CreateRangeStmt: case T_CreateRoleStmt: @@ -1736,6 +1739,14 @@ ProcessUtilitySlow(ParseState *pstate, commandCollected = true; break; + case T_CreatePropGraphStmt: + address = CreatePropGraph(pstate, (CreatePropGraphStmt *) parsetree); + break; + + case T_AlterPropGraphStmt: + address = AlterPropGraph(pstate, (AlterPropGraphStmt *) parsetree); + break; + case T_CreateTransformStmt: address = CreateTransform((CreateTransformStmt *) parsetree); break; @@ -2004,6 +2015,7 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel) case OBJECT_VIEW: case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: + case OBJECT_PROPGRAPH: RemoveRelations(stmt); break; default: @@ -2280,6 +2292,9 @@ AlterObjectTypeCommandTag(ObjectType objtype) case OBJECT_PROCEDURE: tag = CMDTAG_ALTER_PROCEDURE; break; + case OBJECT_PROPGRAPH: + tag = CMDTAG_ALTER_PROPERTY_GRAPH; + break; case OBJECT_ROLE: tag = CMDTAG_ALTER_ROLE; break; @@ -2556,6 +2571,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_INDEX: tag = CMDTAG_DROP_INDEX; break; + case OBJECT_PROPGRAPH: + tag = CMDTAG_DROP_PROPERTY_GRAPH; + break; case OBJECT_TYPE: tag = CMDTAG_DROP_TYPE; break; @@ -2937,6 +2955,14 @@ CreateCommandTag(Node *parsetree) } break; + case T_CreatePropGraphStmt: + tag = CMDTAG_CREATE_PROPERTY_GRAPH; + break; + + case T_AlterPropGraphStmt: + tag = CMDTAG_ALTER_PROPERTY_GRAPH; + break; + case T_CreateTransformStmt: tag = CMDTAG_CREATE_TRANSFORM; break; @@ -3634,6 +3660,14 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreatePropGraphStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterPropGraphStmt: + lev = LOGSTMT_DDL; + break; + case T_CreateTransformStmt: lev = LOGSTMT_DDL; break; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 653685bffc5..01e07e0a58a 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -34,6 +34,11 @@ #include "catalog/pg_operator.h" #include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_element_label.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_label_property.h" +#include "catalog/pg_propgraph_property.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -345,6 +350,9 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno, bool attrsOnly, bool keysOnly, bool showTblSpc, bool inherits, int prettyFlags, bool missing_ok); +static void make_propgraphdef_elements(StringInfo buf, Oid pgrelid, char pgekind); +static void make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid); +static void make_propgraphdef_properties(StringInfo buf, Oid ellabelid, Oid elrelid); static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok); static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags, @@ -1584,6 +1592,331 @@ pg_get_querydef(Query *query, bool pretty) return buf.data; } +/* + * pg_get_propgraphdef - get the definition of a property graph + */ +Datum +pg_get_propgraphdef(PG_FUNCTION_ARGS) +{ + Oid pgrelid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple classtup; + Form_pg_class classform; + char *name; + char *nsp; + + initStringInfo(&buf); + + classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(pgrelid)); + if (!HeapTupleIsValid(classtup)) + PG_RETURN_NULL(); + + classform = (Form_pg_class) GETSTRUCT(classtup); + name = NameStr(classform->relname); + + if (classform->relkind != RELKIND_PROPGRAPH) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a property graph", name))); + + nsp = get_namespace_name(classform->relnamespace); + + appendStringInfo(&buf, "CREATE PROPERTY GRAPH %s", + quote_qualified_identifier(nsp, name)); + + ReleaseSysCache(classtup); + + make_propgraphdef_elements(&buf, pgrelid, PGEKIND_VERTEX); + make_propgraphdef_elements(&buf, pgrelid, PGEKIND_EDGE); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + +/* + * Generates a VERTEX TABLES (...) or EDGE TABLES (...) clause. Pass in the + * property graph relation OID and the element kind (vertex or edge). Result + * is appended to buf. + */ +static void +make_propgraphdef_elements(StringInfo buf, Oid pgrelid, char pgekind) +{ + Relation pgerel; + ScanKeyData scankey[1]; + SysScanDesc scan; + bool first; + HeapTuple tup; + + pgerel = table_open(PropgraphElementRelationId, AccessShareLock); + + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_element_pgepgid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(pgrelid)); + + scan = systable_beginscan(pgerel, PropgraphElementAliasIndexId, true, NULL, 1, scankey); + + first = true; + while ((tup = systable_getnext(scan))) + { + Form_pg_propgraph_element pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup); + char *relname; + Datum datum; + bool isnull; + + if (pgeform->pgekind != pgekind) + continue; + + if (first) + { + appendStringInfo(buf, "\n %s TABLES (\n", pgekind == PGEKIND_VERTEX ? "VERTEX" : "EDGE"); + first = false; + } + else + appendStringInfo(buf, ",\n"); + + relname = get_rel_name(pgeform->pgerelid); + if (relname && strcmp(relname, NameStr(pgeform->pgealias)) == 0) + appendStringInfo(buf, " %s", + generate_relation_name(pgeform->pgerelid, NIL)); + else + appendStringInfo(buf, " %s AS %s", + generate_relation_name(pgeform->pgerelid, NIL), + NameStr(pgeform->pgealias)); + + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgekey, RelationGetDescr(pgerel), &isnull); + if (!isnull) + { + appendStringInfoString(buf, " KEY ("); + decompile_column_index_array(datum, pgeform->pgerelid, buf); + appendStringInfoString(buf, ")"); + } + else + elog(ERROR, "null pgekey for element %u", pgeform->oid); + + if (pgekind == PGEKIND_EDGE) + { + Datum srckey; + Datum srcref; + Datum destkey; + Datum destref; + HeapTuple tup2; + Form_pg_propgraph_element pgeform2; + + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgesrckey, RelationGetDescr(pgerel), &isnull); + srckey = isnull ? 0 : datum; + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgesrcref, RelationGetDescr(pgerel), &isnull); + srcref = isnull ? 0 : datum; + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgedestkey, RelationGetDescr(pgerel), &isnull); + destkey = isnull ? 0 : datum; + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgedestref, RelationGetDescr(pgerel), &isnull); + destref = isnull ? 0 : datum; + + appendStringInfoString(buf, " SOURCE"); + tup2 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(pgeform->pgesrcvertexid)); + if (!tup2) + elog(ERROR, "cache lookup failed for property graph element %u", pgeform->pgesrcvertexid); + pgeform2 = (Form_pg_propgraph_element) GETSTRUCT(tup2); + if (srckey) + { + appendStringInfoString(buf, " KEY ("); + decompile_column_index_array(srckey, pgeform->pgerelid, buf); + appendStringInfo(buf, ") REFERENCES %s (", NameStr(pgeform2->pgealias)); + decompile_column_index_array(srcref, pgeform2->pgerelid, buf); + appendStringInfoString(buf, ")"); + } + else + appendStringInfo(buf, " %s ", NameStr(pgeform2->pgealias)); + ReleaseSysCache(tup2); + + appendStringInfoString(buf, " DESTINATION"); + tup2 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(pgeform->pgedestvertexid)); + if (!tup2) + elog(ERROR, "cache lookup failed for property graph element %u", pgeform->pgedestvertexid); + pgeform2 = (Form_pg_propgraph_element) GETSTRUCT(tup2); + if (destkey) + { + appendStringInfoString(buf, " KEY ("); + decompile_column_index_array(destkey, pgeform->pgerelid, buf); + appendStringInfo(buf, ") REFERENCES %s (", NameStr(pgeform2->pgealias)); + decompile_column_index_array(destref, pgeform2->pgerelid, buf); + appendStringInfoString(buf, ")"); + } + else + appendStringInfo(buf, " %s", NameStr(pgeform2->pgealias)); + ReleaseSysCache(tup2); + } + + make_propgraphdef_labels(buf, pgeform->oid, NameStr(pgeform->pgealias), pgeform->pgerelid); + } + if (!first) + appendStringInfo(buf, "\n )"); + + systable_endscan(scan); + table_close(pgerel, AccessShareLock); +} + +/* + * Generates label and properties list. Pass in the element OID, the element + * alias, and the graph relation OID. Result is append to buf. + */ +static void +make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid) +{ + Relation pglrel; + ScanKeyData scankey[1]; + SysScanDesc scan; + int count; + HeapTuple tup; + + pglrel = table_open(PropgraphElementLabelRelationId, AccessShareLock); + + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_element_label_pgelelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(elid)); + + count = 0; + scan = systable_beginscan(pglrel, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, scankey); + while ((tup = systable_getnext(scan))) + { + count++; + } + systable_endscan(scan); + + /* XXX need to re-init scan key for second scan */ + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_element_label_pgelelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(elid)); + + scan = systable_beginscan(pglrel, PropgraphElementLabelElementLabelIndexId, true, NULL, 1, scankey); + + while ((tup = systable_getnext(scan))) + { + Form_pg_propgraph_element_label pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tup); + const char *labelname; + + labelname = get_propgraph_label_name(pgelform->pgellabelid); + + if (strcmp(labelname, elalias) == 0) + { + /* If the default label is the only label, don't print anything. */ + if (count != 1) + appendStringInfo(buf, " DEFAULT LABEL"); + } + else + appendStringInfo(buf, " LABEL %s", quote_identifier(labelname)); + + make_propgraphdef_properties(buf, pgelform->oid, elrelid); + } + + systable_endscan(scan); + + table_close(pglrel, AccessShareLock); +} + +/* + * Helper function for make_propgraphdef_properties(): Sort (propname, expr) + * pairs by name. + */ +static int +propdata_by_name_cmp(const ListCell *a, const ListCell *b) +{ + List *la = lfirst_node(List, a); + List *lb = lfirst_node(List, b); + char *pna = strVal(linitial(la)); + char *pnb = strVal(linitial(lb)); + + return strcmp(pna, pnb); +} + +/* + * Generates element table properties clause (PROPERTIES (...) or NO + * PROPERTIES). Pass in label ODI and element table OID. Result is appended + * to buf. + */ +static void +make_propgraphdef_properties(StringInfo buf, Oid ellabelid, Oid elrelid) +{ + Relation plprel; + ScanKeyData scankey[1]; + SysScanDesc scan; + HeapTuple tup; + List *outlist = NIL; + + plprel = table_open(PropgraphLabelPropertyRelationId, AccessShareLock); + + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_label_property_plpellabelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ellabelid)); + + /* + * We want to output the properties in a deterministic order. So we first + * read all the data, then sort, then print it. + */ + scan = systable_beginscan(plprel, PropgraphLabelPropertyLabelPropIndexId, true, NULL, 1, scankey); + + while ((tup = systable_getnext(scan))) + { + Form_pg_propgraph_label_property plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tup); + Datum exprDatum; + bool isnull; + char *tmp; + Node *expr; + char *propname; + + exprDatum = heap_getattr(tup, Anum_pg_propgraph_label_property_plpexpr, RelationGetDescr(plprel), &isnull); + Assert(!isnull); + tmp = TextDatumGetCString(exprDatum); + expr = stringToNode(tmp); + pfree(tmp); + + propname = get_propgraph_property_name(plpform->plppropid); + + outlist = lappend(outlist, list_make2(makeString(propname), expr)); + } + + systable_endscan(scan); + table_close(plprel, AccessShareLock); + + list_sort(outlist, propdata_by_name_cmp); + + if (outlist) + { + List *context; + ListCell *lc; + bool first = true; + + context = deparse_context_for(get_relation_name(elrelid), elrelid); + + appendStringInfo(buf, " PROPERTIES ("); + + foreach(lc, outlist) + { + List *data = lfirst_node(List, lc); + char *propname = strVal(linitial(data)); + Node *expr = lsecond(data); + + if (first) + first = false; + else + appendStringInfo(buf, ", "); + + if (IsA(expr, Var) && strcmp(propname, get_attname(elrelid, castNode(Var, expr)->varattno, false)) == 0) + appendStringInfo(buf, "%s", propname); + else + appendStringInfo(buf, "%s AS %s", + deparse_expression_pretty(expr, context, false, false, 0, 0), + propname); + } + + appendStringInfo(buf, ")"); + } + else + appendStringInfo(buf, " NO PROPERTIES"); +} + /* * pg_get_statisticsobjdef * Get the definition of an extended statistics object @@ -7275,6 +7608,171 @@ get_utility_query_def(Query *query, deparse_context *context) } } + +/* + * Parse back a graph label expression + */ +static void +get_graph_label_expr(Node *label_expr, deparse_context *context) +{ + StringInfo buf = context->buf; + + check_stack_depth(); + + switch (nodeTag(label_expr)) + { + case T_GraphLabelRef: + { + GraphLabelRef *lref = (GraphLabelRef *) label_expr; + + appendStringInfoString(buf, quote_identifier(get_propgraph_label_name(lref->labelid))); + break; + } + + case T_BoolExpr: + { + BoolExpr *be = (BoolExpr *) label_expr; + ListCell *lc; + bool first = true; + + Assert(be->boolop == OR_EXPR); + + foreach(lc, be->args) + { + if (!first) + { + if (be->boolop == OR_EXPR) + appendStringInfoString(buf, "|"); + } + else + first = false; + get_graph_label_expr(lfirst(lc), context); + } + + break; + } + + default: + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(label_expr)); + break; + } +} + +/* + * Parse back a path pattern expression + */ +static void +get_path_pattern_expr_def(List *path_pattern_expr, deparse_context *context) +{ + StringInfo buf = context->buf; + ListCell *lc; + + foreach(lc, path_pattern_expr) + { + GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc); + const char *sep = ""; + + switch (gep->kind) + { + case VERTEX_PATTERN: + appendStringInfoString(buf, "("); + break; + case EDGE_PATTERN_LEFT: + appendStringInfoString(buf, "<-["); + break; + case EDGE_PATTERN_RIGHT: + case EDGE_PATTERN_ANY: + appendStringInfoString(buf, "-["); + break; + case PAREN_EXPR: + appendStringInfoString(buf, "("); + break; + } + + if (gep->variable) + { + appendStringInfoString(buf, gep->variable); + sep = " "; + } + + if (gep->labelexpr) + { + appendStringInfoString(buf, sep); + appendStringInfoString(buf, "IS "); + get_graph_label_expr(gep->labelexpr, context); + sep = " "; + } + + if (gep->subexpr) + { + appendStringInfoString(buf, sep); + get_path_pattern_expr_def(gep->subexpr, context); + sep = " "; + } + + if (gep->whereClause) + { + appendStringInfoString(buf, sep); + appendStringInfoString(buf, "WHERE "); + get_rule_expr(gep->whereClause, context, false); + } + + switch (gep->kind) + { + case VERTEX_PATTERN: + appendStringInfoString(buf, ")"); + break; + case EDGE_PATTERN_LEFT: + case EDGE_PATTERN_ANY: + appendStringInfoString(buf, "]-"); + break; + case EDGE_PATTERN_RIGHT: + appendStringInfoString(buf, "]->"); + break; + case PAREN_EXPR: + appendStringInfoString(buf, ")"); + break; + } + + if (gep->quantifier) + { + int lower = linitial_int(gep->quantifier); + int upper = lsecond_int(gep->quantifier); + + appendStringInfo(buf, "{%d,%d}", lower, upper); + } + } +} + +/* + * Parse back a graph pattern + */ +static void +get_graph_pattern_def(GraphPattern *graph_pattern, deparse_context *context) +{ + StringInfo buf = context->buf; + ListCell *lc; + bool first = true; + + foreach(lc, graph_pattern->path_pattern_list) + { + List *path_pattern_expr = lfirst_node(List, lc); + + if (!first) + appendStringInfoString(buf, ", "); + else + first = false; + + get_path_pattern_expr_def(path_pattern_expr, context); + } + + if (graph_pattern->whereClause) + { + appendStringInfoString(buf, "WHERE "); + get_rule_expr(graph_pattern->whereClause, context, false); + } +} + /* * Display a Var appropriately. * @@ -7837,6 +8335,7 @@ get_name_for_var_field(Var *var, int fieldno, case RTE_RELATION: case RTE_VALUES: case RTE_NAMEDTUPLESTORE: + case RTE_GRAPH_TABLE: case RTE_RESULT: /* @@ -10202,6 +10701,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; @@ -12060,6 +12567,36 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) case RTE_TABLEFUNC: get_tablefunc(rte->tablefunc, context, true); break; + case RTE_GRAPH_TABLE: + appendStringInfoString(buf, "GRAPH_TABLE ("); + appendStringInfoString(buf, generate_relation_name(rte->relid, context->namespaces)); + appendStringInfoString(buf, " MATCH "); + get_graph_pattern_def(rte->graph_pattern, context); + appendStringInfoString(buf, " COLUMNS ("); + { + ListCell *lc; + bool first = true; + + foreach(lc, rte->graph_table_columns) + { + TargetEntry *te = lfirst_node(TargetEntry, lc); + deparse_context context = {0}; + + if (!first) + appendStringInfoString(buf, ", "); + else + first = false; + + context.buf = buf; + + get_rule_expr((Node *) te->expr, &context, false); + appendStringInfoString(buf, " AS "); + appendStringInfoString(buf, quote_identifier(te->resname)); + } + } + appendStringInfoString(buf, ")"); + appendStringInfoString(buf, ")"); + break; case RTE_VALUES: /* Values list RTE */ appendStringInfoChar(buf, '('); diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 48a280d089b..746333e315b 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -32,6 +32,8 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_property.h" #include "catalog/pg_publication.h" #include "catalog/pg_range.h" #include "catalog/pg_statistic.h" @@ -3714,3 +3716,39 @@ get_subscription_name(Oid subid, bool missing_ok) return subname; } + +char * +get_propgraph_label_name(Oid labeloid) +{ + HeapTuple tuple; + char *labelname; + + tuple = SearchSysCache1(PROPGRAPHLABELOID, labeloid); + if (!tuple) + { + elog(ERROR, "cache lookup failed for label %u", labeloid); + return NULL; + } + labelname = pstrdup(NameStr(((Form_pg_propgraph_label) GETSTRUCT(tuple))->pgllabel)); + ReleaseSysCache(tuple); + + return labelname; +} + +char * +get_propgraph_property_name(Oid propoid) +{ + HeapTuple tuple; + char *propname; + + tuple = SearchSysCache1(PROPGRAPHPROPOID, propoid); + if (!tuple) + { + elog(ERROR, "cache lookup failed for property %u", propoid); + return NULL; + } + propname = pstrdup(NameStr(((Form_pg_propgraph_property) GETSTRUCT(tuple))->pgpname)); + ReleaseSysCache(tuple); + + return propname; +} diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 64e7dc89f13..be950f3acdc 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -520,7 +520,8 @@ flagInhAttrs(Archive *fout, TableInfo *tblinfo, int numTables) /* Some kinds never have parents */ if (tbinfo->relkind == RELKIND_SEQUENCE || tbinfo->relkind == RELKIND_VIEW || - tbinfo->relkind == RELKIND_MATVIEW) + tbinfo->relkind == RELKIND_MATVIEW || + tbinfo->relkind == RELKIND_PROPGRAPH) continue; /* Don't bother computing anything for non-target tables, either */ diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 68e321212d9..29715e5a920 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3641,6 +3641,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te) strcmp(type, "DOMAIN") == 0 || strcmp(type, "FOREIGN TABLE") == 0 || strcmp(type, "MATERIALIZED VIEW") == 0 || + strcmp(type, "PROPERTY GRAPH") == 0 || strcmp(type, "SEQUENCE") == 0 || strcmp(type, "STATISTICS") == 0 || strcmp(type, "TABLE") == 0 || diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e3240708284..ae05b58ee97 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1622,10 +1622,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, @@ -2775,6 +2775,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; @@ -6897,7 +6900,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); @@ -15798,8 +15802,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) reltypename = "VIEW"; - appendPQExpBuffer(delq, "DROP VIEW %s;\n", qualrelname); - if (dopt->binary_upgrade) binary_upgrade_set_pg_class_oids(fout, q, tbinfo->dobj.catId.oid, false); @@ -15825,6 +15827,47 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) appendPQExpBuffer(q, "\n WITH %s CHECK OPTION", tbinfo->checkoption); appendPQExpBufferStr(q, ";\n"); } + else if (tbinfo->relkind == RELKIND_PROPGRAPH) + { + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + int len; + + reltypename = "PROPERTY GRAPH"; + + if (dopt->binary_upgrade) + binary_upgrade_set_pg_class_oids(fout, q, + tbinfo->dobj.catId.oid, false); + + appendPQExpBuffer(query, + "SELECT pg_catalog.pg_get_propgraphdef('%u'::pg_catalog.oid) AS pgdef", + tbinfo->dobj.catId.oid); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + if (PQntuples(res) != 1) + { + if (PQntuples(res) < 1) + pg_fatal("query to obtain definition of property graph \"%s\" returned no data", + tbinfo->dobj.name); + else + pg_fatal("query to obtain definition of property graph \"%s\" returned more than one definition", + tbinfo->dobj.name); + } + + len = PQgetlength(res, 0, 0); + + if (len == 0) + pg_fatal("definition of property graph \"%s\" appears to be empty (length zero)", + tbinfo->dobj.name); + + appendPQExpBufferStr(q, PQgetvalue(res, 0, 0)); + + PQclear(res); + destroyPQExpBuffer(query); + + appendPQExpBufferStr(q, ";\n"); + } else { char *partkeydef = NULL; @@ -15900,8 +15943,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) numParents = tbinfo->numParents; parents = tbinfo->parents; - appendPQExpBuffer(delq, "DROP %s %s;\n", reltypename, qualrelname); - if (dopt->binary_upgrade) binary_upgrade_set_pg_class_oids(fout, q, tbinfo->dobj.catId.oid, false); @@ -16479,6 +16520,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, @@ -18312,6 +18355,16 @@ getDependencies(Archive *fout) "classid = 'pg_amproc'::regclass AND objid = p.oid " "AND NOT (refclassid = 'pg_opfamily'::regclass AND amprocfamily = refobjid)\n"); + /* + * Translate dependencies of pg_propgraph_element entries into + * dependencies of their parent pg_class entry. + */ + appendPQExpBufferStr(query, "UNION ALL\n" + "SELECT 'pg_class'::regclass AS classid, pgepgid AS objid, refclassid, refobjid, deptype " + "FROM pg_depend d, pg_propgraph_element pge " + "WHERE deptype NOT IN ('p', 'e', 'i') AND " + "classid = 'pg_propgraph_element'::regclass AND objid = pge.oid\n"); + /* Sort the output for efficiency below */ appendPQExpBufferStr(query, "ORDER BY 1,2"); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index d3dd8784d64..31cfdc3c1ef 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2912,6 +2912,17 @@ }, }, + 'CREATE PROPERTY GRAPH propgraph' => { + create_order => 20, + create_sql => 'CREATE PROPERTY GRAPH dump_test.propgraph;', + regexp => qr/^ + \QCREATE PROPERTY GRAPH dump_test.propgraph\E; + /xm, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { exclude_dump_test_schema => 1, only_dump_measurement => 1, }, + }, + 'CREATE PUBLICATION pub1' => { create_order => 50, create_sql => 'CREATE PUBLICATION pub1;', diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 180781ecd05..0dc20491180 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -776,7 +776,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': { @@ -907,6 +907,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd) case 'i': case 's': case 'E': + case 'G': success = listTables(&cmd[1], pattern, show_verbose, show_system); break; case 'r': diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index f67bf0b8925..9ab79534e34 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1029,6 +1029,7 @@ permissionsList(const char *pattern, bool showSystem) " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'" " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'" + " WHEN " CppAsString2(RELKIND_PROPGRAPH) " THEN '%s'" " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'" " END as \"%s\",\n" " ", @@ -1039,6 +1040,7 @@ permissionsList(const char *pattern, bool showSystem) gettext_noop("materialized view"), gettext_noop("sequence"), gettext_noop("foreign table"), + gettext_noop("property graph"), gettext_noop("partitioned table"), gettext_noop("Type")); @@ -1130,6 +1132,7 @@ permissionsList(const char *pattern, bool showSystem) CppAsString2(RELKIND_MATVIEW) "," CppAsString2(RELKIND_SEQUENCE) "," CppAsString2(RELKIND_FOREIGN_TABLE) "," + CppAsString2(RELKIND_PROPGRAPH) "," CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n"); if (!showSystem && !pattern) @@ -2013,6 +2016,10 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""), schemaname, relationname); break; + case RELKIND_PROPGRAPH: + printfPQExpBuffer(&title, _("Property graph \"%s.%s\""), + schemaname, relationname); + break; default: /* untranslated unknown relkind */ printfPQExpBuffer(&title, "?%c? \"%s.%s\"", @@ -3052,6 +3059,32 @@ describeOneTableDetails(const char *schemaname, } } + /* Add property graph definition in verbose mode */ + if (tableinfo.relkind == RELKIND_PROPGRAPH && verbose) + { + PGresult *result; + char *pgdef = NULL; + + printfPQExpBuffer(&buf, + "SELECT pg_catalog.pg_get_propgraphdef('%s'::pg_catalog.oid);", + oid); + result = PSQLexec(buf.data); + if (!result) + goto error_return; + + if (PQntuples(result) > 0) + pgdef = pg_strdup(PQgetvalue(result, 0, 0)); + + PQclear(result); + + if (pgdef) + { + printTableAddFooter(&cont, _("Property graph definition:")); + printfPQExpBuffer(&buf, " %s", pgdef); + printTableAddFooter(&cont, buf.data); + } + } + /* Get view_def if table is a view or materialized view */ if ((tableinfo.relkind == RELKIND_VIEW || tableinfo.relkind == RELKIND_MATVIEW) && verbose) @@ -3903,6 +3936,7 @@ describeRoleGrants(const char *pattern, bool showSystem) * m - materialized views * s - sequences * E - foreign table (Note: different from 'f', the relkind value) + * G - property graphs * (any order of the above is fine) */ bool @@ -3914,6 +3948,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys bool showMatViews = strchr(tabtypes, 'm') != NULL; bool showSeq = strchr(tabtypes, 's') != NULL; bool showForeign = strchr(tabtypes, 'E') != NULL; + bool showPropGraphs = strchr(tabtypes, 'G') != NULL; PQExpBufferData buf; PGresult *res; @@ -3922,8 +3957,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys bool translate_columns[] = {false, false, true, false, false, false, false, false, false}; /* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */ - if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign)) - showTables = showViews = showMatViews = showSeq = showForeign = true; + if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign || showPropGraphs)) + showTables = showViews = showMatViews = showSeq = showForeign = showPropGraphs = true; initPQExpBuffer(&buf); @@ -3940,6 +3975,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'" + " WHEN " CppAsString2(RELKIND_PROPGRAPH) " THEN '%s'" " END as \"%s\",\n" " pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"", gettext_noop("Schema"), @@ -3953,6 +3989,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys gettext_noop("foreign table"), gettext_noop("partitioned table"), gettext_noop("partitioned index"), + gettext_noop("property graph"), gettext_noop("Type"), gettext_noop("Owner")); cols_so_far = 4; @@ -4036,6 +4073,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */ if (showForeign) appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ","); + if (showPropGraphs) + appendPQExpBufferStr(&buf, CppAsString2(RELKIND_PROPGRAPH) ","); appendPQExpBufferStr(&buf, "''"); /* dummy */ appendPQExpBufferStr(&buf, ")\n"); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 6f58a110748..8644dbfc5a2 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -246,6 +246,7 @@ slashUsage(unsigned short int pager) HELP0(" \\dFp[+] [PATTERN] list text search parsers\n"); HELP0(" \\dFt[+] [PATTERN] list text search templates\n"); HELP0(" \\dg[S+] [PATTERN] list roles\n"); + HELP0(" \\dG[S+] [PATTERN] list property graphs"); HELP0(" \\di[S+] [PATTERN] list indexes\n"); HELP0(" \\dl[+] list large objects, same as \\lo_list\n"); HELP0(" \\dL[S+] [PATTERN] list procedural languages\n"); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index d453e224d93..53d2e1dbe2b 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -788,6 +788,14 @@ static const SchemaQuery Query_for_list_of_partitioned_indexes = { .result = "c.relname", }; +static const SchemaQuery Query_for_list_of_propgraphs = { + .catname = "pg_catalog.pg_class c", + .selcondition = "c.relkind IN (" CppAsString2(RELKIND_PROPGRAPH) ")", + .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .namespace = "c.relnamespace", + .result = "pg_catalog.quote_ident(c.relname)", +}; + /* All relations */ static const SchemaQuery Query_for_list_of_relations = { @@ -1256,6 +1264,7 @@ static const pgsql_thing_t words_after_create[] = { {"PARSER", NULL, NULL, &Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW}, {"POLICY", NULL, NULL, NULL}, {"PROCEDURE", NULL, NULL, Query_for_list_of_procedures}, + {"PROPERTY GRAPH", NULL, NULL, &Query_for_list_of_propgraphs}, {"PUBLICATION", NULL, Query_for_list_of_publications}, {"ROLE", Query_for_list_of_roles}, {"ROUTINE", NULL, NULL, &Query_for_list_of_routines, NULL, THING_NO_CREATE}, @@ -2307,6 +2316,20 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK")) COMPLETE_WITH("("); + /* ALTER PROPERTY GRAPH */ + else if (Matches("ALTER", "PROPERTY", "GRAPH")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs); + else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny)) + COMPLETE_WITH("ADD", "ALTER", "DROP", "OWNER TO", "RENAME TO", "SET SCHEMA"); + else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD|ALTER|DROP")) + COMPLETE_WITH("VERTEX", "EDGE"); + else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD|DROP", "VERTEX|EDGE")) + COMPLETE_WITH("TABLES"); + else if (HeadMatches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ADD") && TailMatches("EDGE")) + COMPLETE_WITH("TABLES"); + else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny, "ALTER", "VERTEX|EDGE")) + COMPLETE_WITH("TABLE"); + /* ALTER RULE , add ON */ else if (Matches("ALTER", "RULE", MatchAny)) COMPLETE_WITH("ON"); @@ -2800,7 +2823,7 @@ psql_completion(const char *text, int start, int end) "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR", "POLICY", - "PROCEDURE", "PROCEDURAL LANGUAGE", "PUBLICATION", "ROLE", + "PROCEDURE", "PROCEDURAL LANGUAGE", "PROPERTY GRAPH", "PUBLICATION", "ROLE", "ROUTINE", "RULE", "SCHEMA", "SEQUENCE", "SERVER", "STATISTICS", "SUBSCRIPTION", "TABLE", "TABLESPACE", "TEXT SEARCH", "TRANSFORM FOR", @@ -2838,6 +2861,8 @@ psql_completion(const char *text, int start, int end) } else if (Matches("COMMENT", "ON", "PROCEDURAL", "LANGUAGE")) COMPLETE_WITH_QUERY(Query_for_list_of_languages); + else if (Matches("COMMENT", "ON", "PROPERTY", "GRAPH")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs); else if (Matches("COMMENT", "ON", "RULE", MatchAny)) COMPLETE_WITH("ON"); else if (Matches("COMMENT", "ON", "RULE", MatchAny, "ON")) @@ -3156,6 +3181,25 @@ psql_completion(const char *text, int start, int end) else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING")) COMPLETE_WITH("("); +/* CREATE PROPERTY GRAPH */ + else if (Matches("CREATE", "PROPERTY")) + COMPLETE_WITH("GRAPH"); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny)) + COMPLETE_WITH("VERTEX"); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE")) + COMPLETE_WITH("TABLES"); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES")) + COMPLETE_WITH("("); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(*)")) + COMPLETE_WITH("EDGE"); + else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP")) + COMPLETE_WITH("TABLES"); + else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP", "TABLES")) + COMPLETE_WITH("("); + else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && TailMatches("EDGE|RELATIONSHIP", "TABLES", "(")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* CREATE PUBLICATION */ else if (Matches("CREATE", "PUBLICATION", MatchAny)) @@ -3823,6 +3867,12 @@ psql_completion(const char *text, int start, int end) else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); + /* DROP PROPERTY GRAPH */ + else if (Matches("DROP", "PROPERTY")) + COMPLETE_WITH("GRAPH"); + else if (Matches("DROP", "PROPERTY", "GRAPH")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs); + /* DROP RULE */ else if (Matches("DROP", "RULE", MatchAny)) COMPLETE_WITH("ON"); @@ -4069,6 +4119,7 @@ psql_completion(const char *text, int start, int end) "LARGE OBJECT", "PARAMETER", "PROCEDURE", + "PROPERTY GRAPH", "ROUTINE", "SCHEMA", "SEQUENCE", @@ -4198,6 +4249,14 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("FROM"); } +/* GRAPH_TABLE */ + else if (TailMatches("GRAPH_TABLE")) + COMPLETE_WITH("("); + else if (TailMatches("GRAPH_TABLE", "(")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs); + else if (TailMatches("GRAPH_TABLE", "(", MatchAny)) + COMPLETE_WITH("MATCH"); + /* GROUP BY */ else if (TailMatches("FROM", MatchAny, "GROUP")) COMPLETE_WITH("BY"); @@ -4521,8 +4580,10 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("TABLE", "COLUMN", "AGGREGATE", "DATABASE", "DOMAIN", "EVENT TRIGGER", "FOREIGN TABLE", "FUNCTION", "LARGE OBJECT", "MATERIALIZED VIEW", "LANGUAGE", - "PUBLICATION", "PROCEDURE", "ROLE", "ROUTINE", "SCHEMA", + "PROPERTY GRAPH", "PUBLICATION", "PROCEDURE", "ROLE", "ROUTINE", "SCHEMA", "SEQUENCE", "SUBSCRIPTION", "TABLESPACE", "TYPE", "VIEW"); + else if (Matches("SECURITY", "LABEL", "ON", "PROPERTY", "GRAPH")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs); else if (Matches("SECURITY", "LABEL", "ON", MatchAny, MatchAny)) COMPLETE_WITH("IS"); @@ -4939,6 +5000,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("OBJECT"); else if (TailMatches("CREATE|ALTER|DROP", "MATERIALIZED")) COMPLETE_WITH("VIEW"); + else if (TailMatches("CREATE|ALTER|DROP", "PROPERTY")) + COMPLETE_WITH("GRAPH"); else if (TailMatches("CREATE|ALTER|DROP", "TEXT")) COMPLETE_WITH("SEARCH"); else if (TailMatches("CREATE|ALTER|DROP", "USER")) diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l index ddc4658b925..f2f876f5983 100644 --- a/src/fe_utils/psqlscan.l +++ b/src/fe_utils/psqlscan.l @@ -302,6 +302,8 @@ less_equals "<=" greater_equals ">=" less_greater "<>" not_equals "!=" +/* Note there is no need for left_arrow, since "<-" is not a single operator. */ +right_arrow "->" /* * "self" is the set of chars that should be returned as single-character @@ -313,7 +315,7 @@ not_equals "!=" * If you change either set, adjust the character lists appearing in the * rule for "operator"! */ -self [,()\[\].;\:\+\-\*\/\%\^\<\>\=] +self [,()\[\].;\:\|\+\-\*\/\%\^\<\>\=] op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=] operator {op_chars}+ @@ -647,6 +649,10 @@ other . ECHO; } +{right_arrow} { + ECHO; + } + /* * These rules are specific to psql --- they implement parenthesis * counting and detection of command-ending semicolon. These must diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile index 167f91a6e3f..e126aa199b3 100644 --- a/src/include/catalog/Makefile +++ b/src/include/catalog/Makefile @@ -81,7 +81,12 @@ CATALOG_HEADERS := \ pg_publication_namespace.h \ pg_publication_rel.h \ pg_subscription.h \ - pg_subscription_rel.h + pg_subscription_rel.h \ + pg_propgraph_element.h \ + pg_propgraph_element_label.h \ + pg_propgraph_label.h \ + pg_propgraph_label_property.h \ + pg_propgraph_property.h GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build index f70d1daba52..de218c3e99e 100644 --- a/src/include/catalog/meson.build +++ b/src/include/catalog/meson.build @@ -69,6 +69,11 @@ catalog_headers = [ 'pg_publication_rel.h', 'pg_subscription.h', 'pg_subscription_rel.h', + 'pg_propgraph_element.h', + 'pg_propgraph_element_label.h', + 'pg_propgraph_label.h', + 'pg_propgraph_label_property.h', + 'pg_propgraph_property.h', ] # The .dat files we need can just be listed alphabetically. diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 0fc2c093b0d..729a2d70153 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -171,6 +171,7 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128); #define RELKIND_FOREIGN_TABLE 'f' /* foreign table */ #define RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */ #define RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */ +#define RELKIND_PROPGRAPH 'g' /* property graph */ #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6a5476d3c4c..57c8c288a39 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3792,6 +3792,9 @@ proargtypes => 'oid oid', prosrc => 'oidge' }, # System-view support functions +{ oid => '8302', descr => 'source text of a property graph', + proname => 'pg_get_propgraphdef', provolatile => 's', prorettype => 'text', + proargtypes => 'oid', prosrc => 'pg_get_propgraphdef' }, { oid => '1573', descr => 'source text of a rule', proname => 'pg_get_ruledef', provolatile => 's', prorettype => 'text', proargtypes => 'oid', prosrc => 'pg_get_ruledef' }, diff --git a/src/include/catalog/pg_propgraph_element.h b/src/include/catalog/pg_propgraph_element.h new file mode 100644 index 00000000000..2bc2066b6c1 --- /dev/null +++ b/src/include/catalog/pg_propgraph_element.h @@ -0,0 +1,103 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_element.h + * definition of the "property graph elements" system catalog (pg_propgraph_element) + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_element.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_ELEMENT_H +#define PG_PROPGRAPH_ELEMENT_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_element_d.h" + +/* ---------------- + * pg_propgraph_element definition. cpp turns this into + * typedef struct FormData_pg_propgraph_element + * ---------------- + */ +CATALOG(pg_propgraph_element,8299,PropgraphElementRelationId) +{ + Oid oid; + + /* OID of the property graph relation */ + Oid pgepgid BKI_LOOKUP(pg_class); + + /* OID of the element table */ + Oid pgerelid BKI_LOOKUP(pg_class); + + /* element alias */ + NameData pgealias; + + /* vertex or edge? -- see PGEKIND_* below */ + char pgekind; + + /* for edges: source vertex */ + Oid pgesrcvertexid BKI_LOOKUP_OPT(pg_propgraph_element); + + /* for edges: destination vertex */ + Oid pgedestvertexid BKI_LOOKUP_OPT(pg_propgraph_element); + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + /* element key (column numbers in pgerelid relation) */ + int16 pgekey[1] BKI_FORCE_NOT_NULL; + + /* + * for edges: source vertex key (column numbers in pgerelid relation) + */ + int16 pgesrckey[1]; + + /* + * for edges: source vertex table referenced columns (column numbers in + * relation reached via pgesrcvertexid) + */ + int16 pgesrcref[1]; + + /* + * for edges: destination vertex key (column numbers in pgerelid relation) + */ + int16 pgedestkey[1]; + + /* + * for edges: destination vertex table referenced columns (column numbers + * in relation reached via pgedestvertexid) + */ + int16 pgedestref[1]; +#endif +} FormData_pg_propgraph_element; + +/* ---------------- + * Form_pg_propgraph_element corresponds to a pointer to a tuple with + * the format of pg_propgraph_element relation. + * ---------------- + */ +typedef FormData_pg_propgraph_element *Form_pg_propgraph_element; + +DECLARE_TOAST(pg_propgraph_element, 8315, 8316); + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_element_oid_index, 8300, PropgraphElementObjectIndexId, pg_propgraph_element, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_element_alias_index, 8301, PropgraphElementAliasIndexId, pg_propgraph_element, btree(pgepgid oid_ops, pgealias name_ops)); + +MAKE_SYSCACHE(PROPGRAPHELOID, pg_propgraph_element_oid_index, 128); +MAKE_SYSCACHE(PROPGRAPHELALIAS, pg_propgraph_element_alias_index, 128); + +#ifdef EXPOSE_TO_CLIENT_CODE + +/* + * Symbolic values for pgekind column + */ +#define PGEKIND_VERTEX 'v' +#define PGEKIND_EDGE 'e' + +#endif /* EXPOSE_TO_CLIENT_CODE */ + +#endif /* PG_PROPGRAPH_ELEMENT_H */ diff --git a/src/include/catalog/pg_propgraph_element_label.h b/src/include/catalog/pg_propgraph_element_label.h new file mode 100644 index 00000000000..91851a19277 --- /dev/null +++ b/src/include/catalog/pg_propgraph_element_label.h @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_element_label.h + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_element_label.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_ELEMENT_LABEL_H +#define PG_PROPGRAPH_ELEMENT_LABEL_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_element_label_d.h" + +/* ---------------- + * pg_propgraph_element_label definition. cpp turns this into + * typedef struct FormData_pg_propgraph_element_label + * ---------------- + */ +CATALOG(pg_propgraph_element_label,8305,PropgraphElementLabelRelationId) +{ + Oid oid; + + /* OID of the label */ + Oid pgellabelid BKI_LOOKUP(pg_propgraph_label); + + /* OID of the property graph element */ + Oid pgelelid BKI_LOOKUP(pg_propgraph_element); +} FormData_pg_propgraph_element_label; + +/* ---------------- + * Form_pg_propgraph_element_label corresponds to a pointer to a tuple with + * the format of pg_propgraph_element_label relation. + * ---------------- + */ +typedef FormData_pg_propgraph_element_label *Form_pg_propgraph_element_label; + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_element_label_oid_index, 8312, PropgraphElementLabelObjectIndexId, pg_propgraph_element_label, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_element_label_element_label_index, 8313, PropgraphElementLabelElementLabelIndexId, pg_propgraph_element_label, btree(pgelelid oid_ops, pgellabelid oid_ops)); +DECLARE_INDEX(pg_propgraph_element_label_label_index, 8317, PropgraphElementLabelLabelIndexId, pg_propgraph_element_label, btree(pgellabelid oid_ops)); + +MAKE_SYSCACHE(PROPGRAPHELEMENTLABELELEMENTLABEL, pg_propgraph_element_label_element_label_index, 128); + +#endif /* PG_PROPGRAPH_ELEMENT_LABEL_H */ diff --git a/src/include/catalog/pg_propgraph_label.h b/src/include/catalog/pg_propgraph_label.h new file mode 100644 index 00000000000..c6b711351b5 --- /dev/null +++ b/src/include/catalog/pg_propgraph_label.h @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_label.h + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_label.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_LABEL_H +#define PG_PROPGRAPH_LABEL_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_label_d.h" + +/* ---------------- + * pg_propgraph_label definition. cpp turns this into + * typedef struct FormData_pg_propgraph_label + * ---------------- + */ +CATALOG(pg_propgraph_label,8303,PropgraphLabelRelationId) +{ + Oid oid; + + /* OID of the property graph relation */ + Oid pglpgid BKI_LOOKUP(pg_class); + + /* label name */ + NameData pgllabel; +} FormData_pg_propgraph_label; + +/* ---------------- + * Form_pg_propgraph_label corresponds to a pointer to a tuple with + * the format of pg_propgraph_label relation. + * ---------------- + */ +typedef FormData_pg_propgraph_label *Form_pg_propgraph_label; + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_label_oid_index, 8304, PropgraphLabelObjectIndexId, pg_propgraph_label, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_label_graph_name_index, 8314, PropgraphLabelGraphNameIndexId, pg_propgraph_label, btree(pglpgid oid_ops, pgllabel name_ops)); + +MAKE_SYSCACHE(PROPGRAPHLABELOID, pg_propgraph_label_oid_index, 128); +MAKE_SYSCACHE(PROPGRAPHLABELNAME, pg_propgraph_label_graph_name_index, 128); + +#endif /* PG_PROPGRAPH_LABEL_H */ diff --git a/src/include/catalog/pg_propgraph_label_property.h b/src/include/catalog/pg_propgraph_label_property.h new file mode 100644 index 00000000000..a9595215675 --- /dev/null +++ b/src/include/catalog/pg_propgraph_label_property.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_label_property.h + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_label_property.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_LABEL_PROPERTY_H +#define PG_PROPGRAPH_LABEL_PROPERTY_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_label_property_d.h" + +/* ---------------- + * pg_propgraph_label_property definition. cpp turns this into + * typedef struct FormData_pg_propgraph_label_property + * ---------------- + */ +CATALOG(pg_propgraph_label_property,8318,PropgraphLabelPropertyRelationId) +{ + Oid oid; + + /* OID of the property */ + Oid plppropid BKI_LOOKUP(pg_propgraph_property); + + /* OID of the element label */ + Oid plpellabelid BKI_LOOKUP(pg_propgraph_element_label); + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + + /* property expression */ + pg_node_tree plpexpr BKI_FORCE_NOT_NULL; + +#endif +} FormData_pg_propgraph_label_property; + +/* ---------------- + * Form_pg_propgraph_label_property corresponds to a pointer to a tuple with + * the format of pg_propgraph_label_property relation. + * ---------------- + */ +typedef FormData_pg_propgraph_label_property *Form_pg_propgraph_label_property; + +DECLARE_TOAST(pg_propgraph_label_property, 8319, 8320); + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_label_property_oid_index, 8321, PropgraphLabelPropertyObjectIndexId, pg_propgraph_label_property, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_label_property_label_prop_index, 8322, PropgraphLabelPropertyLabelPropIndexId, pg_propgraph_label_property, btree(plpellabelid oid_ops, plppropid oid_ops)); + +MAKE_SYSCACHE(PROPGRAPHLABELPROP, pg_propgraph_label_property_label_prop_index, 128); + +#endif /* PG_PROPGRAPH_LABEL_PROPERTY_H */ diff --git a/src/include/catalog/pg_propgraph_property.h b/src/include/catalog/pg_propgraph_property.h new file mode 100644 index 00000000000..b8921ace30c --- /dev/null +++ b/src/include/catalog/pg_propgraph_property.h @@ -0,0 +1,54 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_property.h + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_property.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_PROPERTY_H +#define PG_PROPGRAPH_PROPERTY_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_property_d.h" + +/* ---------------- + * pg_propgraph_property definition. cpp turns this into + * typedef struct FormData_pg_propgraph_property + * ---------------- + */ +CATALOG(pg_propgraph_property,8306,PropgraphPropertyRelationId) +{ + Oid oid; + + /* OID of the property graph relation */ + Oid pgppgid BKI_LOOKUP(pg_class); + + /* property name */ + NameData pgpname; + + /* data type of the property */ + Oid pgptypid BKI_LOOKUP_OPT(pg_type); +} FormData_pg_propgraph_property; + +/* ---------------- + * Form_pg_propgraph_property corresponds to a pointer to a tuple with + * the format of pg_propgraph_property relation. + * ---------------- + */ +typedef FormData_pg_propgraph_property *Form_pg_propgraph_property; + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_property_oid_index, 8307, PropgraphPropertyObjectIndexId, pg_propgraph_property, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_property_name_index, 8308, PropgraphPropertyNameIndexId, pg_propgraph_property, btree(pgppgid oid_ops, pgpname name_ops)); + +MAKE_SYSCACHE(PROPGRAPHPROPOID, pg_propgraph_property_oid_index, 128); +MAKE_SYSCACHE(PROPGRAPHPROPNAME, pg_propgraph_property_name_index, 128); + +#endif /* PG_PROPGRAPH_PROPERTY_H */ diff --git a/src/include/commands/propgraphcmds.h b/src/include/commands/propgraphcmds.h new file mode 100644 index 00000000000..2440bd4a60c --- /dev/null +++ b/src/include/commands/propgraphcmds.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * propgraphcmds.h + * prototypes for propgraphcmds.c. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/propgraphcmds.h + * + *------------------------------------------------------------------------- + */ + +#ifndef PROPGRAPHCMDS_H +#define PROPGRAPHCMDS_H + +#include "catalog/objectaddress.h" +#include "parser/parse_node.h" + +extern ObjectAddress CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt); +extern ObjectAddress AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt); + +#endif /* PROPGRAPHCMDS_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 85a62b538e5..c4223fda574 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -682,6 +682,19 @@ typedef struct RangeTableFuncCol ParseLoc location; /* token location, or -1 if unknown */ } RangeTableFuncCol; +/* + * RangeGraphTable - raw form of GRAPH_TABLE clause + */ +typedef struct RangeGraphTable +{ + NodeTag type; + RangeVar *graph_name; + struct GraphPattern *graph_pattern; + List *columns; + Alias *alias; /* table alias & optional column aliases */ + ParseLoc location; /* token location, or -1 if unknown */ +} RangeGraphTable; + /* * RangeTableSample - TABLESAMPLE appearing in a raw FROM clause * @@ -961,6 +974,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 ****************************************************************************/ @@ -1033,6 +1082,7 @@ typedef enum RTEKind RTE_VALUES, /* VALUES (), (), ... */ 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 */ @@ -1193,6 +1243,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): */ @@ -2288,6 +2344,7 @@ typedef enum ObjectType OBJECT_PARAMETER_ACL, OBJECT_POLICY, OBJECT_PROCEDURE, + OBJECT_PROPGRAPH, OBJECT_PUBLICATION, OBJECT_PUBLICATION_NAMESPACE, OBJECT_PUBLICATION_REL, @@ -4014,6 +4071,88 @@ typedef struct CreateCastStmt bool inout; } CreateCastStmt; +/* ---------------------- + * CREATE PROPERTY GRAPH Statement + * ---------------------- + */ +typedef struct CreatePropGraphStmt +{ + NodeTag type; + RangeVar *pgname; + List *vertex_tables; + List *edge_tables; +} CreatePropGraphStmt; + +typedef struct PropGraphVertex +{ + NodeTag type; + RangeVar *vtable; + List *vkey; + List *labels; + ParseLoc location; +} PropGraphVertex; + +typedef struct PropGraphEdge +{ + NodeTag type; + RangeVar *etable; + List *ekey; + List *esrckey; + char *esrcvertex; + List *esrcvertexcols; + List *edestkey; + char *edestvertex; + List *edestvertexcols; + List *labels; + ParseLoc location; +} PropGraphEdge; + +typedef struct PropGraphLabelAndProperties +{ + NodeTag type; + const char *label; + struct PropGraphProperties *properties; + ParseLoc location; +} PropGraphLabelAndProperties; + +typedef struct PropGraphProperties +{ + NodeTag type; + List *properties; + bool all; + ParseLoc location; +} PropGraphProperties; + +/* ---------------------- + * ALTER PROPERTY GRAPH Statement + * ---------------------- + */ + +typedef enum AlterPropGraphElementKind +{ + PROPGRAPH_ELEMENT_KIND_VERTEX = 1, + PROPGRAPH_ELEMENT_KIND_EDGE = 2, +} AlterPropGraphElementKind; + +typedef struct AlterPropGraphStmt +{ + NodeTag type; + RangeVar *pgname; + bool missing_ok; + List *add_vertex_tables; + List *add_edge_tables; + List *drop_vertex_tables; + List *drop_edge_tables; + DropBehavior drop_behavior; + AlterPropGraphElementKind element_kind; + const char *element_alias; + List *add_labels; + const char *drop_label; + const char *alter_label; + PropGraphProperties *add_properties; + List *drop_properties; +} AlterPropGraphStmt; + /* ---------------------- * CREATE TRANSFORM Statement * ---------------------- diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 4830efc5738..d2f469d291c 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -2130,6 +2130,28 @@ typedef struct InferenceElem Oid inferopclass; /* OID of att opclass, or InvalidOid */ } InferenceElem; +/* + * GraphLabelRef - label reference in label expression inside GRAPH_TABLE clause + */ +typedef struct GraphLabelRef +{ + NodeTag type; + Oid labelid; + ParseLoc location; +} GraphLabelRef; + +/* + * GraphPropertyRef - property reference inside GRAPH_TABLE clause + */ +typedef struct GraphPropertyRef +{ + Expr xpr; + const char *elvarname; + Oid propid; + Oid typeId; + ParseLoc location; +} GraphPropertyRef; + /*-------------------- * TargetEntry - * a target entry (used in query target lists) diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 28b66fccb43..385f7fdfa17 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -62,5 +62,8 @@ extern List *BuildOnConflictExcludedTargetlist(Relation targetrel, Index exclRelIndex); extern SortGroupClause *makeSortGroupClauseForSetOp(Oid rescoltype, bool require_hash); +extern void constructSetOpTargetlist(SetOperationStmt *op, List *ltargetlist, + List *rtargetlist, List **targetlist, const char *context, + ParseState *pstate, bool recursive); #endif /* ANALYZE_H */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index f7fe834cf45..54ba90e635a 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -136,6 +136,7 @@ PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("depth", DEPTH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("desc", DESC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("destination", DESTINATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) @@ -147,6 +148,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("edge", EDGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) @@ -190,6 +192,8 @@ PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("graph", GRAPH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("graph_table", GRAPH_TABLE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD, BARE_LABEL) @@ -294,6 +298,7 @@ PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("no", NO, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("node", NODE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("none", NONE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD, BARE_LABEL) @@ -356,6 +361,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) @@ -370,6 +377,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) @@ -490,6 +498,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("vertex", VERTEX, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h new file mode 100644 index 00000000000..af0f550cd2c --- /dev/null +++ b/src/include/parser/parse_graphtable.h @@ -0,0 +1,31 @@ +/*------------------------------------------------------------------------- + * + * parse_graphtable.h + * parsing of GRAPH_TABLE + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/parser/parse_graphtable.h + * + *------------------------------------------------------------------------- + */ +#ifndef PARSE_GRAPHTABLE_H +#define PARSE_GRAPHTABLE_H + +#include "nodes/parsenodes.h" +#include "nodes/pg_list.h" +#include "parser/parse_node.h" + +typedef struct GraphTableParseState +{ + Oid graphid; + List *variables; +} GraphTableParseState; + +extern Node *graph_table_property_reference(ParseState *pstate, ColumnRef *cref, Node *var); + +extern Node *transformGraphPattern(ParseState *pstate, GraphTableParseState *gpstate, GraphPattern *graph_pattern); + +#endif /* PARSE_GRAPHTABLE_H */ diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 5b781d87a9d..3246fbe156f 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -82,6 +82,7 @@ typedef enum ParseExprKind EXPR_KIND_COPY_WHERE, /* WHERE condition in COPY FROM */ EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */ EXPR_KIND_CYCLE_MARK, /* cycle mark value */ + EXPR_KIND_PROPGRAPH_PROPERTY, /* derived property expression */ } ParseExprKind; diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index bea2da54961..c11b895b7bb 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -81,6 +81,14 @@ extern ParseNamespaceItem *addRangeTableEntryForTableFunc(ParseState *pstate, Alias *alias, bool lateral, bool inFromCl); +extern ParseNamespaceItem *addRangeTableEntryForGraphTable(ParseState *pstate, + Oid graphid, + GraphPattern *graph_pattern, + List *columns, + List *colnames, + Alias *alias, + bool lateral, + bool inFromCl); extern ParseNamespaceItem *addRangeTableEntryForJoin(ParseState *pstate, List *colnames, ParseNamespaceColumn *nscolumns, diff --git a/src/include/rewrite/rewriteGraphTable.h b/src/include/rewrite/rewriteGraphTable.h new file mode 100644 index 00000000000..0c0319f5cfa --- /dev/null +++ b/src/include/rewrite/rewriteGraphTable.h @@ -0,0 +1,21 @@ +/*------------------------------------------------------------------------- + * + * rewriteGraphTable.h + * Support for rewriting GRAPH_TABLE clauses. + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/rewrite/rewriteGraphTable.h + * + *------------------------------------------------------------------------- + */ +#ifndef REWRITEGRAPHTABLE_H +#define REWRITEGRAPHTABLE_H + +#include "nodes/parsenodes.h" + +extern Query *rewriteGraphTable(Query *parsetree, int rt_index); + +#endif /* REWRITEGRAPHTABLE_H */ diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h index 7fdcec6dd93..5582483d06c 100644 --- a/src/include/tcop/cmdtaglist.h +++ b/src/include/tcop/cmdtaglist.h @@ -48,6 +48,7 @@ PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, fals PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false) PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false) PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false) +PG_CMDTAG(CMDTAG_ALTER_PROPERTY_GRAPH, "ALTER PROPERTY GRAPH", true, false, false) PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false) PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false) PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false) @@ -103,6 +104,7 @@ PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, fa PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false) PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false) PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false) +PG_CMDTAG(CMDTAG_CREATE_PROPERTY_GRAPH, "CREATE PROPERTY GRAPH", true, false, false) PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false) PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false) PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false) @@ -156,6 +158,7 @@ PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, fals PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false) PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false) PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false) +PG_CMDTAG(CMDTAG_DROP_PROPERTY_GRAPH, "DROP PROPERTY GRAPH", true, false, false) PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false) PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false) PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false) diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 731d84b2a93..b6813d7292e 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -166,6 +166,7 @@ typedef struct ArrayType Acl; #define ACL_ALL_RIGHTS_LANGUAGE (ACL_USAGE) #define ACL_ALL_RIGHTS_LARGEOBJECT (ACL_SELECT|ACL_UPDATE) #define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM) +#define ACL_ALL_RIGHTS_PROPGRAPH (ACL_SELECT) #define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE) #define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE) #define ACL_ALL_RIGHTS_TYPE (ACL_USAGE) diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 20446f6f836..82d0bbb267d 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -206,6 +206,9 @@ extern char *get_publication_name(Oid pubid, bool missing_ok); extern Oid get_subscription_oid(const char *subname, bool missing_ok); extern char *get_subscription_name(Oid subid, bool missing_ok); +extern char *get_propgraph_label_name(Oid labeloid); +extern char *get_propgraph_property_name(Oid propoid); + #define type_is_array(typid) (get_element_type(typid) != InvalidOid) /* type_is_array_domain accepts both plain arrays and domains over arrays */ #define type_is_array_domain(typid) (get_base_element_type(typid) != InvalidOid) diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index fe8d3e51780..24659cb41e0 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -67,7 +67,8 @@ 'EQUALS_GREATER' => '=>', 'LESS_EQUALS' => '<=', 'GREATER_EQUALS' => '>=', - 'NOT_EQUALS' => '<>',); + 'NOT_EQUALS' => '<>', + 'RIGHT_ARROW' => '->',); # specific replace_types for specific non-terminals - never include the ':' # ECPG-only replace_types are defined in ecpg-replace_types @@ -186,9 +187,9 @@ sub main my $prec = 0; - # Make sure any braces are split - s/{/ { /g; - s/}/ } /g; + # Make sure any (unquoted) braces are split + s/(?=" 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}+ @@ -819,6 +821,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) @@ -910,7 +916,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]; /* * Likewise, if what we have left is two chars, and @@ -930,6 +936,8 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+ return NOT_EQUALS; if (yytext[0] == '!' && yytext[1] == '=') return NOT_EQUALS; + if (yytext[0] == '-' && yytext[1] == '>') + return RIGHT_ARROW; } } diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index a29d2dfacdd..d3bbea95728 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -232,6 +232,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token ICONST PARAM %token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER %token LESS_EQUALS GREATER_EQUALS NOT_EQUALS +%token BRACKET_RIGHT_ARROW LEFT_ARROW_BRACKET MINUS_LEFT_BRACKET RIGHT_BRACKET_MINUS /* * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c). diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out index ae54cb254f9..ab9ce9e9acd 100644 --- a/src/test/regress/expected/alter_generic.out +++ b/src/test/regress/expected/alter_generic.out @@ -520,6 +520,49 @@ ERROR: left and right associated data types for operator class options parsing ALTER OPERATOR FAMILY alt_opf19 USING btree ADD FUNCTION 5 (int4) test_opclass_options_func(internal); -- Ok ALTER OPERATOR FAMILY alt_opf19 USING btree DROP FUNCTION 5 (int4, int4); DROP OPERATOR FAMILY alt_opf19 USING btree; +-- +-- Property Graph +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; +CREATE PROPERTY GRAPH alt_graph1; +CREATE PROPERTY GRAPH alt_graph2; +CREATE PROPERTY GRAPH alt_graph3; +ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph2; -- failed (name conflict) +ERROR: relation "alt_graph2" already exists +ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph4; -- OK +ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ERROR: must be able to SET ROLE "regress_alter_generic_user2" +ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user3; -- OK +ALTER PROPERTY GRAPH alt_graph4 SET SCHEMA alt_nsp2; -- OK +ALTER PROPERTY GRAPH alt_nsp2.alt_graph4 RENAME TO alt_graph2; -- OK +ALTER PROPERTY GRAPH alt_graph2 SET SCHEMA alt_nsp2; -- failed (name conflict) +ERROR: relation "alt_graph2" already exists in schema "alt_nsp2" +SET SESSION AUTHORIZATION regress_alter_generic_user2; +CREATE PROPERTY GRAPH alt_graph5; +ALTER PROPERTY GRAPH alt_graph3 RENAME TO alt_graph5; -- failed (not owner) +ERROR: must be owner of property graph alt_graph3 +ALTER PROPERTY GRAPH alt_graph5 RENAME TO alt_graph6; -- OK +ALTER PROPERTY GRAPH alt_graph3 OWNER TO regress_alter_generic_user2; -- failed (not owner) +ERROR: must be owner of property graph alt_graph3 +ALTER PROPERTY GRAPH alt_graph6 OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ERROR: must be able to SET ROLE "regress_alter_generic_user3" +ALTER PROPERTY GRAPH alt_graph3 SET SCHEMA alt_nsp2; -- failed (not owner) +ERROR: must be owner of property graph alt_graph3 +RESET SESSION AUTHORIZATION; +SELECT nspname, relname, rolname + FROM pg_class c, pg_namespace n, pg_authid a + WHERE c.relnamespace = n.oid AND c.relowner = a.oid + AND n.nspname in ('alt_nsp1', 'alt_nsp2') + AND c.relkind = 'g' + ORDER BY nspname, relname; + nspname | relname | rolname +----------+------------+----------------------------- + alt_nsp1 | alt_graph2 | regress_alter_generic_user3 + alt_nsp1 | alt_graph3 | regress_alter_generic_user1 + alt_nsp1 | alt_graph6 | regress_alter_generic_user2 + alt_nsp2 | alt_graph2 | regress_alter_generic_user1 +(4 rows) + -- -- Statistics -- @@ -710,7 +753,7 @@ NOTICE: drop cascades to server alt_fserv3 DROP LANGUAGE alt_lang2 CASCADE; DROP LANGUAGE alt_lang3 CASCADE; DROP SCHEMA alt_nsp1 CASCADE; -NOTICE: drop cascades to 28 other objects +NOTICE: drop cascades to 31 other objects DETAIL: drop cascades to function alt_func3(integer) drop cascades to function alt_agg3(integer) drop cascades to function alt_func4(integer) @@ -727,6 +770,9 @@ drop cascades to operator family alt_opc1 for access method hash drop cascades to operator family alt_opc2 for access method hash drop cascades to operator family alt_opf4 for access method hash drop cascades to operator family alt_opf2 for access method hash +drop cascades to property graph alt_graph2 +drop cascades to property graph alt_graph3 +drop cascades to property graph alt_graph6 drop cascades to table alt_regress_1 drop cascades to table alt_regress_2 drop cascades to text search dictionary alt_ts_dict3 @@ -740,12 +786,13 @@ drop cascades to text search template alt_ts_temp2 drop cascades to text search parser alt_ts_prs3 drop cascades to text search parser alt_ts_prs2 DROP SCHEMA alt_nsp2 CASCADE; -NOTICE: drop cascades to 9 other objects +NOTICE: drop cascades to 10 other objects DETAIL: drop cascades to function alt_nsp2.alt_func2(integer) drop cascades to function alt_nsp2.alt_agg2(integer) drop cascades to conversion alt_nsp2.alt_conv2 drop cascades to operator alt_nsp2.@-@(integer,integer) drop cascades to operator family alt_nsp2.alt_opf2 for access method hash +drop cascades to property graph alt_nsp2.alt_graph2 drop cascades to text search dictionary alt_nsp2.alt_ts_dict2 drop cascades to text search configuration alt_nsp2.alt_ts_conf2 drop cascades to text search template alt_nsp2.alt_ts_temp2 diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out new file mode 100644 index 00000000000..43316fbc029 --- /dev/null +++ b/src/test/regress/expected/create_property_graph.out @@ -0,0 +1,496 @@ +CREATE SCHEMA create_property_graph_tests; +GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC; +SET search_path = create_property_graph_tests; +CREATE ROLE regress_graph_user1; +CREATE ROLE regress_graph_user2; +CREATE PROPERTY GRAPH g1; +COMMENT ON PROPERTY GRAPH g1 IS 'a graph'; +CREATE PROPERTY GRAPH g1; -- error: duplicate +ERROR: relation "g1" already exists +CREATE TABLE t1 (a int, b text); +CREATE TABLE t2 (i int PRIMARY KEY, j int, k int); +CREATE TABLE t3 (x int, y text, z text); +CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i)); +CREATE TABLE e2 (a int, x int, t text); +CREATE PROPERTY GRAPH g2 + VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); +-- test dependencies/object descriptions +DROP TABLE t1; -- fail +ERROR: cannot drop table t1 because other objects depend on it +DETAIL: vertex t1 of property graph g2 depends on table t1 +edge e1 of property graph g2 depends on table t1 +edge e2 of property graph g2 depends on table t1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER TABLE t1 DROP COLUMN b; -- non-key column; fail +ERROR: cannot drop column b of table t1 because other objects depend on it +DETAIL: property b of label t1 of vertex t1 of property graph g2 depends on column b of table t1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER TABLE t1 DROP COLUMN a; -- key column; fail +ERROR: cannot drop column a of table t1 because other objects depend on it +DETAIL: vertex t1 of property graph g2 depends on column a of table t1 +edge e1 of property graph g2 depends on column a of table t1 +edge e2 of property graph g2 depends on column a of table t1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- like g2 but assembled with ALTER +CREATE PROPERTY GRAPH g3; +ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL); +ALTER PROPERTY GRAPH g3 + ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1) + ADD EDGE TABLES ( + e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS ADD LABEL t3l3 PROPERTIES ALL COLUMNS; +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x; -- error +ERROR: property graph "g3" element "t3" has no label "t3l3x" +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3; +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2); -- fail (TODO: dubious error message) +ERROR: cannot drop vertex t2 of property graph g3 because other objects depend on it +DETAIL: edge e1 of property graph g3 depends on vertex t2 of property graph g3 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE; +NOTICE: drop cascades to edge e1 of property graph g3 +ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2); +CREATE PROPERTY GRAPH g4 + VERTEX TABLES ( + t1 KEY (a) NO PROPERTIES, + t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz) + ) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i) + PROPERTIES ALL COLUMNS, + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + PROPERTIES ALL COLUMNS + ); +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk); +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k); +CREATE TABLE t11 (a int PRIMARY KEY); +CREATE TABLE t12 (b int PRIMARY KEY); +CREATE TABLE t13 ( + c int PRIMARY KEY, + d int REFERENCES t11, + e int REFERENCES t12 +); +CREATE PROPERTY GRAPH g5 + VERTEX TABLES (t11, t12) + EDGE TABLES (t13 SOURCE t11 DESTINATION t12); +SELECT pg_get_propgraphdef('g5'::regclass); + pg_get_propgraphdef +------------------------------------------------------------------------------------------------------------------- + CREATE PROPERTY GRAPH create_property_graph_tests.g5 + + VERTEX TABLES ( + + t11 KEY (a) PROPERTIES (a), + + t12 KEY (b) PROPERTIES (b) + + ) + + EDGE TABLES ( + + t13 KEY (c) SOURCE KEY (e) REFERENCES t11 (a) DESTINATION KEY (e) REFERENCES t12 (b) PROPERTIES (c, d, e)+ + ) +(1 row) + +-- error cases +CREATE PROPERTY GRAPH gx VERTEX TABLES (xx, yy); +ERROR: relation "xx" does not exist +CREATE PROPERTY GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a)); +ERROR: alias "t1" used more than once as element table +LINE 1: ...Y GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a)... + ^ +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION t2 + ); +ERROR: source vertex "t1" of edge "e1" does not exist +LINE 4: e1 SOURCE t1 DESTINATION t2 + ^ +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION tx + ); +ERROR: destination vertex "tx" of edge "e1" does not exist +LINE 4: e1 SOURCE t1 DESTINATION tx + ^ +COMMENT ON PROPERTY GRAPH gx IS 'not a graph'; +ERROR: relation "gx" does not exist +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 KEY (a), t2) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION t2 -- no foreign keys + ); +ERROR: no SOURCE key specified and no suitable foreign key exists for definition of edge "e1" +LINE 4: e1 SOURCE t1 DESTINATION t2 -- no foreign keys + ^ +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL foo PROPERTIES (a + 1 AS aa) + LABEL bar PROPERTIES (1 + a AS aa) -- expression mismatch + ); +ERROR: element "t1" property "aa" expression mismatch: (1 + a) vs. (a + 1) +DETAIL: In a property graph element, a property of the same name has to have the same expression in each label. +ALTER PROPERTY GRAPH g2 + ADD VERTEX TABLES ( + t1 AS t1x KEY (a) LABEL foo PROPERTIES (a + 1 AS aa) + LABEL bar PROPERTIES (1 + a AS aa) -- expression mismatch + ); +ERROR: element "t1x" property "aa" expression mismatch: (1 + a) vs. (a + 1) +DETAIL: In a property graph element, a property of the same name has to have the same expression in each label. +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) PROPERTIES (b AS p1), + t2 PROPERTIES (k AS p1) -- type mismatch + ); +ERROR: property "p1" data type mismatch: text vs. integer +DETAIL: In a property graph, a property of the same name has to have the same data type in each label. +ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE t1 ADD LABEL foo PROPERTIES (b AS k); -- type mismatch +ERROR: property "k" data type mismatch: integer vs. text +DETAIL: In a property graph, a property of the same name has to have the same data type in each label. +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa), + t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS b, k) -- mismatching number of properties on label + ); +ERROR: mismatching number of properties in definition of label "l1" +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL l1 PROPERTIES (a, b), + t2 KEY (i) LABEL l1 PROPERTIES (i AS a) -- mismatching number of properties on label + ); +ERROR: mismatching number of properties in definition of label "l1" +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL l1 PROPERTIES (a, b), + t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS j) -- mismatching property names on label + ); +ERROR: mismatching properties names in definition of label "l1" +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS yy, b AS zz); -- mismatching number of properties on label +ERROR: mismatching number of properties in definition of label "t3l1" +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS zz); -- mismatching property names on label +ERROR: mismatching properties names in definition of label "t3l1" +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x); -- mismatching number of properties on label +ERROR: mismatching number of properties in definition of label "t3l1" +ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1; +SET ROLE regress_graph_user1; +GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2; +GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2; -- fail +ERROR: invalid privilege type UPDATE for property graph +RESET ROLE; +-- information schema +SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name; + property_graph_catalog | property_graph_schema | property_graph_name +------------------------+-----------------------------+--------------------- + regression | create_property_graph_tests | g1 + regression | create_property_graph_tests | g2 + regression | create_property_graph_tests | g3 + regression | create_property_graph_tests | g4 + regression | create_property_graph_tests | g5 +(5 rows) + +SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | element_table_kind | table_catalog | table_schema | table_name | element_table_definition +------------------------+-----------------------------+---------------------+---------------------+--------------------+---------------+-----------------------------+------------+-------------------------- + regression | create_property_graph_tests | g2 | e1 | EDGE | regression | create_property_graph_tests | e1 | + regression | create_property_graph_tests | g2 | e2 | EDGE | regression | create_property_graph_tests | e2 | + regression | create_property_graph_tests | g2 | t1 | VERTEX | regression | create_property_graph_tests | t1 | + regression | create_property_graph_tests | g2 | t2 | VERTEX | regression | create_property_graph_tests | t2 | + regression | create_property_graph_tests | g2 | t3 | VERTEX | regression | create_property_graph_tests | t3 | + regression | create_property_graph_tests | g3 | t1 | VERTEX | regression | create_property_graph_tests | t1 | + regression | create_property_graph_tests | g3 | t3 | VERTEX | regression | create_property_graph_tests | t3 | + regression | create_property_graph_tests | g4 | e1 | EDGE | regression | create_property_graph_tests | e1 | + regression | create_property_graph_tests | g4 | e2 | EDGE | regression | create_property_graph_tests | e2 | + regression | create_property_graph_tests | g4 | t1 | VERTEX | regression | create_property_graph_tests | t1 | + regression | create_property_graph_tests | g4 | t2 | VERTEX | regression | create_property_graph_tests | t2 | + regression | create_property_graph_tests | g4 | t3 | VERTEX | regression | create_property_graph_tests | t3 | + regression | create_property_graph_tests | g5 | t11 | VERTEX | regression | create_property_graph_tests | t11 | + regression | create_property_graph_tests | g5 | t12 | VERTEX | regression | create_property_graph_tests | t12 | + regression | create_property_graph_tests | g5 | t13 | EDGE | regression | create_property_graph_tests | t13 | +(15 rows) + +SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | column_name | ordinal_position +------------------------+-----------------------------+---------------------+---------------------+-------------+------------------ + regression | create_property_graph_tests | g2 | e1 | a | 1 + regression | create_property_graph_tests | g2 | e1 | i | 2 + regression | create_property_graph_tests | g2 | e2 | a | 1 + regression | create_property_graph_tests | g2 | e2 | x | 2 + regression | create_property_graph_tests | g2 | t1 | a | 1 + regression | create_property_graph_tests | g2 | t2 | i | 1 + regression | create_property_graph_tests | g2 | t3 | x | 1 + regression | create_property_graph_tests | g3 | t1 | a | 1 + regression | create_property_graph_tests | g3 | t3 | x | 1 + regression | create_property_graph_tests | g4 | e1 | a | 1 + regression | create_property_graph_tests | g4 | e1 | i | 2 + regression | create_property_graph_tests | g4 | e2 | a | 1 + regression | create_property_graph_tests | g4 | e2 | x | 2 + regression | create_property_graph_tests | g4 | t1 | a | 1 + regression | create_property_graph_tests | g4 | t2 | i | 1 + regression | create_property_graph_tests | g4 | t3 | x | 1 + regression | create_property_graph_tests | g5 | t11 | a | 1 + regression | create_property_graph_tests | g5 | t12 | b | 1 + regression | create_property_graph_tests | g5 | t13 | c | 1 +(19 rows) + +SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position; + property_graph_catalog | property_graph_schema | property_graph_name | edge_table_alias | vertex_table_alias | edge_end | edge_table_column_name | vertex_table_column_name | ordinal_position +------------------------+-----------------------------+---------------------+------------------+--------------------+-------------+------------------------+--------------------------+------------------ + regression | create_property_graph_tests | g2 | e1 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g2 | e1 | t2 | DESTINATION | i | i | 1 + regression | create_property_graph_tests | g2 | e2 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g2 | e2 | t3 | DESTINATION | x | x | 1 + regression | create_property_graph_tests | g2 | e2 | t3 | DESTINATION | t | y | 2 + regression | create_property_graph_tests | g4 | e1 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g4 | e1 | t2 | DESTINATION | i | i | 1 + regression | create_property_graph_tests | g4 | e2 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g4 | e2 | t3 | DESTINATION | x | x | 1 + regression | create_property_graph_tests | g4 | e2 | t3 | DESTINATION | t | y | 2 + regression | create_property_graph_tests | g5 | t13 | t11 | SOURCE | e | a | 1 + regression | create_property_graph_tests | g5 | t13 | t12 | DESTINATION | e | b | 1 +(12 rows) + +SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | label_name +------------------------+-----------------------------+---------------------+---------------------+------------ + regression | create_property_graph_tests | g2 | e1 | e1 + regression | create_property_graph_tests | g2 | e2 | e2 + regression | create_property_graph_tests | g2 | t1 | t1 + regression | create_property_graph_tests | g2 | t2 | t2 + regression | create_property_graph_tests | g2 | t3 | t3l1 + regression | create_property_graph_tests | g2 | t3 | t3l2 + regression | create_property_graph_tests | g3 | t1 | t1 + regression | create_property_graph_tests | g3 | t3 | t3l1 + regression | create_property_graph_tests | g3 | t3 | t3l2 + regression | create_property_graph_tests | g4 | e1 | e1 + regression | create_property_graph_tests | g4 | e2 | e2 + regression | create_property_graph_tests | g4 | t1 | t1 + regression | create_property_graph_tests | g4 | t2 | t2 + regression | create_property_graph_tests | g4 | t3 | t3l1 + regression | create_property_graph_tests | g4 | t3 | t3l2 + regression | create_property_graph_tests | g5 | t11 | t11 + regression | create_property_graph_tests | g5 | t12 | t12 + regression | create_property_graph_tests | g5 | t13 | t13 +(18 rows) + +SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | property_name | property_expression +------------------------+-----------------------------+---------------------+---------------------+---------------+--------------------- + regression | create_property_graph_tests | g2 | e1 | a | a + regression | create_property_graph_tests | g2 | e1 | i | i + regression | create_property_graph_tests | g2 | e1 | t | t + regression | create_property_graph_tests | g2 | e2 | a | a + regression | create_property_graph_tests | g2 | e2 | t | t + regression | create_property_graph_tests | g2 | e2 | x | x + regression | create_property_graph_tests | g2 | t1 | a | a + regression | create_property_graph_tests | g2 | t1 | b | b + regression | create_property_graph_tests | g2 | t2 | i | i + regression | create_property_graph_tests | g2 | t2 | j | j + regression | create_property_graph_tests | g2 | t2 | k | k + regression | create_property_graph_tests | g2 | t3 | x | x + regression | create_property_graph_tests | g2 | t3 | y | y + regression | create_property_graph_tests | g2 | t3 | z | z + regression | create_property_graph_tests | g3 | t1 | a | a + regression | create_property_graph_tests | g3 | t1 | b | b + regression | create_property_graph_tests | g3 | t3 | x | x + regression | create_property_graph_tests | g3 | t3 | y | y + regression | create_property_graph_tests | g3 | t3 | z | z + regression | create_property_graph_tests | g4 | e1 | a | a + regression | create_property_graph_tests | g4 | e1 | i | i + regression | create_property_graph_tests | g4 | e1 | t | t + regression | create_property_graph_tests | g4 | e2 | a | a + regression | create_property_graph_tests | g4 | e2 | t | t + regression | create_property_graph_tests | g4 | e2 | x | x + regression | create_property_graph_tests | g4 | t2 | i_j | (i + j) + regression | create_property_graph_tests | g4 | t2 | kk | (k * 2) + regression | create_property_graph_tests | g4 | t3 | x | x + regression | create_property_graph_tests | g4 | t3 | yy | y + regression | create_property_graph_tests | g4 | t3 | zz | z + regression | create_property_graph_tests | g5 | t11 | a | a + regression | create_property_graph_tests | g5 | t12 | b | b + regression | create_property_graph_tests | g5 | t13 | c | c + regression | create_property_graph_tests | g5 | t13 | d | d + regression | create_property_graph_tests | g5 | t13 | e | e +(35 rows) + +SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name; + property_graph_catalog | property_graph_schema | property_graph_name | label_name | property_name +------------------------+-----------------------------+---------------------+------------+--------------- + regression | create_property_graph_tests | g2 | e1 | a + regression | create_property_graph_tests | g2 | e1 | i + regression | create_property_graph_tests | g2 | e1 | t + regression | create_property_graph_tests | g2 | e2 | a + regression | create_property_graph_tests | g2 | e2 | t + regression | create_property_graph_tests | g2 | e2 | x + regression | create_property_graph_tests | g2 | t1 | a + regression | create_property_graph_tests | g2 | t1 | b + regression | create_property_graph_tests | g2 | t2 | i + regression | create_property_graph_tests | g2 | t2 | j + regression | create_property_graph_tests | g2 | t2 | k + regression | create_property_graph_tests | g2 | t3l1 | x + regression | create_property_graph_tests | g2 | t3l1 | y + regression | create_property_graph_tests | g2 | t3l1 | z + regression | create_property_graph_tests | g2 | t3l2 | x + regression | create_property_graph_tests | g2 | t3l2 | y + regression | create_property_graph_tests | g2 | t3l2 | z + regression | create_property_graph_tests | g3 | t1 | a + regression | create_property_graph_tests | g3 | t1 | b + regression | create_property_graph_tests | g3 | t3l1 | x + regression | create_property_graph_tests | g3 | t3l1 | y + regression | create_property_graph_tests | g3 | t3l1 | z + regression | create_property_graph_tests | g3 | t3l2 | x + regression | create_property_graph_tests | g3 | t3l2 | y + regression | create_property_graph_tests | g3 | t3l2 | z + regression | create_property_graph_tests | g4 | e1 | a + regression | create_property_graph_tests | g4 | e1 | i + regression | create_property_graph_tests | g4 | e1 | t + regression | create_property_graph_tests | g4 | e2 | a + regression | create_property_graph_tests | g4 | e2 | t + regression | create_property_graph_tests | g4 | e2 | x + regression | create_property_graph_tests | g4 | t2 | i_j + regression | create_property_graph_tests | g4 | t2 | kk + regression | create_property_graph_tests | g4 | t3l1 | x + regression | create_property_graph_tests | g4 | t3l1 | yy + regression | create_property_graph_tests | g4 | t3l2 | x + regression | create_property_graph_tests | g4 | t3l2 | zz + regression | create_property_graph_tests | g5 | t11 | a + regression | create_property_graph_tests | g5 | t12 | b + regression | create_property_graph_tests | g5 | t13 | c + regression | create_property_graph_tests | g5 | t13 | d + regression | create_property_graph_tests | g5 | t13 | e +(42 rows) + +SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name; + property_graph_catalog | property_graph_schema | property_graph_name | label_name +------------------------+-----------------------------+---------------------+------------ + regression | create_property_graph_tests | g2 | e1 + regression | create_property_graph_tests | g2 | e2 + regression | create_property_graph_tests | g2 | t1 + regression | create_property_graph_tests | g2 | t2 + regression | create_property_graph_tests | g2 | t3l1 + regression | create_property_graph_tests | g2 | t3l2 + regression | create_property_graph_tests | g3 | t1 + regression | create_property_graph_tests | g3 | t3l1 + regression | create_property_graph_tests | g3 | t3l2 + regression | create_property_graph_tests | g4 | e1 + regression | create_property_graph_tests | g4 | e2 + regression | create_property_graph_tests | g4 | t1 + regression | create_property_graph_tests | g4 | t2 + regression | create_property_graph_tests | g4 | t3l1 + regression | create_property_graph_tests | g4 | t3l2 + regression | create_property_graph_tests | g5 | t11 + regression | create_property_graph_tests | g5 | t12 + regression | create_property_graph_tests | g5 | t13 +(18 rows) + +SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name; + property_graph_catalog | property_graph_schema | property_graph_name | property_name | data_type | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier +------------------------+-----------------------------+---------------------+---------------+-----------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+---------------------------+--------------------------+------------------------+---------------+--------------+------------+---------------------+---------------- + regression | create_property_graph_tests | g2 | a | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | a + regression | create_property_graph_tests | g2 | b | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | b + regression | create_property_graph_tests | g2 | i | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | i + regression | create_property_graph_tests | g2 | j | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | j + regression | create_property_graph_tests | g2 | k | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | k + regression | create_property_graph_tests | g2 | t | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | t + regression | create_property_graph_tests | g2 | x | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | x + regression | create_property_graph_tests | g2 | y | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | y + regression | create_property_graph_tests | g2 | z | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | z + regression | create_property_graph_tests | g3 | a | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | a + regression | create_property_graph_tests | g3 | b | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | b + regression | create_property_graph_tests | g3 | x | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | x + regression | create_property_graph_tests | g3 | y | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | y + regression | create_property_graph_tests | g3 | z | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | z + regression | create_property_graph_tests | g4 | a | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | a + regression | create_property_graph_tests | g4 | i | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | i + regression | create_property_graph_tests | g4 | i_j | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | i_j + regression | create_property_graph_tests | g4 | kk | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | kk + regression | create_property_graph_tests | g4 | t | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | t + regression | create_property_graph_tests | g4 | x | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | x + regression | create_property_graph_tests | g4 | yy | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | yy + regression | create_property_graph_tests | g4 | zz | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | zz + regression | create_property_graph_tests | g5 | a | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | a + regression | create_property_graph_tests | g5 | b | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | b + regression | create_property_graph_tests | g5 | c | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | c + regression | create_property_graph_tests | g5 | d | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | d + regression | create_property_graph_tests | g5 | e | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | e +(27 rows) + +SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name; + grantor | grantee | property_graph_catalog | property_graph_schema | property_graph_name | privilege_type | is_grantable +---------------------+---------------------+------------------------+-----------------------------+---------------------+----------------+-------------- + regress_graph_user1 | regress_graph_user1 | regression | create_property_graph_tests | g1 | SELECT | YES + regress_graph_user1 | regress_graph_user2 | regression | create_property_graph_tests | g1 | SELECT | NO +(2 rows) + +\a\t +SELECT pg_get_propgraphdef('g2'::regclass); +CREATE PROPERTY GRAPH create_property_graph_tests.g2 + VERTEX TABLES ( + t1 KEY (a) PROPERTIES (a, b), + t2 KEY (i) PROPERTIES (i, j, k), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y, z) LABEL t3l2 PROPERTIES (x, y, z) + ) + EDGE TABLES ( + e1 KEY (a, i) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i) PROPERTIES (a, i, t), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) PROPERTIES (a, t, x) + ) +SELECT pg_get_propgraphdef('g3'::regclass); +CREATE PROPERTY GRAPH create_property_graph_tests.g3 + VERTEX TABLES ( + t1 KEY (a) PROPERTIES (a, b), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y, z) LABEL t3l2 PROPERTIES (x, y, z) + ) +SELECT pg_get_propgraphdef('g4'::regclass); +CREATE PROPERTY GRAPH create_property_graph_tests.g4 + VERTEX TABLES ( + t1 KEY (a) NO PROPERTIES, + t2 KEY (i) PROPERTIES ((i + j) AS i_j, (k * 2) AS kk), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz) + ) + EDGE TABLES ( + e1 KEY (a, i) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i) PROPERTIES (a, i, t), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) PROPERTIES (a, t, x) + ) +SELECT pg_get_propgraphdef('pg_type'::regclass); -- error +ERROR: "pg_type" is not a property graph +\a\t +\dG g1 + List of relations + Schema | Name | Type | Owner +-----------------------------+------+----------------+--------------------- + create_property_graph_tests | g1 | property graph | regress_graph_user1 +(1 row) + +-- TODO +\d g1 +Property graph "create_property_graph_tests.g1" + Column | Type +--------+------ + +\d+ g1 +Property graph "create_property_graph_tests.g1" + Column | Type | Storage +--------+------+--------- +Property graph definition: + CREATE PROPERTY GRAPH create_property_graph_tests.g1 + +DROP TABLE g2; -- error: wrong object type +ERROR: "g2" is not a table +HINT: Use DROP PROPERTY GRAPH to remove a property graph. +DROP PROPERTY GRAPH g1; +DROP PROPERTY GRAPH g1; -- error: does not exist +ERROR: property graph "g1" does not exist +DROP PROPERTY GRAPH IF EXISTS g1; +NOTICE: property graph "g1" does not exist, skipping +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA create_property_graph_tests CASCADE; +DROP ROLE regress_graph_user1, regress_graph_user2; diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out new file mode 100644 index 00000000000..813fc9aa255 --- /dev/null +++ b/src/test/regress/expected/graph_table.out @@ -0,0 +1,484 @@ +CREATE SCHEMA graph_table_tests; +GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC; +SET search_path = graph_table_tests; +CREATE TABLE products ( + product_no integer PRIMARY KEY, + name varchar, + price numeric +); +CREATE TABLE customers ( + customer_id integer PRIMARY KEY, + name varchar, + address varchar +); +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + ordered_when date +); +CREATE TABLE order_items ( + order_items_id integer PRIMARY KEY, + order_id integer REFERENCES orders (order_id), + product_no integer REFERENCES products (product_no), + quantity integer +); +CREATE TABLE customer_orders ( + customer_orders_id integer PRIMARY KEY, + customer_id integer REFERENCES customers (customer_id), + order_id integer REFERENCES orders (order_id) +); +CREATE TABLE wishlists ( + wishlist_id integer PRIMARY KEY, + wishlist_name varchar +); +CREATE TABLE wishlist_items ( + wishlist_items_id integer PRIMARY KEY, + wishlist_id integer REFERENCES wishlists (wishlist_id), + product_no integer REFERENCES products (product_no) +); +CREATE TABLE customer_wishlists ( + customer_wishlist_id integer PRIMARY KEY, + customer_id integer REFERENCES customers (customer_id), + wishlist_id integer REFERENCES wishlists (wishlist_id) +); +CREATE PROPERTY GRAPH myshop + VERTEX TABLES ( + products, + customers, + orders + DEFAULT LABEL + LABEL lists PROPERTIES (order_id as node_id, 'order'::varchar(10) as list_type), + wishlists + DEFAULT LABEL + LABEL lists PROPERTIES (wishlist_id as node_id, 'wishlist'::varchar(10) as list_type) + ) + EDGE TABLES ( + order_items KEY (order_items_id) + SOURCE KEY (order_id) REFERENCES orders (order_id) + DESTINATION KEY (product_no) REFERENCES products (product_no) + DEFAULT LABEL + LABEL list_items PROPERTIES (order_id as link_id, product_no), + wishlist_items KEY (wishlist_items_id) + SOURCE KEY (wishlist_id) REFERENCES wishlists (wishlist_id) + DESTINATION KEY (product_no) REFERENCES products (product_no) + DEFAULT LABEL + LABEL list_items PROPERTIES (wishlist_id as link_id, product_no), + customer_orders KEY (customer_orders_id) + SOURCE KEY (customer_id) REFERENCES customers (customer_id) + DESTINATION KEY (order_id) REFERENCES orders (order_id) + DEFAULT LABEL + LABEL cust_lists PROPERTIES (customer_id, order_id as link_id), + customer_wishlists KEY (customer_wishlist_id) + SOURCE KEY (customer_id) REFERENCES customers (customer_id) + DESTINATION KEY (wishlist_id) REFERENCES wishlists (wishlist_id) + DEFAULT LABEL + LABEL cust_lists PROPERTIES (customer_id, wishlist_id as link_id) + ); +SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +ERROR: relation "xxx" does not exist +LINE 1: SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS custo... + ^ +SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +ERROR: "pg_class" is not a property graph +LINE 1: SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS ... + ^ +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name)); -- error +ERROR: missing FROM-clause entry for table "cx" +LINE 1: ...US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS... + ^ +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name)); -- error +ERROR: property "namex" does not exist +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers|employees WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +ERROR: label "employees" does not exist in property graph "myshop" +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c.name AS customer_name)); -- error +ERROR: syntax error at or near "COLUMNS" +LINE 1: ...mers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c... + ^ +INSERT INTO products VALUES + (1, 'product1', 10), + (2, 'product2', 20), + (3, 'product3', 30); +INSERT INTO customers VALUES + (1, 'customer1', 'US'), + (2, 'customer2', 'CA'), + (3, 'customer3', 'GL'); +INSERT INTO orders VALUES + (1, date '2024-01-01'), + (2, date '2024-01-02'), + (3, date '2024-01-03'); +INSERT INTO wishlists VALUES + (1, 'wishlist1'), + (2, 'wishlist2'), + (3, 'wishlist3'); +INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES + (1, 1, 1, 5), + (2, 1, 2, 10), + (3, 2, 1, 7); +INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES + (1, 1, 1), + (2, 2, 2); +INSERT INTO customer_wishlists (customer_wishlist_id, customer_id, wishlist_id) VALUES + (1, 2, 3), + (2, 3, 1), + (3, 3, 2); +INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES + (1, 1, 2), + (2, 1, 3), + (3, 2, 1), + (4, 3, 1); +-- single element path pattern +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name)); + name +----------- + customer1 + customer2 + customer3 +(3 rows) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name)); + name +----------- + customer1 +(1 row) + +-- graph element specification without label or variable +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[]->(o IS orders) COLUMNS (c.name AS customer_name)); + customer_name +--------------- + customer1 +(1 row) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (c:customers)-[co:customer_orders]->(o:orders WHERE o.ordered_when = date '2024-01-02') COLUMNS (c.name, c.address)); + name | address +-----------+--------- + customer2 | CA +(1 row) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when)); + name | ordered_when +------+-------------- +(0 rows) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when)); + name | ordered_when +-----------+-------------- + customer1 | 01-01-2024 + customer2 | 01-02-2024 +(2 rows) + +SELECT * FROM GRAPH_TABLE (myshop MATCH ( o IS orders ) <- [ IS customer_orders ] - (c IS customers) COLUMNS ( c.name, o.ordered_when)); + name | ordered_when +-----------+-------------- + customer1 | 01-01-2024 + customer2 | 01-02-2024 +(2 rows) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS cust_lists]->(l IS lists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY customer_name, product_name, list_type; + customer_name | product_name | list_type +---------------+--------------+----------- + customer1 | product1 | order + customer1 | product2 | order + customer2 | product1 | order + customer2 | product1 | wishlist + customer3 | product1 | wishlist + customer3 | product2 | wishlist + customer3 | product3 | wishlist +(7 rows) + +-- label disjunction +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name)) ORDER BY customer_name, product_name; + customer_name | product_name +---------------+-------------- + customer1 | product1 + customer1 | product2 + customer2 | product1 + customer2 | product1 + customer3 | product1 + customer3 | product2 + customer3 | product3 +(7 rows) + +-- property not associated with labels queried results in error +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY 1, 2, 3; +ERROR: property "list_type" of element variable "l" not found +-- vertex to vertex connection abbreviation +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1; + name | ordered_when +-----------+-------------- + customer1 | 01-01-2024 + customer2 | 01-02-2024 +(2 rows) + +-- lateral test +CREATE TABLE x1 (a int, b text); +INSERT INTO x1 VALUES (1, 'one'), (2, 'two'); +SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid)); + a | b | customer_name | cid +---+-----+---------------+----- + 1 | one | customer1 | 1 +(1 row) + +DROP TABLE x1; +create table v1 (id int primary key, + vname varchar(10), + vprop1 int, + vprop2 int); +create table v2 (id1 int, + id2 int, + vname varchar(10), + vprop1 int, + vprop2 int); +create table v3 (id int primary key, + vname varchar(10), + vprop1 int, + vprop2 int); +-- edge connecting v1 and v2 +create table e1_2 (id_1 int, + id_2_1 int, + id_2_2 int, + ename varchar(10), + eprop1 int); +-- edge connecting v1 and v3 +create table e1_3 (id_1 int, + id_3 int, + ename varchar(10), + eprop1 int, + primary key (id_1, id_3)); +create table e2_3 (id_2_1 int, + id_2_2 int, + id_3 int, + ename varchar(10), + eprop1 int); +create property graph g1 +vertex tables ( + v1 + label vl1 properties (vname, vprop1) + label l1 properties (vname as elname), -- label shared by vertexes as well as edges + v2 key (id1, id2) + label vl2 properties (vname, vprop2, 'vl2_prop'::varchar(10) as lprop1) + label vl3 properties (vname, vprop1, 'vl2_prop'::varchar(10) as lprop1) + label l1 properties (vname as elname), + v3 + label vl3 properties (vname, vprop1, 'vl3_prop'::varchar(10) as lprop1) + label l1 properties (vname as elname) +) +-- edges with differing number of columns in destination keys +edge tables ( + e1_2 key (id_1, id_2_1, id_2_2) + source key (id_1) references v1 (id) + destination key (id_2_1, id_2_2) references v2 (id1, id2) + label el1 properties (eprop1, ename) + label l1 properties (ename as elname), + e1_3 + source key (id_1) references v1 (id) + destination key (id_3) references v3 (id) + -- order of property names doesn't matter + label el1 properties (ename, eprop1) + label l1 properties (ename as elname), + e2_3 key (id_2_1, id_2_2, id_3) + source key (id_2_1, id_2_2) references v2 (id1, id2) + destination key (id_3) references v3 (id) + -- new property lprop2 not shared by el1 + -- does not share eprop1 from by el1 + label el2 properties (ename, eprop1 * 10 as lprop2) + label l1 properties (ename as elname) +); +insert into v1 values (1, 'v11', 10, 100), + (2, 'v12', 20, 200), + (3, 'v13', 30, 300); +insert into v2 values (1000, 1, 'v21', 1010, 1100), + (1000, 2, 'v22', 1020, 1200), + (1000, 3, 'v23', 1030, 1300); +insert into v3 values (2001, 'v31', 2010, 2100), + (2002, 'v32', 2020, 2200), + (2003, 'v33', 2030, 2300); +insert into e1_2 values (1, 1000, 2, 'e121', 10001), + (2, 1000, 1, 'e122', 10002); +insert into e1_3 values (1, 2003, 'e131', 10003), + (1, 2001, 'e132', 10004); +insert into e2_3 values (1000, 2, 2002, 'e231', 10005); +-- empty element path pattern, counts number of edges in the graph +SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 as one)); + count +------- + 5 +(1 row) + +SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()->() COLUMNS (1 as one)); + count +------- + 5 +(1 row) + +-- Vertex element v2 has label vl3 which exposes property vprop1. But vl3 is +-- not part of label expression. Instead v2 get bound through label vl2 which +-- does not expose vprop1. The GRAPH_TABLE clause project vprop1. +-- +-- TODO: This case fails since catalogs do not associated properties with +-- elements directly. More code is needed to make it work. +SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname, +a.vprop1)); + vname | vprop1 +-------+-------- + v11 | 10 + v12 | 20 + v13 | 30 + v21 | 1010 + v22 | 1020 + v23 | 1030 +(6 rows) + +-- vprop2 is associated with vl2 but not vl3 +select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, b.ename as conn, c.vname as dest, c.lprop1, c.vprop2, c.vprop1)); + src | conn | dest | lprop1 | vprop2 | vprop1 +-----+------+------+----------+--------+-------- + v12 | e122 | v21 | vl2_prop | 1100 | 1010 + v11 | e121 | v22 | vl2_prop | 1200 | 1020 + v11 | e131 | v33 | vl3_prop | | 2030 + v11 | e132 | v31 | vl3_prop | | 2010 +(4 rows) + +-- Errors +-- vl1 is not associated with property vprop2 +select src, src_vprop2, conn, dest from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, a.vprop2 as src_vprop2, b.ename as conn, c.vname as dest)); +ERROR: property "vprop2" of element variable "a" not found +-- property ename is associated with edge labels but not with a vertex label +select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, src.ename as sename)); +ERROR: property "ename" of element variable "src" not found +-- vname is associated vertex labels but not an edge label +select * from graph_table (g1 match (src)-[conn]->(dest) columns (conn.vname as cvname, conn.ename as cename)); +ERROR: property "vname" of element variable "conn" not found +-- el1 is associated with edges but is only label used to qualify vertex +select * from graph_table (g1 match (src is el1)-[conn]->(dest) columns (conn.ename as cename)); +ERROR: can not find label "el1" in property graph "g1" for element type "vertex" +-- el1 is associated with edges but is one of the labels used to qualify vertex +select * from graph_table (g1 match (src is el1 | vl1)-[conn]->(dest) columns (conn.ename as cename)); +ERROR: can not find label "el1" in property graph "g1" for element type "vertex" +-- select all the properties across all the labels associated with a given type +-- of graph element +select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname, src.vprop1 as svp1, src.vprop2 as svp2, src.lprop1 as slp1, dest.vprop1 as dvp1, dest.vprop2 as dvp2, dest.lprop1 as dlp1, conn.eprop1 as cep1, conn.lprop2 as clp2)); + svname | cename | dvname | svp1 | svp2 | slp1 | dvp1 | dvp2 | dlp1 | cep1 | clp2 +--------+--------+--------+------+------+----------+------+------+----------+-------+-------- + v12 | e122 | v21 | 20 | | | 1010 | 1100 | vl2_prop | 10002 | + v11 | e121 | v22 | 10 | | | 1020 | 1200 | vl2_prop | 10001 | + v11 | e131 | v33 | 10 | | | 2030 | | vl3_prop | 10003 | + v11 | e132 | v31 | 10 | | | 2010 | | vl3_prop | 10004 | + v22 | e231 | v32 | 1020 | 1200 | vl2_prop | 2020 | | vl3_prop | | 100050 +(5 rows) + +-- three label disjunction +select * from graph_table (g1 match (src IS vl1 | vl2 | vl3)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname)); + svname | cename | dvname +--------+--------+-------- + v12 | e122 | v21 + v11 | e121 | v22 + v11 | e131 | v33 + v11 | e132 | v31 + v22 | e231 | v32 +(5 rows) + +-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination. +with all_connected_vertices as (select svn, dvn from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svn, dest.vname as dvn))), + all_vertices as (select vn from graph_table (g1 match (vertex) columns (vertex.vname as vn))) +select vn from all_vertices except (select svn from all_connected_vertices union select dvn from all_connected_vertices); + vn +----- + v13 + v23 +(2 rows) + +-- query all connections using a label shared by vertices and edges +select sn, cn, dn from graph_table (g1 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn)); + sn | cn | dn +-----+------+----- + v12 | e122 | v21 + v11 | e121 | v22 + v11 | e131 | v33 + v11 | e132 | v31 + v22 | e231 | v32 +(5 rows) + +-- property graph with some of the elements, labels and properties same as the +-- previous one. Test whether components from the specified property graph are +-- used. +create property graph g2 +vertex tables ( + v1 + label l1 properties ('g2.' || vname as elname), + v2 key (id1, id2) + label l1 properties ('g2.' || vname as elname), + v3 + label l1 properties ('g2.' || vname as elname) +) +edge tables ( + e1_2 key (id_1, id_2_1, id_2_2) + source key (id_1) references v1 (id) + destination key (id_2_1, id_2_2) references v2 (id1, id2) + label l1 properties ('g2.' || ename as elname), + e1_3 + source key (id_1) references v1 (id) + destination key (id_3) references v3 (id) + label l1 properties ('g2.' || ename as elname), + e2_3 key (id_2_1, id_2_2, id_3) + source key (id_2_1, id_2_2) references v2 (id1, id2) + destination key (id_3) references v3 (id) + label l1 properties ('g2.' || ename as elname) +); +select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn)); + sn | cn | dn +--------+---------+-------- + g2.v12 | g2.e122 | g2.v21 + g2.v11 | g2.e121 | g2.v22 + g2.v11 | g2.e131 | g2.v33 + g2.v11 | g2.e132 | g2.v31 + g2.v22 | g2.e231 | g2.v32 +(5 rows) + +CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); +SELECT pg_get_viewdef('customers_us'::regclass); + pg_get_viewdef +------------------------------------------------------------------------------------------------------------------------------------------------------------------- + SELECT customer_name + + FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE ((c.address)::text = 'US'::text))-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); +(1 row) + +-- test view/graph nesting +CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers; +SELECT * FROM customers; + customer_id | name | address +-------------+-----------+--------- + 1 | customer1 | US + 2 | customer2 | CA + 3 | customer3 | GL +(3 rows) + +SELECT * FROM customers_view; + customer_id | name_redacted | address +-------------+---------------+--------- + 1 | redacted1 | US + 2 | redacted2 | CA + 3 | redacted3 | GL +(3 rows) + +CREATE PROPERTY GRAPH myshop2 + VERTEX TABLES ( + products, + customers_view KEY (customer_id) LABEL customers, + orders + ) + EDGE TABLES ( + order_items KEY (order_items_id) + SOURCE KEY (order_id) REFERENCES orders (order_id) + DESTINATION KEY (product_no) REFERENCES products (product_no), + customer_orders KEY (customer_orders_id) + SOURCE KEY (customer_id) REFERENCES customers_view (customer_id) + DESTINATION KEY (order_id) REFERENCES orders (order_id) + ); +CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE (myshop2 MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name_redacted AS customer_name_redacted)); +SELECT * FROM customers_us_redacted; + customer_name_redacted +------------------------ + redacted1 +(1 row) + +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA graph_table_tests CASCADE; diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index fc42d418bf1..432ba471fe3 100644 --- a/src/test/regress/expected/object_address.out +++ b/src/test/regress/expected/object_address.out @@ -34,6 +34,7 @@ CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig(); CREATE POLICY genpol ON addr_nsp.gentable; CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$; +CREATE PROPERTY GRAPH addr_nsp.gengraph; CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw; CREATE USER MAPPING FOR regress_addr_user SERVER "integer"; ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user; @@ -98,7 +99,7 @@ DECLARE BEGIN FOR objtype IN VALUES ('table'), ('index'), ('sequence'), ('view'), - ('materialized view'), ('foreign table'), + ('materialized view'), ('foreign table'), ('property graph'), ('table column'), ('foreign table column'), ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'), ('table constraint'), ('domain constraint'), ('conversion'), ('default value'), @@ -159,6 +160,12 @@ WARNING: error for foreign table,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" d WARNING: error for foreign table,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist WARNING: error for foreign table,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" WARNING: error for foreign table,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" +WARNING: error for property graph,{eins},{}: relation "eins" does not exist +WARNING: error for property graph,{eins},{integer}: relation "eins" does not exist +WARNING: error for property graph,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist +WARNING: error for property graph,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist +WARNING: error for property graph,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" +WARNING: error for property graph,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" WARNING: error for table column,{eins},{}: column name must be qualified WARNING: error for table column,{eins},{integer}: column name must be qualified WARNING: error for table column,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist @@ -398,6 +405,7 @@ WITH objects (type, name, args) AS (VALUES ('view', '{addr_nsp, genview}', '{}'), ('materialized view', '{addr_nsp, genmatview}', '{}'), ('foreign table', '{addr_nsp, genftable}', '{}'), + ('property graph', '{addr_nsp, gengraph}', '{}'), ('table column', '{addr_nsp, gentable, b}', '{}'), ('foreign table column', '{addr_nsp, genftable, a}', '{}'), ('aggregate', '{addr_nsp, genaggr}', '{int4}'), @@ -474,6 +482,7 @@ view|addr_nsp|genview|addr_nsp.genview|t materialized view|addr_nsp|genmatview|addr_nsp.genmatview|t foreign table|addr_nsp|genftable|addr_nsp.genftable|t foreign table column|addr_nsp|genftable|addr_nsp.genftable.a|t +property graph|addr_nsp|gengraph|addr_nsp.gengraph|t role|NULL|regress_addr_user|regress_addr_user|t server|NULL|addr_fserv|addr_fserv|t user mapping|NULL|NULL|regress_addr_user on server integer|t @@ -518,7 +527,7 @@ DROP PUBLICATION addr_pub; DROP PUBLICATION addr_pub_schema; DROP SUBSCRIPTION regress_addr_sub; DROP SCHEMA addr_nsp CASCADE; -NOTICE: drop cascades to 14 other objects +NOTICE: drop cascades to 15 other objects DETAIL: drop cascades to text search dictionary addr_ts_dict drop cascades to text search configuration addr_ts_conf drop cascades to text search template addr_ts_temp @@ -533,6 +542,7 @@ drop cascades to function genaggr(integer) drop cascades to type gendomain drop cascades to function trig() drop cascades to function proc(integer) +drop cascades to property graph gengraph DROP OWNED BY regress_addr_user; DROP USER regress_addr_user; -- diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index 215eb899be3..3b41fc7ba04 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -266,3 +266,14 @@ NOTICE: checking pg_subscription {subdbid} => pg_database {oid} NOTICE: checking pg_subscription {subowner} => pg_authid {oid} NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid} NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid} +NOTICE: checking pg_propgraph_element {pgepgid} => pg_class {oid} +NOTICE: checking pg_propgraph_element {pgerelid} => pg_class {oid} +NOTICE: checking pg_propgraph_element {pgesrcvertexid} => pg_propgraph_element {oid} +NOTICE: checking pg_propgraph_element {pgedestvertexid} => pg_propgraph_element {oid} +NOTICE: checking pg_propgraph_element_label {pgellabelid} => pg_propgraph_label {oid} +NOTICE: checking pg_propgraph_element_label {pgelelid} => pg_propgraph_element {oid} +NOTICE: checking pg_propgraph_label {pglpgid} => pg_class {oid} +NOTICE: checking pg_propgraph_label_property {plppropid} => pg_propgraph_property {oid} +NOTICE: checking pg_propgraph_label_property {plpellabelid} => pg_propgraph_element_label {oid} +NOTICE: checking pg_propgraph_property {pgppgid} => pg_class {oid} +NOTICE: checking pg_propgraph_property {pgptypid} => pg_type {oid} diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 969ced994f4..6bef6b34a70 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -48,7 +48,7 @@ test: create_index create_index_spgist create_view index_including index_includi # ---------- # Another group of parallel tests # ---------- -test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse +test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse create_property_graph # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * @@ -78,7 +78,7 @@ test: brin_bloom brin_multi # psql depends on create_am # amutils depends on geometry, create_index_spgist, hash_index, brin # ---------- -test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role +test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.utf8 collate.icu.utf8 incremental_sort create_role graph_table # collate.linux.utf8 and collate.icu.utf8 tests cannot be run in parallel with each other test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8 collate.windows.win1252 diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql index de58d268d31..067d5c2b490 100644 --- a/src/test/regress/sql/alter_generic.sql +++ b/src/test/regress/sql/alter_generic.sql @@ -456,6 +456,40 @@ CREATE OPERATOR FAMILY alt_opf19 USING btree; ALTER OPERATOR FAMILY alt_opf19 USING btree DROP FUNCTION 5 (int4, int4); DROP OPERATOR FAMILY alt_opf19 USING btree; +-- +-- Property Graph +-- +SET SESSION AUTHORIZATION regress_alter_generic_user1; +CREATE PROPERTY GRAPH alt_graph1; +CREATE PROPERTY GRAPH alt_graph2; +CREATE PROPERTY GRAPH alt_graph3; + +ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph2; -- failed (name conflict) +ALTER PROPERTY GRAPH alt_graph1 RENAME TO alt_graph4; -- OK +ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user2; -- failed (no role membership) +ALTER PROPERTY GRAPH alt_graph2 OWNER TO regress_alter_generic_user3; -- OK +ALTER PROPERTY GRAPH alt_graph4 SET SCHEMA alt_nsp2; -- OK +ALTER PROPERTY GRAPH alt_nsp2.alt_graph4 RENAME TO alt_graph2; -- OK +ALTER PROPERTY GRAPH alt_graph2 SET SCHEMA alt_nsp2; -- failed (name conflict) + +SET SESSION AUTHORIZATION regress_alter_generic_user2; +CREATE PROPERTY GRAPH alt_graph5; + +ALTER PROPERTY GRAPH alt_graph3 RENAME TO alt_graph5; -- failed (not owner) +ALTER PROPERTY GRAPH alt_graph5 RENAME TO alt_graph6; -- OK +ALTER PROPERTY GRAPH alt_graph3 OWNER TO regress_alter_generic_user2; -- failed (not owner) +ALTER PROPERTY GRAPH alt_graph6 OWNER TO regress_alter_generic_user3; -- failed (no role membership) +ALTER PROPERTY GRAPH alt_graph3 SET SCHEMA alt_nsp2; -- failed (not owner) + +RESET SESSION AUTHORIZATION; + +SELECT nspname, relname, rolname + FROM pg_class c, pg_namespace n, pg_authid a + WHERE c.relnamespace = n.oid AND c.relowner = a.oid + AND n.nspname in ('alt_nsp1', 'alt_nsp2') + AND c.relkind = 'g' + ORDER BY nspname, relname; + -- -- Statistics -- diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql new file mode 100644 index 00000000000..4f9b5c0349b --- /dev/null +++ b/src/test/regress/sql/create_property_graph.sql @@ -0,0 +1,190 @@ +CREATE SCHEMA create_property_graph_tests; +GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC; +SET search_path = create_property_graph_tests; + +CREATE ROLE regress_graph_user1; +CREATE ROLE regress_graph_user2; + +CREATE PROPERTY GRAPH g1; + +COMMENT ON PROPERTY GRAPH g1 IS 'a graph'; + +CREATE PROPERTY GRAPH g1; -- error: duplicate + +CREATE TABLE t1 (a int, b text); +CREATE TABLE t2 (i int PRIMARY KEY, j int, k int); +CREATE TABLE t3 (x int, y text, z text); + +CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i)); +CREATE TABLE e2 (a int, x int, t text); + +CREATE PROPERTY GRAPH g2 + VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); + +-- test dependencies/object descriptions + +DROP TABLE t1; -- fail +ALTER TABLE t1 DROP COLUMN b; -- non-key column; fail +ALTER TABLE t1 DROP COLUMN a; -- key column; fail + +-- like g2 but assembled with ALTER +CREATE PROPERTY GRAPH g3; +ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL); +ALTER PROPERTY GRAPH g3 + ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1) + ADD EDGE TABLES ( + e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS ADD LABEL t3l3 PROPERTIES ALL COLUMNS; +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x; -- error +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3; +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2); -- fail (TODO: dubious error message) +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE; +ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2); + +CREATE PROPERTY GRAPH g4 + VERTEX TABLES ( + t1 KEY (a) NO PROPERTIES, + t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz) + ) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i) + PROPERTIES ALL COLUMNS, + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + PROPERTIES ALL COLUMNS + ); + +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk); +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k); + +CREATE TABLE t11 (a int PRIMARY KEY); +CREATE TABLE t12 (b int PRIMARY KEY); +CREATE TABLE t13 ( + c int PRIMARY KEY, + d int REFERENCES t11, + e int REFERENCES t12 +); + +CREATE PROPERTY GRAPH g5 + VERTEX TABLES (t11, t12) + EDGE TABLES (t13 SOURCE t11 DESTINATION t12); + +SELECT pg_get_propgraphdef('g5'::regclass); + +-- error cases +CREATE PROPERTY GRAPH gx VERTEX TABLES (xx, yy); +CREATE PROPERTY GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a)); +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION t2 + ); +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION tx + ); +COMMENT ON PROPERTY GRAPH gx IS 'not a graph'; +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 KEY (a), t2) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION t2 -- no foreign keys + ); +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL foo PROPERTIES (a + 1 AS aa) + LABEL bar PROPERTIES (1 + a AS aa) -- expression mismatch + ); +ALTER PROPERTY GRAPH g2 + ADD VERTEX TABLES ( + t1 AS t1x KEY (a) LABEL foo PROPERTIES (a + 1 AS aa) + LABEL bar PROPERTIES (1 + a AS aa) -- expression mismatch + ); +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) PROPERTIES (b AS p1), + t2 PROPERTIES (k AS p1) -- type mismatch + ); +ALTER PROPERTY GRAPH g2 ALTER VERTEX TABLE t1 ADD LABEL foo PROPERTIES (b AS k); -- type mismatch + +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL l1 PROPERTIES (a, a AS aa), + t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS b, k) -- mismatching number of properties on label + ); +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL l1 PROPERTIES (a, b), + t2 KEY (i) LABEL l1 PROPERTIES (i AS a) -- mismatching number of properties on label + ); +CREATE PROPERTY GRAPH gx + VERTEX TABLES ( + t1 KEY (a) LABEL l1 PROPERTIES (a, b), + t2 KEY (i) LABEL l1 PROPERTIES (i AS a, j AS j) -- mismatching property names on label + ); +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS yy, b AS zz); -- mismatching number of properties on label +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x, b AS zz); -- mismatching property names on label +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t1 ADD LABEL t3l1 PROPERTIES (a AS x); -- mismatching number of properties on label + + +ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1; +SET ROLE regress_graph_user1; +GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2; +GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2; -- fail +RESET ROLE; + + +-- information schema + +SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name; +SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias; +SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position; +SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position; +SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name; +SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name; +SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name; +SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name; +SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name; +SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name; + + +\a\t +SELECT pg_get_propgraphdef('g2'::regclass); +SELECT pg_get_propgraphdef('g3'::regclass); +SELECT pg_get_propgraphdef('g4'::regclass); + +SELECT pg_get_propgraphdef('pg_type'::regclass); -- error +\a\t + +\dG g1 + +-- TODO +\d g1 +\d+ g1 + +DROP TABLE g2; -- error: wrong object type + +DROP PROPERTY GRAPH g1; + +DROP PROPERTY GRAPH g1; -- error: does not exist + +DROP PROPERTY GRAPH IF EXISTS g1; + +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA create_property_graph_tests CASCADE; + +DROP ROLE regress_graph_user1, regress_graph_user2; diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql new file mode 100644 index 00000000000..2b43b136eac --- /dev/null +++ b/src/test/regress/sql/graph_table.sql @@ -0,0 +1,336 @@ +CREATE SCHEMA graph_table_tests; +GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC; +SET search_path = graph_table_tests; + +CREATE TABLE products ( + product_no integer PRIMARY KEY, + name varchar, + price numeric +); + +CREATE TABLE customers ( + customer_id integer PRIMARY KEY, + name varchar, + address varchar +); + +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + ordered_when date +); + +CREATE TABLE order_items ( + order_items_id integer PRIMARY KEY, + order_id integer REFERENCES orders (order_id), + product_no integer REFERENCES products (product_no), + quantity integer +); + +CREATE TABLE customer_orders ( + customer_orders_id integer PRIMARY KEY, + customer_id integer REFERENCES customers (customer_id), + order_id integer REFERENCES orders (order_id) +); + +CREATE TABLE wishlists ( + wishlist_id integer PRIMARY KEY, + wishlist_name varchar +); + +CREATE TABLE wishlist_items ( + wishlist_items_id integer PRIMARY KEY, + wishlist_id integer REFERENCES wishlists (wishlist_id), + product_no integer REFERENCES products (product_no) +); + +CREATE TABLE customer_wishlists ( + customer_wishlist_id integer PRIMARY KEY, + customer_id integer REFERENCES customers (customer_id), + wishlist_id integer REFERENCES wishlists (wishlist_id) +); + +CREATE PROPERTY GRAPH myshop + VERTEX TABLES ( + products, + customers, + orders + DEFAULT LABEL + LABEL lists PROPERTIES (order_id as node_id, 'order'::varchar(10) as list_type), + wishlists + DEFAULT LABEL + LABEL lists PROPERTIES (wishlist_id as node_id, 'wishlist'::varchar(10) as list_type) + ) + EDGE TABLES ( + order_items KEY (order_items_id) + SOURCE KEY (order_id) REFERENCES orders (order_id) + DESTINATION KEY (product_no) REFERENCES products (product_no) + DEFAULT LABEL + LABEL list_items PROPERTIES (order_id as link_id, product_no), + wishlist_items KEY (wishlist_items_id) + SOURCE KEY (wishlist_id) REFERENCES wishlists (wishlist_id) + DESTINATION KEY (product_no) REFERENCES products (product_no) + DEFAULT LABEL + LABEL list_items PROPERTIES (wishlist_id as link_id, product_no), + customer_orders KEY (customer_orders_id) + SOURCE KEY (customer_id) REFERENCES customers (customer_id) + DESTINATION KEY (order_id) REFERENCES orders (order_id) + DEFAULT LABEL + LABEL cust_lists PROPERTIES (customer_id, order_id as link_id), + customer_wishlists KEY (customer_wishlist_id) + SOURCE KEY (customer_id) REFERENCES customers (customer_id) + DESTINATION KEY (wishlist_id) REFERENCES wishlists (wishlist_id) + DEFAULT LABEL + LABEL cust_lists PROPERTIES (customer_id, wishlist_id as link_id) + ); + +SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers|employees WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders] COLUMNS (c.name AS customer_name)); -- error + +INSERT INTO products VALUES + (1, 'product1', 10), + (2, 'product2', 20), + (3, 'product3', 30); +INSERT INTO customers VALUES + (1, 'customer1', 'US'), + (2, 'customer2', 'CA'), + (3, 'customer3', 'GL'); +INSERT INTO orders VALUES + (1, date '2024-01-01'), + (2, date '2024-01-02'), + (3, date '2024-01-03'); +INSERT INTO wishlists VALUES + (1, 'wishlist1'), + (2, 'wishlist2'), + (3, 'wishlist3'); +INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES + (1, 1, 1, 5), + (2, 1, 2, 10), + (3, 2, 1, 7); +INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES + (1, 1, 1), + (2, 2, 2); +INSERT INTO customer_wishlists (customer_wishlist_id, customer_id, wishlist_id) VALUES + (1, 2, 3), + (2, 3, 1), + (3, 3, 2); +INSERT INTO wishlist_items (wishlist_items_id, wishlist_id, product_no) VALUES + (1, 1, 2), + (2, 1, 3), + (3, 2, 1), + (4, 3, 1); + +-- single element path pattern +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers) COLUMNS (c.name)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name)); +-- graph element specification without label or variable +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[]->(o IS orders) COLUMNS (c.name AS customer_name)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (c:customers)-[co:customer_orders]->(o:orders WHERE o.ordered_when = date '2024-01-02') COLUMNS (c.name, c.address)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when)); +SELECT * FROM GRAPH_TABLE (myshop MATCH ( o IS orders ) <- [ IS customer_orders ] - (c IS customers) COLUMNS ( c.name, o.ordered_when)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS cust_lists]->(l IS lists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY customer_name, product_name, list_type; +-- label disjunction +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name)) ORDER BY customer_name, product_name; +-- property not associated with labels queried results in error +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name as product_name, l.list_type)) ORDER BY 1, 2, 3; +-- vertex to vertex connection abbreviation +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers)->(o IS orders) COLUMNS (c.name, o.ordered_when)) ORDER BY 1; + +-- lateral test +CREATE TABLE x1 (a int, b text); +INSERT INTO x1 VALUES (1, 'one'), (2, 'two'); +SELECT * FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid)); +DROP TABLE x1; + + +create table v1 (id int primary key, + vname varchar(10), + vprop1 int, + vprop2 int); + +create table v2 (id1 int, + id2 int, + vname varchar(10), + vprop1 int, + vprop2 int); + +create table v3 (id int primary key, + vname varchar(10), + vprop1 int, + vprop2 int); + +-- edge connecting v1 and v2 +create table e1_2 (id_1 int, + id_2_1 int, + id_2_2 int, + ename varchar(10), + eprop1 int); + +-- edge connecting v1 and v3 +create table e1_3 (id_1 int, + id_3 int, + ename varchar(10), + eprop1 int, + primary key (id_1, id_3)); + +create table e2_3 (id_2_1 int, + id_2_2 int, + id_3 int, + ename varchar(10), + eprop1 int); + +create property graph g1 +vertex tables ( + v1 + label vl1 properties (vname, vprop1) + label l1 properties (vname as elname), -- label shared by vertexes as well as edges + v2 key (id1, id2) + label vl2 properties (vname, vprop2, 'vl2_prop'::varchar(10) as lprop1) + label vl3 properties (vname, vprop1, 'vl2_prop'::varchar(10) as lprop1) + label l1 properties (vname as elname), + v3 + label vl3 properties (vname, vprop1, 'vl3_prop'::varchar(10) as lprop1) + label l1 properties (vname as elname) +) +-- edges with differing number of columns in destination keys +edge tables ( + e1_2 key (id_1, id_2_1, id_2_2) + source key (id_1) references v1 (id) + destination key (id_2_1, id_2_2) references v2 (id1, id2) + label el1 properties (eprop1, ename) + label l1 properties (ename as elname), + e1_3 + source key (id_1) references v1 (id) + destination key (id_3) references v3 (id) + -- order of property names doesn't matter + label el1 properties (ename, eprop1) + label l1 properties (ename as elname), + e2_3 key (id_2_1, id_2_2, id_3) + source key (id_2_1, id_2_2) references v2 (id1, id2) + destination key (id_3) references v3 (id) + -- new property lprop2 not shared by el1 + -- does not share eprop1 from by el1 + label el2 properties (ename, eprop1 * 10 as lprop2) + label l1 properties (ename as elname) +); + +insert into v1 values (1, 'v11', 10, 100), + (2, 'v12', 20, 200), + (3, 'v13', 30, 300); + +insert into v2 values (1000, 1, 'v21', 1010, 1100), + (1000, 2, 'v22', 1020, 1200), + (1000, 3, 'v23', 1030, 1300); + +insert into v3 values (2001, 'v31', 2010, 2100), + (2002, 'v32', 2020, 2200), + (2003, 'v33', 2030, 2300); + +insert into e1_2 values (1, 1000, 2, 'e121', 10001), + (2, 1000, 1, 'e122', 10002); + +insert into e1_3 values (1, 2003, 'e131', 10003), + (1, 2001, 'e132', 10004); +insert into e2_3 values (1000, 2, 2002, 'e231', 10005); + +-- empty element path pattern, counts number of edges in the graph +SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-[]->() COLUMNS (1 as one)); +SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()->() COLUMNS (1 as one)); +-- Vertex element v2 has label vl3 which exposes property vprop1. But vl3 is +-- not part of label expression. Instead v2 get bound through label vl2 which +-- does not expose vprop1. The GRAPH_TABLE clause project vprop1. +-- +-- TODO: This case fails since catalogs do not associated properties with +-- elements directly. More code is needed to make it work. +SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 | vl2) COLUMNS (a.vname, +a.vprop1)); +-- vprop2 is associated with vl2 but not vl3 +select src, conn, dest, lprop1, vprop2, vprop1 from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, b.ename as conn, c.vname as dest, c.lprop1, c.vprop2, c.vprop1)); + +-- Errors +-- vl1 is not associated with property vprop2 +select src, src_vprop2, conn, dest from graph_table (g1 match (a is vl1)-[b is el1]->(c is vl2 | vl3) columns (a.vname as src, a.vprop2 as src_vprop2, b.ename as conn, c.vname as dest)); +-- property ename is associated with edge labels but not with a vertex label +select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, src.ename as sename)); +-- vname is associated vertex labels but not an edge label +select * from graph_table (g1 match (src)-[conn]->(dest) columns (conn.vname as cvname, conn.ename as cename)); +-- el1 is associated with edges but is only label used to qualify vertex +select * from graph_table (g1 match (src is el1)-[conn]->(dest) columns (conn.ename as cename)); +-- el1 is associated with edges but is one of the labels used to qualify vertex +select * from graph_table (g1 match (src is el1 | vl1)-[conn]->(dest) columns (conn.ename as cename)); + +-- select all the properties across all the labels associated with a given type +-- of graph element +select * from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname, src.vprop1 as svp1, src.vprop2 as svp2, src.lprop1 as slp1, dest.vprop1 as dvp1, dest.vprop2 as dvp2, dest.lprop1 as dlp1, conn.eprop1 as cep1, conn.lprop2 as clp2)); +-- three label disjunction +select * from graph_table (g1 match (src IS vl1 | vl2 | vl3)-[conn]->(dest) columns (src.vname as svname, conn.ename as cename, dest.vname as dvname)); +-- graph'ical query: find a vertex which is not connected to any other vertex as a source or a destination. +with all_connected_vertices as (select svn, dvn from graph_table (g1 match (src)-[conn]->(dest) columns (src.vname as svn, dest.vname as dvn))), + all_vertices as (select vn from graph_table (g1 match (vertex) columns (vertex.vname as vn))) +select vn from all_vertices except (select svn from all_connected_vertices union select dvn from all_connected_vertices); +-- query all connections using a label shared by vertices and edges +select sn, cn, dn from graph_table (g1 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn)); + +-- property graph with some of the elements, labels and properties same as the +-- previous one. Test whether components from the specified property graph are +-- used. +create property graph g2 +vertex tables ( + v1 + label l1 properties ('g2.' || vname as elname), + v2 key (id1, id2) + label l1 properties ('g2.' || vname as elname), + v3 + label l1 properties ('g2.' || vname as elname) +) +edge tables ( + e1_2 key (id_1, id_2_1, id_2_2) + source key (id_1) references v1 (id) + destination key (id_2_1, id_2_2) references v2 (id1, id2) + label l1 properties ('g2.' || ename as elname), + e1_3 + source key (id_1) references v1 (id) + destination key (id_3) references v3 (id) + label l1 properties ('g2.' || ename as elname), + e2_3 key (id_2_1, id_2_2, id_3) + source key (id_2_1, id_2_2) references v2 (id1, id2) + destination key (id_3) references v3 (id) + label l1 properties ('g2.' || ename as elname) +); +select sn, cn, dn from graph_table (g2 match (src : l1)-[conn : l1]->(dest : l1) columns (src.elname as sn, conn.elname as cn, dest.elname as dn)); + +CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); + +SELECT pg_get_viewdef('customers_us'::regclass); + +-- test view/graph nesting + +CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers; +SELECT * FROM customers; +SELECT * FROM customers_view; + +CREATE PROPERTY GRAPH myshop2 + VERTEX TABLES ( + products, + customers_view KEY (customer_id) LABEL customers, + orders + ) + EDGE TABLES ( + order_items KEY (order_items_id) + SOURCE KEY (order_id) REFERENCES orders (order_id) + DESTINATION KEY (product_no) REFERENCES products (product_no), + customer_orders KEY (customer_orders_id) + SOURCE KEY (customer_id) REFERENCES customers_view (customer_id) + DESTINATION KEY (order_id) REFERENCES orders (order_id) + ); + +CREATE VIEW customers_us_redacted AS SELECT * FROM GRAPH_TABLE (myshop2 MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name_redacted AS customer_name_redacted)); + +SELECT * FROM customers_us_redacted; + +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA graph_table_tests CASCADE; diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql index 1a6c61f49d5..93f9f9c704f 100644 --- a/src/test/regress/sql/object_address.sql +++ b/src/test/regress/sql/object_address.sql @@ -37,6 +37,7 @@ CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig(); CREATE POLICY genpol ON addr_nsp.gentable; CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$; +CREATE PROPERTY GRAPH addr_nsp.gengraph; CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw; CREATE USER MAPPING FOR regress_addr_user SERVER "integer"; ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user; @@ -90,7 +91,7 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable; BEGIN FOR objtype IN VALUES ('table'), ('index'), ('sequence'), ('view'), - ('materialized view'), ('foreign table'), + ('materialized view'), ('foreign table'), ('property graph'), ('table column'), ('foreign table column'), ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'), ('table constraint'), ('domain constraint'), ('conversion'), ('default value'), @@ -163,6 +164,7 @@ CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable; ('view', '{addr_nsp, genview}', '{}'), ('materialized view', '{addr_nsp, genmatview}', '{}'), ('foreign table', '{addr_nsp, genftable}', '{}'), + ('property graph', '{addr_nsp, gengraph}', '{}'), ('table column', '{addr_nsp, gentable, b}', '{}'), ('foreign table column', '{addr_nsp, genftable, a}', '{}'), ('aggregate', '{addr_nsp, genaggr}', '{int4}'), diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 61ad417cde6..7097cd58ffd 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -4152,3 +4152,24 @@ yyscan_t z_stream z_streamp zic_t + +# TODO +AlterPropGraphElementKind +AlterPropGraphStmt +CreatePropGraphStmt +FormData_pg_propgraph_element +FormData_pg_propgraph_element_label +FormData_pg_propgraph_label +FormData_pg_propgraph_label_property +FormData_pg_propgraph_property +GraphElementPattern +GraphElementPatternKind +GraphLabelRef +GraphPattern +GraphPropertyRef +GraphTableParseState +PropGraphEdge +PropGraphLabelAndProperties +PropGraphProperties +PropGraphVertex +RangeGraphTable base-commit: 3e53492aa7084bceaa92757c90e067d79768797e -- 2.45.2