From 22673830c106db994093b0c764f62459bb7e5f56 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 16 Feb 2024 15:50:43 +0100 Subject: [PATCH v0] WIP: SQL Property Graph Queries (SQL/PGQ) Implementation of SQL property graph queries, according to SQL/PGQ standard (ISO 9075-16:2023). --- doc/src/sgml/catalogs.sgml | 48 +- doc/src/sgml/ddl.sgml | 229 +++- doc/src/sgml/features.sgml | 4 +- doc/src/sgml/func.sgml | 15 + doc/src/sgml/information_schema.sgml | 80 ++ .../sgml/keywords/sql2023-16-nonreserved.txt | 27 + doc/src/sgml/keywords/sql2023-16-reserved.txt | 12 + doc/src/sgml/queries.sgml | 156 +++ doc/src/sgml/ref/allfiles.sgml | 3 + doc/src/sgml/ref/alter_extension.sgml | 3 +- doc/src/sgml/ref/alter_property_graph.sgml | 150 +++ doc/src/sgml/ref/comment.sgml | 1 + doc/src/sgml/ref/create_property_graph.sgml | 232 ++++ doc/src/sgml/ref/drop_property_graph.sgml | 111 ++ doc/src/sgml/ref/grant.sgml | 7 +- doc/src/sgml/ref/psql-ref.sgml | 13 +- doc/src/sgml/ref/revoke.sgml | 7 + doc/src/sgml/ref/security_label.sgml | 1 + doc/src/sgml/ref/select.sgml | 43 + doc/src/sgml/reference.sgml | 3 + src/backend/catalog/Makefile | 5 +- src/backend/catalog/aclchk.c | 24 + src/backend/catalog/dependency.c | 20 + src/backend/catalog/information_schema.sql | 363 ++++++ src/backend/catalog/objectaddress.c | 140 +++ src/backend/catalog/pg_class.c | 2 + src/backend/catalog/sql_features.txt | 100 ++ src/backend/commands/Makefile | 1 + src/backend/commands/alter.c | 31 +- src/backend/commands/dropcmds.c | 1 + src/backend/commands/event_trigger.c | 6 + src/backend/commands/meson.build | 1 + src/backend/commands/propgraphcmds.c | 1001 +++++++++++++++++ src/backend/commands/seclabel.c | 1 + src/backend/commands/tablecmds.c | 14 + src/backend/executor/execMain.c | 2 +- src/backend/nodes/nodeFuncs.c | 72 ++ src/backend/nodes/outfuncs.c | 5 + src/backend/nodes/queryjumblefuncs.c | 5 + src/backend/nodes/readfuncs.c | 5 + src/backend/optimizer/path/allpaths.c | 4 + src/backend/optimizer/prep/prepjointree.c | 8 + src/backend/parser/Makefile | 1 + src/backend/parser/gram.y | 693 +++++++++++- src/backend/parser/meson.build | 1 + src/backend/parser/parse_clause.c | 85 ++ src/backend/parser/parse_collate.c | 7 + src/backend/parser/parse_graphtable.c | 251 +++++ src/backend/parser/parse_relation.c | 94 ++ src/backend/parser/parse_target.c | 5 + src/backend/parser/scan.l | 48 + src/backend/rewrite/Makefile | 1 + src/backend/rewrite/meson.build | 1 + src/backend/rewrite/rewriteGraphTable.c | 422 +++++++ src/backend/rewrite/rewriteHandler.c | 12 + src/backend/tcop/utility.c | 34 + src/backend/utils/adt/ruleutils.c | 502 +++++++++ src/bin/pg_dump/common.c | 3 +- src/bin/pg_dump/pg_backup_archiver.c | 1 + src/bin/pg_dump/pg_dump.c | 67 +- src/bin/pg_dump/t/002_pg_dump.pl | 11 + src/bin/psql/command.c | 3 +- src/bin/psql/describe.c | 43 +- src/bin/psql/help.c | 1 + src/bin/psql/tab-complete.c | 65 +- src/fe_utils/psqlscan.l | 8 + src/include/catalog/dependency.h | 3 + src/include/catalog/meson.build | 3 + src/include/catalog/pg_class.h | 1 + src/include/catalog/pg_proc.dat | 3 + src/include/catalog/pg_propgraph_element.h | 103 ++ src/include/catalog/pg_propgraph_label.h | 59 + src/include/catalog/pg_propgraph_property.h | 74 ++ src/include/commands/propgraphcmds.h | 23 + src/include/nodes/parsenodes.h | 135 +++ src/include/nodes/primnodes.h | 22 + src/include/parser/kwlist.h | 10 + src/include/parser/parse_graphtable.h | 31 + src/include/parser/parse_relation.h | 8 + src/include/rewrite/rewriteGraphTable.h | 21 + src/include/tcop/cmdtaglist.h | 3 + src/include/utils/acl.h | 1 + src/pl/plpgsql/src/pl_gram.y | 1 + src/test/regress/expected/alter_generic.out | 51 +- .../expected/create_property_graph.out | 370 ++++++ src/test/regress/expected/graph_table.out | 122 ++ src/test/regress/expected/object_address.out | 14 +- src/test/regress/expected/oidjoins.out | 9 + src/test/regress/parallel_schedule | 4 +- src/test/regress/sql/alter_generic.sql | 34 + .../regress/sql/create_property_graph.sql | 130 +++ src/test/regress/sql/graph_table.sql | 96 ++ src/test/regress/sql/object_address.sql | 4 +- src/tools/pgindent/typedefs.list | 19 + 94 files changed, 6620 insertions(+), 49 deletions(-) create mode 100644 doc/src/sgml/keywords/sql2023-16-nonreserved.txt create mode 100644 doc/src/sgml/keywords/sql2023-16-reserved.txt create mode 100644 doc/src/sgml/ref/alter_property_graph.sgml create mode 100644 doc/src/sgml/ref/create_property_graph.sgml create mode 100644 doc/src/sgml/ref/drop_property_graph.sgml create mode 100644 src/backend/commands/propgraphcmds.c create mode 100644 src/backend/parser/parse_graphtable.c create mode 100644 src/backend/rewrite/rewriteGraphTable.c create mode 100644 src/include/catalog/pg_propgraph_element.h create mode 100644 src/include/catalog/pg_propgraph_label.h create mode 100644 src/include/catalog/pg_propgraph_property.h create mode 100644 src/include/commands/propgraphcmds.h create mode 100644 src/include/parser/parse_graphtable.h create mode 100644 src/include/rewrite/rewriteGraphTable.h create mode 100644 src/test/regress/expected/create_property_graph.out create mode 100644 src/test/regress/expected/graph_table.out create mode 100644 src/test/regress/sql/create_property_graph.sql create mode 100644 src/test/regress/sql/graph_table.sql diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 880f717b103..675b8b1ba8e 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -240,6 +240,21 @@ System Catalogs functions and procedures + + pg_propgraph_element + property graph elements (vertices and edges) + + + + pg_propgraph_label + property graph labels + + + + pg_propgraph_property + property graph properties + + pg_publication publications for logical replication @@ -2115,7 +2130,8 @@ <structname>pg_class</structname> Columns c = composite type, f = foreign table, p = partitioned table, - I = partitioned index + I = partitioned index, + g = property graph @@ -6258,6 +6274,36 @@ <structname>pg_proc</structname> Columns + + <structname>pg_propgraph_element</structname> + + + pg_propgraph_element + + + TODO + + + + <structname>pg_propgraph_label</structname> + + + pg_propgraph_label + + + TODO + + + + <structname>pg_propgraph_property</structname> + + + pg_propgraph_property + + + TODO + + <structname>pg_publication</structname> diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 9d7e2c756be..7333cf57e34 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1951,7 +1951,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, @@ -5269,6 +5269,233 @@ 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 noun + 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 cf3de80394e..237136e2aec 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -25000,6 +25000,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 9e66be4e83d..02f18eedbd7 100644 --- a/doc/src/sgml/information_schema.sgml +++ b/doc/src/sgml/information_schema.sgml @@ -4171,6 +4171,86 @@ <structname>parameters</structname> Columns + + <literal>pg_edge_table_components</literal> + + + TODO + + + + + <literal>pg_element_table_key_columns</literal> + + + TODO + + + + + <literal>pg_element_table_labels</literal> + + + TODO + + + + + <literal>pg_element_table_properties</literal> + + + TODO + + + + + <literal>pg_element_tables</literal> + + + TODO + + + + + <literal>pg_element_label_properties</literal> + + + TODO + + + + + <literal>pg_element_labels</literal> + + + TODO + + + + + <literal>pg_property_data_types</literal> + + + TODO + + + + + <literal>pg_property_graph_privileges</literal> + + + TODO + + + + + <literal>property_graphs</literal> + + + TODO + + + <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 648b283b064..5b088331c6e 100644 --- a/doc/src/sgml/queries.sgml +++ b/doc/src/sgml/queries.sgml @@ -2744,4 +2744,160 @@ 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. + + + + + 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 4a42999b183..990d2b1af3c 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..e4e56ad652d --- /dev/null +++ b/doc/src/sgml/ref/alter_property_graph.sgml @@ -0,0 +1,150 @@ + + + + + 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_name [, ...] ) ] + [ {EDGE|RELATIONSHIP} TABLES ( { edge_table_name SOURCE source_table_name DESTINATION dest_table_name } [, ...] ) ] + +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 + + + TODO + + + + + 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. + + + + + + TODO + + + TODO + + + + + + 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. + + + + + + + + Examples + + + +ALTER PROPERTY GRAPH g1 RENAME TO g2; + + + + + Compatibility + + + TODO + + + + + 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..257186a6a95 --- /dev/null +++ b/doc/src/sgml/ref/create_property_graph.sgml @@ -0,0 +1,232 @@ + + + + + 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 | 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 + + + 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 9d27b7fcde5..35a3a0234ec 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 cc7d797159b..c613f16b437 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 2db66bbf378..8f43a1bbd71 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 5f96b7e1ded..f9e35ba9fc1 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 9917df7839b..2ab3eecb80f 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 aa94f6adf6a..d3fb7836088 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/Makefile b/src/backend/catalog/Makefile index 196ecafc909..e7fc979fc22 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -118,7 +118,10 @@ CATALOG_HEADERS := \ pg_publication_namespace.h \ pg_publication_rel.h \ pg_subscription.h \ - pg_subscription_rel.h + pg_subscription_rel.h \ + pg_propgraph_element.h \ + pg_propgraph_label.h \ + pg_propgraph_property.h GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h syscache_ids.h syscache_info.h system_fk_info.h diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 1e44a71f61c..b9328fbe135 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -307,6 +307,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 */ @@ -551,6 +554,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); @@ -621,6 +628,7 @@ ExecGrantStmt_oids(InternalGrant *istmt) { case OBJECT_TABLE: case OBJECT_SEQUENCE: + case OBJECT_PROPGRAPH: ExecGrant_Relation(istmt); break; case OBJECT_DATABASE: @@ -693,6 +701,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); @@ -893,6 +902,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: @@ -1094,6 +1107,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); @@ -2778,6 +2795,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; @@ -2904,6 +2924,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; @@ -3040,6 +3063,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 e742c78ea35..736410af776 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -50,6 +50,9 @@ #include "catalog/pg_parameter_acl.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_property.h" #include "catalog/pg_publication.h" #include "catalog/pg_publication_namespace.h" #include "catalog/pg_publication_rel.h" @@ -70,6 +73,7 @@ #include "commands/event_trigger.h" #include "commands/extension.h" #include "commands/policy.h" +#include "commands/propgraphcmds.h" #include "commands/publicationcmds.h" #include "commands/seclabel.h" #include "commands/sequence.h" @@ -184,6 +188,9 @@ static const Oid object_classes[] = { EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */ ParameterAclRelationId, /* OCLASS_PARAMETER_ACL */ PolicyRelationId, /* OCLASS_POLICY */ + PropgraphElementRelationId, /* OCLASS_PROPGRAPH_ELEMENT */ + PropgraphLabelRelationId, /* OCLASS_PROPGRAPH_LABEL */ + PropgraphPropertyRelationId, /* OCLASS_PROPGRAPH_PROPERTY */ PublicationNamespaceRelationId, /* OCLASS_PUBLICATION_NAMESPACE */ PublicationRelationId, /* OCLASS_PUBLICATION */ PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */ @@ -1500,6 +1507,9 @@ doDeletion(const ObjectAddress *object, int flags) case OCLASS_AM: case OCLASS_AMOP: case OCLASS_AMPROC: + case OCLASS_PROPGRAPH_ELEMENT: + case OCLASS_PROPGRAPH_LABEL: + case OCLASS_PROPGRAPH_PROPERTY: case OCLASS_SCHEMA: case OCLASS_TSPARSER: case OCLASS_TSDICT: @@ -2212,6 +2222,7 @@ find_expr_references_walker(Node *node, switch (rte->rtekind) { case RTE_RELATION: + case RTE_GRAPH_TABLE: add_object_address(OCLASS_CLASS, rte->relid, 0, context->addrs); break; @@ -2952,6 +2963,15 @@ getObjectClass(const ObjectAddress *object) case PolicyRelationId: return OCLASS_POLICY; + case PropgraphElementRelationId: + return OCLASS_PROPGRAPH_ELEMENT; + + case PropgraphLabelRelationId: + return OCLASS_PROPGRAPH_LABEL; + + case PropgraphPropertyRelationId: + return OCLASS_PROPGRAPH_PROPERTY; + case PublicationNamespaceRelationId: return OCLASS_PUBLICATION_NAMESPACE; diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index c402ae72741..aab28fd708c 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -3009,3 +3009,366 @@ CREATE VIEW user_mappings AS FROM _pg_user_mappings; GRANT SELECT ON user_mappings TO PUBLIC; + + +-- SQL/PGQ views; these use section numbers from part 16 of the standard. + +/* + * 15.2 + * PG_DEFINED_LABEL_SETS view + */ + +-- TODO + + +/* + * 15.3 + * PG_DEFINED_LABEL_SET_LABELS view + */ + +-- TODO + + +/* + * 15.4 + * PG_EDGE_DEFINED_LABEL_SETS view + */ + +-- TODO + + +/* + * 15.5 + * PG_EDGE_TABLE_COMPONENTS view + */ + +CREATE VIEW pg_edge_table_components AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(eg.pgealias AS sql_identifier) AS edge_table_alias, + CAST(v.pgealias AS sql_identifier) AS vertex_table_alias, + CAST(CASE eg.end WHEN 'src' THEN 'SOURCE' WHEN 'dest' THEN 'DESTINATION' END AS character_data) AS edge_end, + CAST(ae.attname AS sql_identifier) AS edge_table_column_name, + CAST(av.attname AS sql_identifier) AS vertex_table_column_name, + CAST((eg.egkey).n AS cardinal_number) AS ordinal_position + FROM pg_namespace npg + JOIN + (SELECT * FROM pg_class WHERE relkind = 'g') AS pg + ON npg.oid = pg.relnamespace + JOIN + (SELECT pgepgid, pgealias, pgerelid, 'src' AS end, pgesrcvertexid AS vertexid, _pg_expandarray(pgesrckey) AS egkey, _pg_expandarray(pgesrcref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e' + UNION ALL + SELECT pgepgid, pgealias, pgerelid, 'dest' AS end, pgedestvertexid AS vertexid, _pg_expandarray(pgedestkey) AS egkey, _pg_expandarray(pgedestref) AS egref FROM pg_propgraph_element WHERE pgekind = 'e' + ) AS eg + ON pg.oid = eg.pgepgid + JOIN + (SELECT * FROM pg_propgraph_element WHERE pgekind = 'v') AS v + ON eg.vertexid = v.oid + JOIN + (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS ae + ON eg.pgerelid = ae.attrelid AND (eg.egkey).x = ae.attnum + JOIN + (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS av + ON v.pgerelid = av.attrelid AND (eg.egref).x = av.attnum + WHERE NOT pg_is_other_temp_schema(npg.oid) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_edge_table_components TO PUBLIC; + + +/* + * 15.6 + * PG_EDGE_TRIPLETS view + */ + +-- TODO + + +/* + * 15.7 + * PG_ELEMENT_TABLE_KEY_COLUMNS view + */ + +CREATE VIEW pg_element_table_key_columns AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(pgealias AS sql_identifier) AS element_table_alias, + CAST(a.attname AS sql_identifier) AS column_name, + CAST((el.ekey).n AS cardinal_number) AS ordinal_position + FROM pg_namespace npg + JOIN + (SELECT * FROM pg_class WHERE relkind = 'g') AS pg + ON npg.oid = pg.relnamespace + JOIN + (SELECT pgepgid, pgealias, pgerelid, _pg_expandarray(pgekey) AS ekey FROM pg_propgraph_element) AS el + ON pg.oid = el.pgepgid + JOIN + (SELECT * FROM pg_attribute WHERE NOT attisdropped) AS a + ON el.pgerelid = a.attrelid AND (el.ekey).x = a.attnum + WHERE NOT pg_is_other_temp_schema(npg.oid) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_table_key_columns TO PUBLIC; + + +/* + * 15.8 + * PG_ELEMENT_TABLE_LABELS view + */ + +CREATE VIEW pg_element_table_labels AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(e.pgealias AS sql_identifier) AS element_table_alias, + CAST(l.pgllabel AS sql_identifier) AS label_name + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND l.pglelid = e.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_table_labels TO PUBLIC; + + +/* + * 15.9 + * PG_ELEMENT_TABLE_PROPERTIES view + */ + +CREATE VIEW pg_element_table_properties AS + SELECT DISTINCT + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(e.pgealias AS sql_identifier) AS element_table_alias, + CAST(pr.pgpname AS sql_identifier) AS property_name, + CAST(pg_get_expr(pr.pgpexpr, e.pgerelid) AS character_data) AS property_expression + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l, pg_propgraph_property pr + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND l.pglelid = e.oid + AND pr.pgplabelid = l.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_table_properties TO PUBLIC; + + +/* + * 15.10 + * PG_ELEMENT_TABLES view + */ + +CREATE VIEW pg_element_tables AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(e.pgealias AS sql_identifier) AS element_table_alias, + CAST(CASE e.pgekind WHEN 'e' THEN 'EDGE' WHEN 'v' THEN 'VERTEX' END AS character_data) AS element_table_kind, + CAST(current_database() AS sql_identifier) AS table_catalog, + CAST(nt.nspname AS sql_identifier) AS table_schema, + CAST(t.relname AS sql_identifier) AS table_name, + CAST(NULL AS character_data) AS element_table_definition + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_class t, pg_namespace nt + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND e.pgerelid = t.oid + AND t.relnamespace = nt.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_element_tables TO PUBLIC; + + +/* + * 15.11 + * PG_LABEL_PROPERTIES view + */ + +CREATE VIEW pg_label_properties AS + SELECT DISTINCT + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(l.pgllabel AS sql_identifier) AS label_name, + CAST(pr.pgpname AS sql_identifier) AS property_name + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l, pg_propgraph_property pr + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND l.pglelid = e.oid + AND pr.pgplabelid = l.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_label_properties TO PUBLIC; + + +/* + * 15.12 + * PG_LABELS view + */ + +CREATE VIEW pg_labels AS + SELECT DISTINCT + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(l.pgllabel AS sql_identifier) AS label_name + FROM pg_namespace npg, pg_class pg, pg_propgraph_element e, pg_propgraph_label l + WHERE pg.relnamespace = npg.oid + AND e.pgepgid = pg.oid + AND l.pglelid = e.oid + AND pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_labels TO PUBLIC; + + +/* + * 15.13 + * PG_PROPERTY_DATA_TYPES view + */ + +CREATE VIEW pg_property_data_types AS + SELECT DISTINCT + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(npg.nspname AS sql_identifier) AS property_graph_schema, + CAST(pg.relname AS sql_identifier) AS property_graph_name, + CAST(pgp.pgpname AS sql_identifier) AS property_name, + + CAST( + CASE WHEN t.typtype = 'd' THEN + CASE WHEN bt.typelem <> 0 AND bt.typlen = -1 THEN 'ARRAY' + WHEN nbt.nspname = 'pg_catalog' THEN format_type(t.typbasetype, null) + ELSE 'USER-DEFINED' END + ELSE + CASE WHEN t.typelem <> 0 AND t.typlen = -1 THEN 'ARRAY' + WHEN nt.nspname = 'pg_catalog' THEN format_type(pgp.pgptypid, null) + ELSE 'USER-DEFINED' END + END + AS character_data) + AS data_type, + + CAST(null AS cardinal_number) AS character_maximum_length, + CAST(null AS cardinal_number) AS character_octet_length, + CAST(null AS sql_identifier) AS character_set_catalog, + CAST(null AS sql_identifier) AS character_set_schema, + CAST(null AS sql_identifier) AS character_set_name, + CAST(null AS sql_identifier) AS collation_catalog, -- FIXME + CAST(null AS sql_identifier) AS collation_schema, -- FIXME + CAST(null AS sql_identifier) AS collation_name, -- FIXME + CAST(null AS cardinal_number) AS numeric_precision, + CAST(null AS cardinal_number) AS numeric_precision_radix, + CAST(null AS cardinal_number) AS numeric_scale, + CAST(null AS cardinal_number) AS datetime_precision, + CAST(null AS character_data) AS interval_type, + CAST(null AS cardinal_number) AS interval_precision, + + CAST(current_database() AS sql_identifier) AS user_defined_type_catalog, + CAST(coalesce(nbt.nspname, nt.nspname) AS sql_identifier) AS user_defined_type_schema, + CAST(coalesce(bt.typname, t.typname) AS sql_identifier) AS user_defined_type_name, + + CAST(null AS sql_identifier) AS scope_catalog, + CAST(null AS sql_identifier) AS scope_schema, + CAST(null AS sql_identifier) AS scope_name, + + CAST(null AS cardinal_number) AS maximum_cardinality, + CAST(pgp.pgpname AS sql_identifier) AS dtd_identifier + + FROM pg_propgraph_property pgp + JOIN (pg_class pg JOIN pg_namespace npg ON (pg.relnamespace = npg.oid)) ON pgp.pgppgid = pg.oid + JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON pgp.pgptypid = t.oid + LEFT JOIN (pg_type bt JOIN pg_namespace nbt ON (bt.typnamespace = nbt.oid)) + ON (t.typtype = 'd' AND t.typbasetype = bt.oid) + + WHERE pg.relkind = 'g' + AND (NOT pg_is_other_temp_schema(npg.oid)) + AND (pg_has_role(pg.relowner, 'USAGE') + OR has_table_privilege(pg.oid, 'SELECT')); + +GRANT SELECT ON pg_property_data_types TO PUBLIC; + + +/* + * 15.14 + * PG_PROPERTY_GRAPH_PRIVILEGES view + */ + +CREATE VIEW pg_property_graph_privileges AS + SELECT CAST(u_grantor.rolname AS sql_identifier) AS grantor, + CAST(grantee.rolname AS sql_identifier) AS grantee, + CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(nc.nspname AS sql_identifier) AS property_graph_schema, + CAST(c.relname AS sql_identifier) AS property_graph_name, + CAST(c.prtype AS character_data) AS privilege_type, + CAST( + CASE WHEN + -- object owner always has grant options + pg_has_role(grantee.oid, c.relowner, 'USAGE') + OR c.grantable + THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_grantable + + FROM ( + SELECT oid, relname, relnamespace, relkind, relowner, (aclexplode(coalesce(relacl, acldefault('r', relowner)))).* FROM pg_class + ) AS c (oid, relname, relnamespace, relkind, relowner, grantor, grantee, prtype, grantable), + pg_namespace nc, + pg_authid u_grantor, + ( + SELECT oid, rolname FROM pg_authid + UNION ALL + SELECT 0::oid, 'PUBLIC' + ) AS grantee (oid, rolname) + + WHERE c.relnamespace = nc.oid + AND c.relkind IN ('g') + AND c.grantee = grantee.oid + AND c.grantor = u_grantor.oid + AND c.prtype IN ('SELECT') + AND (pg_has_role(u_grantor.oid, 'USAGE') + OR pg_has_role(grantee.oid, 'USAGE') + OR grantee.rolname = 'PUBLIC'); + +GRANT SELECT ON pg_property_graph_privileges TO PUBLIC; + + +/* + * 15.15 + * PG_VERTEX_DEFINED_LABEL_SETS view + */ + +-- TODO + + +/* + * 15.16 + * PROPERTY_GRAPHS view + */ + +CREATE VIEW property_graphs AS + SELECT CAST(current_database() AS sql_identifier) AS property_graph_catalog, + CAST(nc.nspname AS sql_identifier) AS property_graph_schema, + CAST(c.relname AS sql_identifier) AS property_graph_name + FROM pg_namespace nc, pg_class c + WHERE c.relnamespace = nc.oid + AND c.relkind = 'g' + AND (NOT pg_is_other_temp_schema(nc.oid)) + AND (pg_has_role(c.relowner, 'USAGE') + OR has_table_privilege(c.oid, 'SELECT')); + +GRANT SELECT ON property_graphs TO PUBLIC; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 8bb2924b9cd..bf43790d5a4 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -49,6 +49,9 @@ #include "catalog/pg_parameter_acl.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_property.h" #include "catalog/pg_publication.h" #include "catalog/pg_publication_namespace.h" #include "catalog/pg_publication_rel.h" @@ -373,6 +376,48 @@ static const ObjectPropertyType ObjectProperty[] = OBJECT_OPFAMILY, true }, + { + "property graph element", + PropgraphElementRelationId, + PropgraphElementObjectIndexId, + PROPGRAPHELOID, + PROPGRAPHELALIAS, + Anum_pg_propgraph_element_oid, + Anum_pg_propgraph_element_pgealias, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, + { + "property graph label", + PropgraphLabelRelationId, + PropgraphLabelObjectIndexId, + -1, + PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + Anum_pg_propgraph_label_pgllabel, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, + { + "property graph property", + PropgraphPropertyRelationId, + PropgraphPropertyObjectIndexId, + -1, + PROPGRAPHPROPNAME, + Anum_pg_propgraph_property_oid, + Anum_pg_propgraph_property_pgpname, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false + }, { "role", AuthIdRelationId, @@ -683,6 +728,9 @@ static const struct object_type_map { "foreign table", OBJECT_FOREIGN_TABLE }, + { + "property graph", OBJECT_PROPGRAPH + }, { "table column", OBJECT_COLUMN }, @@ -852,6 +900,18 @@ static const struct object_type_map { "policy", OBJECT_POLICY }, + /* OCLASS_PROPGRAPH_ELEMENT */ + { + "property graph element", -1 + }, + /* OCLASS_PROPGRAPH_LABEL */ + { + "property graph label", -1 + }, + /* OCLASS_PROPGRAPH_PROPERTY */ + { + "property graph property", -1 + }, /* OCLASS_PUBLICATION */ { "publication", OBJECT_PUBLICATION @@ -992,6 +1052,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, @@ -1400,6 +1461,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, @@ -2315,6 +2383,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: @@ -2434,6 +2503,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: @@ -3964,6 +4034,43 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case OCLASS_PROPGRAPH_ELEMENT: + { + HeapTuple tup; + Form_pg_propgraph_element pgeform; + StringInfoData rel; + + tup = SearchSysCache1(PROPGRAPHELOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for property graph element %u", + object->objectId); + + pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup); + + initStringInfo(&rel); + getRelationDescription(&rel, pgeform->pgepgid, false); + + /* translator: second %s is, e.g., "property graph %s" */ + appendStringInfo(&buffer, _("element %s in %s"), + NameStr(pgeform->pgealias), rel.data); + pfree(rel.data); + ReleaseSysCache(tup); + break; + } + + case OCLASS_PROPGRAPH_LABEL: + { + appendStringInfo(&buffer, "label %u TODO", object->objectId); + break; + } + + case OCLASS_PROPGRAPH_PROPERTY: + { + appendStringInfo(&buffer, "property %u TODO", object->objectId); + break; + } + case OCLASS_PUBLICATION: { char *pubname = get_publication_name(object->objectId, @@ -4151,6 +4258,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"), @@ -4571,6 +4682,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) appendStringInfoString(&buffer, "policy"); break; + case OCLASS_PROPGRAPH_ELEMENT: + appendStringInfoString(&buffer, "property graph element"); + break; + + case OCLASS_PROPGRAPH_LABEL: + appendStringInfoString(&buffer, "property graph label"); + break; + + case OCLASS_PROPGRAPH_PROPERTY: + appendStringInfoString(&buffer, "property graph property"); + break; + case OCLASS_PUBLICATION: appendStringInfoString(&buffer, "publication"); break; @@ -4654,6 +4777,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"); @@ -5813,6 +5939,18 @@ getObjectIdentityParts(const ObjectAddress *object, break; } + case OCLASS_PROPGRAPH_ELEMENT: + appendStringInfo(&buffer, "%u TODO", object->objectId); + break; + + case OCLASS_PROPGRAPH_LABEL: + appendStringInfo(&buffer, "%u TODO", object->objectId); + break; + + case OCLASS_PROPGRAPH_PROPERTY: + appendStringInfo(&buffer, "%u TODO", object->objectId); + break; + case OCLASS_PUBLICATION: { char *pubname; @@ -6121,6 +6259,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 4085a2d18d2..fbfca2068d8 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -349,6 +349,106 @@ F866 FETCH FIRST clause: PERCENT option NO F867 FETCH FIRST clause: WITH TIES option YES F868 ORDER BY in grouped table YES F869 SQL implementation info population YES +G000 Graph pattern YES SQL/PGQ required +G001 Repeatable-elements match mode YES SQL/PGQ required XXX +G002 Different-edges match mode NO +G003 Explicit REPEATABLE ELEMENTS keyword NO +G004 Path variables NO +G005 Path search prefix in a path pattern NO +G006 Graph pattern KEEP clause: path mode prefix NO +G007 Graph pattern KEEP clause: path search prefix NO +G008 Graph pattern WHERE clause YES SQL/PGQ required +G010 Explicit WALK keyword NO +G011 Advanced path modes: TRAIL NO +G012 Advanced path modes: SIMPLE NO +G013 Advanced path modes: ACYCLIC NO +G014 Explicit PATH/PATHS keywords NO +G015 All path search: explicit ALL keyword NO +G016 Any path search NO +G017 All shortest path search NO +G018 Any shortest path search NO +G019 Counted shortest path search NO +G020 Counted shortest group search NO +G030 Path multiset alternation NO +G031 Path multiset alternation: variable length path operands NO +G032 Path pattern union NO +G033 Path pattern union: variable length path operands NO +G034 Path concatenation YES SQL/PGQ required +G035 Quantified paths NO +G036 Quantified edges NO +G037 Questioned paths NO +G038 Parenthesized path pattern expression NO +G039 Simplified path pattern expression: full defaulting NO +G040 Vertex pattern YES SQL/PGQ required +G041 Non-local element pattern predicates NO +G042 Basic full edge patterns YES SQL/PGQ required +G043 Complete full edge patterns NO +G044 Basic abbreviated edge patterns YES +G045 Complete abbreviated edge patterns NO +G046 Relaxed topological consistency: adjacent vertex patterns NO +G047 Relaxed topological consistency: concise edge patterns NO +G048 Parenthesized path pattern: subpath variable declaration NO +G049 Parenthesized path pattern: path mode prefix NO +G050 Parenthesized path pattern: WHERE clause NO +G051 Parenthesized path pattern: non-local predicates NO +G060 Bounded graph pattern quantifiers NO +G061 Unbounded graph pattern quantifiers NO +G070 Label expression: label disjunction NO SQL/PGQ required +G071 Label expression: label conjunction NO +G072 Label expression: label negation NO +G073 Label expression: individual label name YES SQL/PGQ required +G074 Label expression: wildcard label NO +G075 Parenthesized label expression NO +G080 Simplified path pattern expression: basic defaulting NO +G081 Simplified path pattern expression: full overrides NO +G082 Simplified path pattern expression: basic overrides NO +G090 Property reference YES SQL/PGQ required +G100 ELEMENT_ID function NO +G110 IS DIRECTED predicate NO +G111 IS LABELED predicate NO +G112 IS SOURCE and IS DESTINATION predicate NO +G113 ALL_DIFFERENT predicate NO +G114 SAME predicate NO +G115 PROPERTY_EXISTS predicate NO +G120 Within-match aggregates NO +G800 PATH_NAME function NO +G801 ELEMENT_NUMBER function NO +G802 PATH_LENGTH function NO +G803 MATCHNUM function NO +G810 IS BOUND predicate NO +G811 IS BOUND predicate: AS option NO +G820 BINDING_COUNT NO +G830 Colon in 'is label' expression YES +G840 Path-ordered aggregates NO +G850 SQL/PGQ Information Schema views YES +G860 GET DIAGNOSTICS enhancements for SQL-property graphs NO +G900 GRAPH_TABLE YES SQL/PGQ required +G901 GRAPH_TABLE: ONE ROW PER VERTEX NO +G902 GRAPH_TABLE: ONE ROW PER STEP NO +G903 GRAPH_TABLE: explicit ONE ROW PER MATCH keywords NO +G904 All properties reference NO +G905 GRAPH_TABLE: optional COLUMNS clause NO +G906 GRAPH_TABLE: explicit EXPORT ALL NO +G907 GRAPH_TABLE: EXPORT ALL EXCEPT NO +G908 GRAPH_TABLE: EXPORT SINGLETONS list NO +G909 GRAPH_TABLE: explicit EXPORT NO SINGLETONS NO +G910 GRAPH_TABLE: 'in paths clause' NO +G920 DDL-based SQL-property graphs YES SQL/PGQ required +G921 Empty SQL-property graph YES +G922 Views as element tables YES +G923 In-line views as element tables NO +G924 Explicit key clause for element tables YES SQL/PGQ required +G925 Explicit label and properties clause for element tables YES SQL/PGQ required +G926 More than one label for vertex tables YES +G927 More than one label for edge tables YES +G928 Value expressions as properties and renaming of properties YES +G929 Labels and properties: EXCEPT list NO +G940 Multi-sourced/destined edges NO XXX +G941 Implicit removal of incomplete edges NO XXX +G950 Alter property graph statement: ADD/DROP element table YES +G960 Alter element table definition: ADD/DROP LABEL YES +G970 Alter element table definition: ALTER LABEL YES +G980 DROP PROPERTY GRAPH: CASCADE drop behavior YES R010 Row pattern recognition: FROM clause NO R020 Row pattern recognition: WINDOW clause NO R030 Row pattern recognition: full aggregate support NO diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 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 b2e4260aef7..e8234ac34a0 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -383,6 +383,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_MATVIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: + case OBJECT_PROPGRAPH: return RenameRelation(stmt); case OBJECT_COLUMN: @@ -539,6 +540,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, case OBJECT_TABLE: case OBJECT_VIEW: case OBJECT_MATVIEW: + case OBJECT_PROPGRAPH: address = AlterTableNamespace(stmt, oldSchemaAddr ? &oldNspOid : NULL); break; @@ -690,6 +692,9 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, case OCLASS_EVENT_TRIGGER: case OCLASS_PARAMETER_ACL: case OCLASS_POLICY: + case OCLASS_PROPGRAPH_ELEMENT: + case OCLASS_PROPGRAPH_LABEL: + case OCLASS_PROPGRAPH_PROPERTY: case OCLASS_PUBLICATION: case OCLASS_PUBLICATION_NAMESPACE: case OCLASS_PUBLICATION_REL: @@ -912,6 +917,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: @@ -921,16 +927,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 87a2db4e0f5..79ce0967995 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -487,6 +487,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 c8b662131c3..243ba9e2b33 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1168,6 +1168,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_OPFAMILY: case OBJECT_POLICY: case OBJECT_PROCEDURE: + case OBJECT_PROPGRAPH: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: @@ -1248,6 +1249,9 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_DEFACL: case OCLASS_EXTENSION: case OCLASS_POLICY: + case OCLASS_PROPGRAPH_ELEMENT: + case OCLASS_PROPGRAPH_LABEL: + case OCLASS_PROPGRAPH_PROPERTY: case OCLASS_PUBLICATION: case OCLASS_PUBLICATION_NAMESPACE: case OCLASS_PUBLICATION_REL: @@ -2266,6 +2270,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: @@ -2350,6 +2355,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..25948861343 --- /dev/null +++ b/src/backend/commands/propgraphcmds.c @@ -0,0 +1,1001 @@ +/*------------------------------------------------------------------------- + * + * propgraphcmds.c + * property graph manipulation + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/backend/commands/propgraphcmds.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_class.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_property.h" +#include "commands/propgraphcmds.h" +#include "commands/tablecmds.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_relation.h" +#include "parser/parse_target.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + + +struct element_info +{ + Oid elementid; + char kind; + Oid relid; + char *aliasname; + ArrayType *key; + + char *srcvertex; + Oid srcvertexid; + ArrayType *srckey; + ArrayType *srcref; + + char *destvertex; + Oid destvertexid; + ArrayType *destkey; + ArrayType *destref; + + List *labels; +}; + + +static ArrayType *propgraph_element_get_key(ParseState *pstate, const List *key_clause, int location, Relation element_rel); +static ArrayType *array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel); +static void insert_element_record(ObjectAddress pgaddress, struct element_info *einfo); +static Oid insert_label_record(Oid graphid, Oid peoid, const char *label); +static void insert_property_records(Oid graphid, Oid labeloid, Oid pgerelid, const PropGraphProperties *properties); +static void insert_property_record(Oid graphid, Oid labeloid, const char *propname, const Expr *expr); +static Oid get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location); +static Oid get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location); +static Oid get_element_relid(Oid peid); + + +/* + * CREATE PROPERTY GRAPH + */ +ObjectAddress +CreatePropGraph(ParseState *pstate, const CreatePropGraphStmt *stmt) +{ + CreateStmt *cstmt = makeNode(CreateStmt); + char components_persistence; + ListCell *lc; + ObjectAddress pgaddress; + List *vertex_infos = NIL; + List *edge_infos = NIL; + List *element_aliases = NIL; + + if (stmt->pgname->relpersistence == RELPERSISTENCE_UNLOGGED) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("property graphs cannot be unlogged because they do not have storage"))); + + components_persistence = RELPERSISTENCE_PERMANENT; + + foreach(lc, stmt->vertex_tables) + { + PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc); + struct element_info *vinfo; + Relation rel; + + vinfo = palloc0_object(struct element_info); + vinfo->kind = PGEKIND_VERTEX; + + vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(vinfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + components_persistence = RELPERSISTENCE_TEMP; + + if (vertex->vtable->alias) + vinfo->aliasname = vertex->vtable->alias->aliasname; + else + vinfo->aliasname = vertex->vtable->relname; + + if (list_member(element_aliases, makeString(vinfo->aliasname))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("alias \"%s\" used more than once as element table", vinfo->aliasname), + parser_errposition(pstate, vertex->location))); + + vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, vertex->location, rel); + + vinfo->labels = vertex->labels; + + table_close(rel, NoLock); + + vertex_infos = lappend(vertex_infos, vinfo); + + element_aliases = lappend(element_aliases, makeString(vinfo->aliasname)); + } + + foreach(lc, stmt->edge_tables) + { + PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc); + struct element_info *einfo; + Relation rel; + ListCell *lc2; + Oid srcrelid; + Oid destrelid; + Relation srcrel; + Relation destrel; + + einfo = palloc0_object(struct element_info); + einfo->kind = PGEKIND_EDGE; + + einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(einfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + components_persistence = RELPERSISTENCE_TEMP; + + if (edge->etable->alias) + einfo->aliasname = edge->etable->alias->aliasname; + else + einfo->aliasname = edge->etable->relname; + + if (list_member(element_aliases, makeString(einfo->aliasname))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("alias \"%s\" used more than once as element table", einfo->aliasname), + parser_errposition(pstate, edge->location))); + + einfo->key = propgraph_element_get_key(pstate, edge->ekey, edge->location, rel); + + einfo->srcvertex = edge->esrcvertex; + einfo->destvertex = edge->edestvertex; + + srcrelid = 0; + destrelid = 0; + foreach(lc2, vertex_infos) + { + struct element_info *vinfo = lfirst(lc2); + + if (strcmp(vinfo->aliasname, edge->esrcvertex) == 0) + srcrelid = vinfo->relid; + + if (strcmp(vinfo->aliasname, edge->edestvertex) == 0) + destrelid = vinfo->relid; + + if (srcrelid && destrelid) + break; + } + if (!srcrelid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("source vertex \"%s\" of edge \"%s\" does not exist", + edge->esrcvertex, einfo->aliasname), + parser_errposition(pstate, edge->location))); + if (!destrelid) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("destination vertex \"%s\" of edge \"%s\" does not exist", + edge->edestvertex, einfo->aliasname), + parser_errposition(pstate, edge->location))); + + if (!edge->esrckey || !edge->esrcvertexcols || !edge->edestkey || !edge->edestvertexcols) + elog(ERROR, "TODO foreign key support"); + + srcrel = table_open(srcrelid, NoLock); + destrel = table_open(destrelid, NoLock); + + if (list_length(edge->esrckey) != list_length(edge->esrcvertexcols)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("mismatching number of columns in source vertex definition of edge \"%s\"", + einfo->aliasname), + parser_errposition(pstate, edge->location))); + + if (list_length(edge->edestkey) != list_length(edge->edestvertexcols)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("mismatching number of columns in destination vertex definition of edge \"%s\"", + einfo->aliasname), + parser_errposition(pstate, edge->location))); + + einfo->srckey = array_from_column_list(pstate, edge->esrckey, edge->location, rel); + einfo->srcref = array_from_column_list(pstate, edge->esrcvertexcols, edge->location, srcrel); + einfo->destkey = array_from_column_list(pstate, edge->edestkey, edge->location, rel); + einfo->destref = array_from_column_list(pstate, edge->edestvertexcols, edge->location, destrel); + + /* TODO: various consistency checks */ + + einfo->labels = edge->labels; + + table_close(destrel, NoLock); + table_close(srcrel, NoLock); + + table_close(rel, NoLock); + + edge_infos = lappend(edge_infos, einfo); + + element_aliases = lappend(element_aliases, makeString(einfo->aliasname)); + } + + cstmt->relation = stmt->pgname; + cstmt->oncommit = ONCOMMIT_NOOP; + + /* + * Automatically make it temporary if any component tables are temporary + * (see also DefineView()). + */ + if (stmt->pgname->relpersistence == RELPERSISTENCE_PERMANENT + && components_persistence == RELPERSISTENCE_TEMP) + { + cstmt->relation = copyObject(cstmt->relation); + cstmt->relation->relpersistence = RELPERSISTENCE_TEMP; + ereport(NOTICE, + (errmsg("property graph \"%s\" will be temporary", + stmt->pgname->relname))); + } + + pgaddress = DefineRelation(cstmt, RELKIND_PROPGRAPH, InvalidOid, NULL, NULL); + + foreach(lc, vertex_infos) + { + struct element_info *vinfo = lfirst(lc); + + insert_element_record(pgaddress, vinfo); + } + + foreach(lc, edge_infos) + { + struct element_info *einfo = lfirst(lc); + ListCell *lc2; + + /* + * Look up the vertices again. Now the vertices have OIDs assigned, + * which we need. + */ + foreach(lc2, vertex_infos) + { + struct element_info *vinfo = lfirst(lc2); + + if (strcmp(vinfo->aliasname, einfo->srcvertex) == 0) + einfo->srcvertexid = vinfo->elementid; + if (strcmp(vinfo->aliasname, einfo->destvertex) == 0) + einfo->destvertexid = vinfo->elementid; + if (einfo->srcvertexid && einfo->destvertexid) + break; + } + Assert(einfo->srcvertexid); + Assert(einfo->destvertexid); + insert_element_record(pgaddress, einfo); + } + + return pgaddress; +} + +/* + * Process the key clause specified for an element. If key_clause is non-NIL, + * then it is a list of column names. Otherwise, the primary key of the + * relation is used. The return value is an array of column numbers. + */ +static ArrayType * +propgraph_element_get_key(ParseState *pstate, const List *key_clause, int location, Relation element_rel) +{ + ArrayType *a; + + if (key_clause == NIL) + { + Oid pkidx = RelationGetPrimaryKeyIndex(element_rel); + + if (!pkidx) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("element table key must be specified for table without primary key"), + parser_errposition(pstate, location))); + else + { + Relation indexDesc; + int numattrs; + Datum *attnumsd; + + indexDesc = index_open(pkidx, AccessShareLock); + numattrs = indexDesc->rd_index->indkey.dim1; + attnumsd = palloc_array(Datum, numattrs); + for (int i = 0; i < numattrs; i++) + attnumsd[i] = Int16GetDatum(indexDesc->rd_index->indkey.values[i]); + a = construct_array_builtin(attnumsd, numattrs, INT2OID); + index_close(indexDesc, NoLock); + } + } + else + { + a = array_from_column_list(pstate, key_clause, location, element_rel); + } + + return a; +} + +/* + * Convert list of column names in the specified relation into an array of + * column numbers. + */ +static ArrayType * +array_from_column_list(ParseState *pstate, const List *colnames, int location, Relation element_rel) +{ + int numattrs; + Datum *attnumsd; + int i; + ListCell *lc; + + numattrs = list_length(colnames); + attnumsd = palloc_array(Datum, numattrs); + + i = 0; + foreach(lc, colnames) + { + char *colname = strVal(lfirst(lc)); + Oid relid = RelationGetRelid(element_rel); + AttrNumber attnum; + + attnum = get_attnum(relid, colname); + if (!attnum) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + colname, get_rel_name(relid)), + parser_errposition(pstate, location))); + attnumsd[i++] = Int16GetDatum(attnum); + } + + for (int j = 0; j < numattrs; j++) + { + for (int k = j + 1; k < numattrs; k++) + { + if (DatumGetInt16(attnumsd[j]) == DatumGetInt16(attnumsd[k])) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("graph key columns list must not contain duplicates"), + parser_errposition(pstate, location))); + } + } + + return construct_array_builtin(attnumsd, numattrs, INT2OID); +} + +/* + * Insert a record for an element into the pg_propgraph_element catalog. Also + * inserts labels and properties into their respective catalogs. + */ +static void +insert_element_record(ObjectAddress pgaddress, struct element_info *einfo) +{ + Oid graphid = pgaddress.objectId; + Relation rel; + NameData aliasname; + Oid peoid; + Datum values[Natts_pg_propgraph_element] = {0}; + bool nulls[Natts_pg_propgraph_element] = {0}; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + rel = table_open(PropgraphElementRelationId, RowExclusiveLock); + + peoid = GetNewOidWithIndex(rel, PropgraphElementObjectIndexId, Anum_pg_propgraph_element_oid); + einfo->elementid = peoid; + values[Anum_pg_propgraph_element_oid - 1] = ObjectIdGetDatum(peoid); + values[Anum_pg_propgraph_element_pgepgid - 1] = ObjectIdGetDatum(graphid); + values[Anum_pg_propgraph_element_pgerelid - 1] = ObjectIdGetDatum(einfo->relid); + namestrcpy(&aliasname, einfo->aliasname); + values[Anum_pg_propgraph_element_pgealias - 1] = NameGetDatum(&aliasname); + values[Anum_pg_propgraph_element_pgekind - 1] = CharGetDatum(einfo->kind); + values[Anum_pg_propgraph_element_pgesrcvertexid - 1] = ObjectIdGetDatum(einfo->srcvertexid); + values[Anum_pg_propgraph_element_pgedestvertexid - 1] = ObjectIdGetDatum(einfo->destvertexid); + values[Anum_pg_propgraph_element_pgekey - 1] = PointerGetDatum(einfo->key); + + if (einfo->srckey) + values[Anum_pg_propgraph_element_pgesrckey - 1] = PointerGetDatum(einfo->srckey); + else + nulls[Anum_pg_propgraph_element_pgesrckey - 1] = true; + if (einfo->srcref) + values[Anum_pg_propgraph_element_pgesrcref - 1] = PointerGetDatum(einfo->srcref); + else + nulls[Anum_pg_propgraph_element_pgesrcref - 1] = true; + if (einfo->destkey) + values[Anum_pg_propgraph_element_pgedestkey - 1] = PointerGetDatum(einfo->destkey); + else + nulls[Anum_pg_propgraph_element_pgedestkey - 1] = true; + if (einfo->destref) + values[Anum_pg_propgraph_element_pgedestref - 1] = PointerGetDatum(einfo->destref); + else + nulls[Anum_pg_propgraph_element_pgedestref - 1] = true; + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphElementRelationId, peoid); + + /* Add dependency on the property graph */ + recordDependencyOn(&myself, &pgaddress, DEPENDENCY_AUTO); + + /* Add dependency on the relation */ + ObjectAddressSet(referenced, RelationRelationId, einfo->relid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* Add dependencies on vertices */ + /* TODO: columns */ + if (einfo->srcvertexid) + { + ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->srcvertexid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + if (einfo->destvertexid) + { + ObjectAddressSet(referenced, PropgraphElementRelationId, einfo->destvertexid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + table_close(rel, NoLock); + + if (einfo->labels) + { + ListCell *lc; + + foreach(lc, einfo->labels) + { + PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc); + Oid labeloid; + + if (lp->label) + labeloid = insert_label_record(graphid, peoid, lp->label); + else + labeloid = insert_label_record(graphid, peoid, einfo->aliasname); + insert_property_records(graphid, labeloid, einfo->relid, lp->properties); + } + } + else + { + Oid labeloid; + PropGraphProperties *pr = makeNode(PropGraphProperties); + + pr->all = true; + pr->location = -1; + + labeloid = insert_label_record(graphid, peoid, einfo->aliasname); + insert_property_records(graphid, labeloid, einfo->relid, pr); + } +} + +/* + * Insert a record for a label into the pg_propgraph_label catalog. + */ +static Oid +insert_label_record(Oid graphid, Oid peoid, const char *label) +{ + Relation rel; + NameData labelname; + Oid labeloid; + Datum values[Natts_pg_propgraph_label] = {0}; + bool nulls[Natts_pg_propgraph_label] = {0}; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + rel = table_open(PropgraphLabelRelationId, RowExclusiveLock); + + labeloid = GetNewOidWithIndex(rel, PropgraphLabelObjectIndexId, Anum_pg_propgraph_label_oid); + values[Anum_pg_propgraph_label_oid - 1] = ObjectIdGetDatum(labeloid); + values[Anum_pg_propgraph_label_pglpgid - 1] = ObjectIdGetDatum(graphid); + namestrcpy(&labelname, label); + values[Anum_pg_propgraph_label_pgllabel - 1] = NameGetDatum(&labelname); + values[Anum_pg_propgraph_label_pglelid - 1] = ObjectIdGetDatum(peoid); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphLabelRelationId, labeloid); + + /* Add dependency on the property graph element */ + ObjectAddressSet(referenced, PropgraphElementRelationId, peoid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + table_close(rel, NoLock); + + return labeloid; +} + +/* + * Insert records for properties into the pg_propgraph_property catalog. + */ +static void +insert_property_records(Oid graphid, Oid labeloid, Oid pgerelid, const PropGraphProperties *properties) +{ + List *proplist = NIL; + ParseState *pstate; + ParseNamespaceItem *nsitem; + List *tp; + Relation rel; + ListCell *lc; + + if (properties->all) + { + Relation attRelation; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple attributeTuple; + + attRelation = table_open(AttributeRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(pgerelid)); + scan = systable_beginscan(attRelation, AttributeRelidNumIndexId, + true, NULL, 1, key); + while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) + { + Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple); + ColumnRef *cr; + ResTarget *rt; + + if (att->attnum <= 0 || att->attisdropped) + continue; + + cr = makeNode(ColumnRef); + rt = makeNode(ResTarget); + + cr->fields = list_make1(makeString(NameStr(att->attname))); + cr->location = -1; + + rt->name = pstrdup(NameStr(att->attname)); + rt->val = (Node *) cr; + rt->location = -1; + + proplist = lappend(proplist, rt); + } + systable_endscan(scan); + table_close(attRelation, RowShareLock); + } + else + { + proplist = properties->properties; + + foreach(lc, proplist) + { + ResTarget *rt = lfirst_node(ResTarget, lc); + + if (!rt->name && !IsA(rt->val, ColumnRef)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("property name required"), + parser_errposition(NULL, rt->location)); + } + } + + rel = table_open(pgerelid, AccessShareLock); + + pstate = make_parsestate(NULL); + nsitem = addRangeTableEntryForRelation(pstate, + rel, + AccessShareLock, + NULL, + false, + true); + addNSItemToQuery(pstate, nsitem, true, true, true); + + table_close(rel, NoLock); + + tp = transformTargetList(pstate, proplist, EXPR_KIND_OTHER); + + foreach(lc, tp) + { + TargetEntry *te = lfirst_node(TargetEntry, lc); + + insert_property_record(graphid, labeloid, te->resname, te->expr); + } +} + +/* + * Insert one record for a property into the pg_propgraph_property catalog. + */ +static void +insert_property_record(Oid graphid, Oid labeloid, const char *propname, const Expr *expr) +{ + Relation rel; + NameData propnamedata; + Oid propoid; + Datum values[Natts_pg_propgraph_property] = {0}; + bool nulls[Natts_pg_propgraph_property] = {0}; + HeapTuple tup; + ObjectAddress myself; + ObjectAddress referenced; + + rel = table_open(PropgraphPropertyRelationId, RowExclusiveLock); + + propoid = GetNewOidWithIndex(rel, PropgraphPropertyObjectIndexId, Anum_pg_propgraph_property_oid); + values[Anum_pg_propgraph_property_oid - 1] = ObjectIdGetDatum(propoid); + values[Anum_pg_propgraph_property_pgppgid - 1] = ObjectIdGetDatum(graphid); + namestrcpy(&propnamedata, propname); + values[Anum_pg_propgraph_property_pgpname - 1] = NameGetDatum(&propnamedata); + values[Anum_pg_propgraph_property_pgptypid - 1] = ObjectIdGetDatum(exprType((const Node *) expr)); + values[Anum_pg_propgraph_property_pgplabelid - 1] = ObjectIdGetDatum(labeloid); + values[Anum_pg_propgraph_property_pgpexpr - 1] = CStringGetTextDatum(nodeToString(expr)); + + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, tup); + heap_freetuple(tup); + + ObjectAddressSet(myself, PropgraphPropertyRelationId, propoid); + + /* Add dependency on the property graph label */ + ObjectAddressSet(referenced, PropgraphLabelRelationId, labeloid); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + table_close(rel, NoLock); +} + +/* + * ALTER PROPERTY GRAPH + */ +ObjectAddress +AlterPropGraph(ParseState *pstate, const AlterPropGraphStmt *stmt) +{ + Oid pgrelid; + ListCell *lc; + ObjectAddress pgaddress; + + pgrelid = RangeVarGetRelidExtended(stmt->pgname, + ShareRowExclusiveLock, + stmt->missing_ok ? RVR_MISSING_OK : 0, + RangeVarCallbackOwnsRelation, + NULL); + if (pgrelid == InvalidOid) + { + ereport(NOTICE, + (errmsg("relation \"%s\" does not exist, skipping", + stmt->pgname->relname))); + return InvalidObjectAddress; + } + + ObjectAddressSet(pgaddress, RelationRelationId, pgrelid); + + foreach(lc, stmt->add_vertex_tables) + { + PropGraphVertex *vertex = lfirst_node(PropGraphVertex, lc); + struct element_info *vinfo; + Relation rel; + + vinfo = palloc0_object(struct element_info); + vinfo->kind = PGEKIND_VERTEX; + + vinfo->relid = RangeVarGetRelidExtended(vertex->vtable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(vinfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + elog(ERROR, "TODO"); + + if (vertex->vtable->alias) + vinfo->aliasname = vertex->vtable->alias->aliasname; + else + vinfo->aliasname = vertex->vtable->relname; + + vinfo->key = propgraph_element_get_key(pstate, vertex->vkey, vertex->location, rel); + + vinfo->labels = vertex->labels; + + table_close(rel, NoLock); + + insert_element_record(pgaddress, vinfo); + } + + CommandCounterIncrement(); + + foreach(lc, stmt->add_edge_tables) + { + PropGraphEdge *edge = lfirst_node(PropGraphEdge, lc); + struct element_info *einfo; + Relation rel; + Oid srcrelid; + Oid destrelid; + Relation srcrel; + Relation destrel; + + einfo = palloc0_object(struct element_info); + einfo->kind = PGEKIND_EDGE; + + einfo->relid = RangeVarGetRelidExtended(edge->etable, AccessShareLock, 0, RangeVarCallbackOwnsRelation, NULL); + + rel = table_open(einfo->relid, NoLock); + + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + elog(ERROR, "TODO"); + + if (edge->etable->alias) + einfo->aliasname = edge->etable->alias->aliasname; + else + einfo->aliasname = edge->etable->relname; + + einfo->key = propgraph_element_get_key(pstate, edge->ekey, edge->location, rel); + + einfo->srcvertexid = get_vertex_oid(pstate, pgrelid, edge->esrcvertex, edge->location); + einfo->destvertexid = get_vertex_oid(pstate, pgrelid, edge->edestvertex, edge->location); + + if (!edge->esrckey || !edge->esrcvertexcols || !edge->edestkey || !edge->edestvertexcols) + elog(ERROR, "TODO foreign key support"); + + srcrelid = get_element_relid(einfo->srcvertexid); + destrelid = get_element_relid(einfo->destvertexid); + + srcrel = table_open(srcrelid, AccessShareLock); + destrel = table_open(destrelid, AccessShareLock); + + if (list_length(edge->esrckey) != list_length(edge->esrcvertexcols)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("mismatching number of columns in source vertex definition of edge \"%s\"", + einfo->aliasname), + parser_errposition(pstate, edge->location))); + + if (list_length(edge->edestkey) != list_length(edge->edestvertexcols)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("mismatching number of columns in destination vertex definition of edge \"%s\"", + einfo->aliasname), + parser_errposition(pstate, edge->location))); + + einfo->srckey = array_from_column_list(pstate, edge->esrckey, edge->location, rel); + einfo->srcref = array_from_column_list(pstate, edge->esrcvertexcols, edge->location, srcrel); + einfo->destkey = array_from_column_list(pstate, edge->edestkey, edge->location, rel); + einfo->destref = array_from_column_list(pstate, edge->edestvertexcols, edge->location, destrel); + + /* TODO: various consistency checks */ + + einfo->labels = edge->labels; + + table_close(destrel, NoLock); + table_close(srcrel, NoLock); + + table_close(rel, NoLock); + + insert_element_record(pgaddress, einfo); + } + + foreach(lc, stmt->drop_vertex_tables) + { + char *alias = strVal(lfirst(lc)); + Oid peoid; + ObjectAddress obj; + + peoid = get_vertex_oid(pstate, pgrelid, alias, -1); + ObjectAddressSet(obj, PropgraphElementRelationId, peoid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + + foreach(lc, stmt->drop_edge_tables) + { + char *alias = strVal(lfirst(lc)); + Oid peoid; + ObjectAddress obj; + + peoid = get_edge_oid(pstate, pgrelid, alias, -1); + ObjectAddressSet(obj, PropgraphElementRelationId, peoid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + + foreach(lc, stmt->add_labels) + { + PropGraphLabelAndProperties *lp = lfirst_node(PropGraphLabelAndProperties, lc); + Oid peoid; + Oid pgerelid; + Oid labeloid; + + Assert(lp->label); + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + pgerelid = get_element_relid(peoid); + + labeloid = insert_label_record(pgrelid, peoid, lp->label); + insert_property_records(pgrelid, labeloid, pgerelid, lp->properties); + } + + if (stmt->drop_label) + { + Oid peoid; + Oid labeloid; + ObjectAddress obj; + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + ObjectIdGetDatum(peoid), + CStringGetDatum(stmt->drop_label)); + if (!labeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label), + parser_errposition(pstate, -1)); + + ObjectAddressSet(obj, PropgraphLabelRelationId, labeloid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + + if (stmt->add_properties) + { + Oid peoid; + Oid pgerelid; + Oid labeloid; + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + pgerelid = get_element_relid(peoid); + + labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + ObjectIdGetDatum(peoid), + CStringGetDatum(stmt->alter_label)); + if (!labeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label), + parser_errposition(pstate, -1)); + + insert_property_records(pgrelid, labeloid, pgerelid, stmt->add_properties); + } + + if (stmt->drop_properties) + { + Oid peoid; + Oid labeloid; + ObjectAddress obj; + + if (stmt->element_kind == PROPGRAPH_ELEMENT_KIND_VERTEX) + peoid = get_vertex_oid(pstate, pgrelid, stmt->element_alias, -1); + else + peoid = get_edge_oid(pstate, pgrelid, stmt->element_alias, -1); + + labeloid = GetSysCacheOid2(PROPGRAPHLABELNAME, + Anum_pg_propgraph_label_oid, + ObjectIdGetDatum(peoid), + CStringGetDatum(stmt->alter_label)); + if (!labeloid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" has no label \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->drop_label), + parser_errposition(pstate, -1)); + + foreach(lc, stmt->drop_properties) + { + char *propname = strVal(lfirst(lc)); + Oid propoid; + + propoid = GetSysCacheOid2(PROPGRAPHPROPNAME, + Anum_pg_propgraph_property_oid, + ObjectIdGetDatum(labeloid), + CStringGetDatum(propname)); + if (!propoid) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" element \"%s\" label \"%s\" has no property \"%s\"", + get_rel_name(pgrelid), stmt->element_alias, stmt->alter_label, propname), + parser_errposition(pstate, -1)); + + ObjectAddressSet(obj, PropgraphPropertyRelationId, propoid); + performDeletion(&obj, stmt->drop_behavior, 0); + } + } + + return pgaddress; +} + +/* + * Get OID of vertex from graph OID and element alias. Element must be a + * vertex, otherwise error. + */ +static Oid +get_vertex_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location) +{ + HeapTuple tuple; + Oid peoid; + + tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias)); + if (!tuple) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" has no element with alias \"%s\"", + get_rel_name(pgrelid), alias), + parser_errposition(pstate, location)); + + if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_VERTEX) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("element \"%s\" of property graph \"%s\" is not a vertex", + alias, get_rel_name(pgrelid)), + parser_errposition(pstate, location)); + + peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid; + + ReleaseSysCache(tuple); + + return peoid; +} + +/* + * Get OID of edge from graph OID and element alias. Element must be an edge, + * otherwise error. + */ +static Oid +get_edge_oid(ParseState *pstate, Oid pgrelid, const char *alias, int location) +{ + HeapTuple tuple; + Oid peoid; + + tuple = SearchSysCache2(PROPGRAPHELALIAS, ObjectIdGetDatum(pgrelid), CStringGetDatum(alias)); + if (!tuple) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("property graph \"%s\" has no element with alias \"%s\"", + get_rel_name(pgrelid), alias), + parser_errposition(pstate, location)); + + if (((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgekind != PGEKIND_EDGE) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("element \"%s\" of property graph \"%s\" is not an edge", + alias, get_rel_name(pgrelid)), + parser_errposition(pstate, location)); + + peoid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->oid; + + ReleaseSysCache(tuple); + + return peoid; +} + +/* + * Get the element table relation OID from the OID of the element. + */ +static Oid +get_element_relid(Oid peid) +{ + HeapTuple tuple; + Oid pgerelid; + + tuple = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(peid)); + if (!tuple) + elog(ERROR, "cache lookup failed for property graph element %u", peid); + + pgerelid = ((Form_pg_propgraph_element) GETSTRUCT(tuple))->pgerelid; + + ReleaseSysCache(tuple); + + return pgerelid; +} diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 5607273bf9f..0a5e34abe99 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -49,6 +49,7 @@ SecLabelSupportsObjectType(ObjectType objtype) case OBJECT_LARGEOBJECT: case OBJECT_MATVIEW: case OBJECT_PROCEDURE: + case OBJECT_PROPGRAPH: case OBJECT_PUBLICATION: case OBJECT_ROLE: case OBJECT_ROUTINE: diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 237eeeec67e..a9e90b57eca 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -301,6 +301,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} }; @@ -1539,6 +1545,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); @@ -13970,6 +13980,9 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, case OCLASS_EXTENSION: case OCLASS_EVENT_TRIGGER: case OCLASS_PARAMETER_ACL: + case OCLASS_PROPGRAPH_ELEMENT: + case OCLASS_PROPGRAPH_LABEL: + case OCLASS_PROPGRAPH_PROPERTY: case OCLASS_PUBLICATION: case OCLASS_PUBLICATION_NAMESPACE: case OCLASS_PUBLICATION_REL: @@ -14795,6 +14808,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: case RELKIND_PARTITIONED_TABLE: + case RELKIND_PROPGRAPH: /* ok to change owner */ break; case RELKIND_INDEX: diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 13a9b7da83b..73a914acdb6 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -602,7 +602,7 @@ ExecCheckPermissions(List *rangeTable, List *rteperminfos, */ Assert(rte->rtekind == RTE_RELATION || (rte->rtekind == RTE_SUBQUERY && - rte->relkind == RELKIND_VIEW)); + (rte->relkind == RELKIND_VIEW || rte->relkind == RELKIND_PROPGRAPH))); (void) getRTEPermissionInfo(rteperminfos, rte); /* Many-to-one mapping not allowed */ diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index e1a5bc7e95d..521b3ffa017 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -265,6 +265,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 */ @@ -501,6 +504,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; } @@ -1000,6 +1006,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 */ @@ -2035,6 +2044,7 @@ expression_tree_walker_impl(Node *node, case T_RangeTblRef: case T_SortGroupClause: case T_CTESearchClause: + case T_GraphPropertyRef: /* primitive node types with no expression subnodes */ break; case T_WithCheckOption: @@ -2535,6 +2545,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)); @@ -2728,6 +2758,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: @@ -3728,6 +3764,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: @@ -4279,6 +4319,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; @@ -4437,6 +4489,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 25171864dbf..555db730a48 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -555,6 +555,11 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) /* we re-use these RELATION fields, too: */ WRITE_OID_FIELD(relid); break; + case RTE_GRAPH_TABLE: + WRITE_OID_FIELD(relid); + WRITE_NODE_FIELD(graph_pattern); + WRITE_NODE_FIELD(graph_table_columns); + break; case RTE_RESULT: /* no extra fields */ break; diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c index 82f725baaa5..7a81959d057 100644 --- a/src/backend/nodes/queryjumblefuncs.c +++ b/src/backend/nodes/queryjumblefuncs.c @@ -394,6 +394,11 @@ _jumbleRangeTblEntry(JumbleState *jstate, Node *node) case RTE_NAMEDTUPLESTORE: JUMBLE_STRING(enrname); break; + case RTE_GRAPH_TABLE: + JUMBLE_FIELD(relid); + JUMBLE_NODE(graph_pattern); + JUMBLE_NODE(graph_table_columns); + break; case RTE_RESULT: break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index cfb552fde74..3b23cfa0613 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -563,6 +563,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 d404fbf262c..103ec0b9681 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -731,6 +731,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 aa83dd3636f..e02c71cea3b 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -1188,6 +1188,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; } } } @@ -2249,6 +2253,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 401c16686c6..b03dc4cdb05 100644 --- a/src/backend/parser/Makefile +++ b/src/backend/parser/Makefile @@ -23,6 +23,7 @@ OBJS = \ parse_enr.o \ parse_expr.o \ parse_func.o \ + parse_graphtable.o \ parse_merge.o \ parse_node.o \ parse_oper.o \ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 60b31d9f852..4c75938ca7f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -297,6 +297,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt + CreatePropGraphStmt AlterPropGraphStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropOpClassStmt DropOpFamilyStmt DropStmt @@ -660,6 +661,36 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_object_constructor_null_clause_opt json_array_constructor_null_clause_opt +%type 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. @@ -677,6 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %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 /* * If you want to make any keyword changes, update the keyword table in @@ -703,18 +735,18 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS - DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC + DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH DESC DESTINATION DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP - EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT + EACH EDGE ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS - GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS + GENERATED GLOBAL GRANT GRANTED GRAPH GRAPH_TABLE GREATEST GROUP_P GROUPING GROUPS HANDLER HAVING HEADER_P HOLD HOUR_P @@ -735,7 +767,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE - NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE + NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NODE NONE NORMALIZE NORMALIZED NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC @@ -747,19 +779,19 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY - PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION + PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PROPERTIES PROPERTY PUBLICATION QUOTE RANGE READ REAL REASSIGN RECHECK RECURSIVE REF_P REFERENCES REFERENCING - REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA + REFRESH REINDEX RELATIONSHIP RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW - SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P + SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SOURCE SQL_P STABLE STANDALONE_P START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER @@ -772,7 +804,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); UNLISTEN UNLOGGED UNTIL UPDATE USER USING VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING - VERBOSE VERSION_P VIEW VIEWS VOLATILE + VERBOSE VERSION_P VERTEX VIEW VIEWS VOLATILE WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE @@ -809,6 +841,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %token MODE_PLPGSQL_ASSIGN2 %token MODE_PLPGSQL_ASSIGN3 + /* + * FIXME: The brace characters are assigned token symbols because if we + * mention literal braces in the rules then the ecpg parser assembly breaks. + */ +%token LEFT_BRACE RIGHT_BRACE /* Precedence: lowest to highest */ %left UNION EXCEPT @@ -867,7 +904,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */ %nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP SET KEYS OBJECT_P SCALAR VALUE_P WITH WITHOUT -%left Op OPERATOR /* multi-character ops and user-defined operators */ +%left Op OPERATOR LEFT_ARROW RIGHT_ARROW /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' %left '^' @@ -998,6 +1035,7 @@ stmt: | AlterOperatorStmt | AlterTypeStmt | AlterPolicyStmt + | AlterPropGraphStmt | AlterSeqStmt | AlterSystemStmt | AlterTableStmt @@ -1038,6 +1076,7 @@ stmt: | AlterOpFamilyStmt | CreatePolicyStmt | CreatePLangStmt + | CreatePropGraphStmt | CreateSchemaStmt | CreateSeqStmt | CreateStmt @@ -6891,6 +6930,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; } @@ -7747,6 +7787,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)); @@ -9064,6 +9113,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 @@ -9364,6 +9773,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); @@ -9989,6 +10408,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); @@ -10332,6 +10771,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); @@ -13408,6 +13856,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); @@ -14589,6 +15048,10 @@ a_expr: c_expr { $$ = $1; } { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); } | a_expr NOT_EQUALS a_expr { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); } + | a_expr LEFT_ARROW a_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<-", $1, $3, @2); } + | a_expr RIGHT_ARROW a_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); } | a_expr qual_Op a_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); } @@ -15068,6 +15531,10 @@ b_expr: c_expr { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); } | b_expr NOT_EQUALS b_expr { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); } + | b_expr LEFT_ARROW b_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<-", $1, $3, @2); } + | b_expr RIGHT_ARROW b_expr + { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "->", $1, $3, @2); } | b_expr qual_Op b_expr %prec Op { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); } | qual_Op b_expr %prec Op @@ -16172,6 +16639,8 @@ MathOp: '+' { $$ = "+"; } | LESS_EQUALS { $$ = "<="; } | GREATER_EQUALS { $$ = ">="; } | NOT_EQUALS { $$ = "<>"; } + | LEFT_ARROW { $$ = "<-"; } + | RIGHT_ARROW { $$ = "->"; } ; qual_Op: Op @@ -16683,6 +17152,192 @@ json_array_aggregate_order_by_clause_opt: | /* EMPTY */ { $$ = NIL; } ; + +/***************************************************************************** + * + * graph patterns + * + *****************************************************************************/ + +graph_pattern: + path_pattern_list where_clause + { + GraphPattern *gp = makeNode(GraphPattern); + + gp->path_pattern_list = $1; + gp->whereClause = $2; + $$ = (Node *) gp; + } + ; + +path_pattern_list: + path_pattern { $$ = list_make1($1); } + | path_pattern_list ',' path_pattern { $$ = lappend($1, $3); } + ; + +path_pattern: + path_pattern_expression { $$ = $1; } + ; + +/* + * path pattern expression + */ + +path_pattern_expression: + path_term { $$ = $1; } + /* | path_multiset_alternation */ + /* | path_pattern_union */ + ; + +path_term: + path_factor { $$ = list_make1($1); } + | path_term path_factor { $$ = lappend($1, $2); } + ; + +path_factor: + path_primary opt_graph_pattern_quantifier + { + GraphElementPattern *gep = (GraphElementPattern *) $1; + + gep->quantifier = $2; + } + ; + +path_primary: + '(' opt_colid opt_is_label_expression where_clause ')' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = VERTEX_PATTERN; + gep->variable = $2; + gep->labelexpr = $3; + gep->whereClause = $4; + gep->location = @1; + + $$ = (Node *) gep; + } + /* full edge pointing left: <-[ xxx ]- */ + | LEFT_ARROW_BRACKET opt_colid opt_is_label_expression where_clause RIGHT_BRACKET_MINUS + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_LEFT; + gep->variable = $2; + gep->labelexpr = $3; + gep->whereClause = $4; + gep->location = @1; + + $$ = (Node *) gep; + } + /* full edge pointing right: -[ xxx ]-> */ + | MINUS_LEFT_BRACKET opt_colid opt_is_label_expression where_clause BRACKET_RIGHT_ARROW + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_RIGHT; + gep->variable = $2; + gep->labelexpr = $3; + gep->whereClause = $4; + gep->location = @1; + + $$ = (Node *) gep; + } + /* full edge any direction: -[ xxx ]- */ + | MINUS_LEFT_BRACKET opt_colid opt_is_label_expression where_clause RIGHT_BRACKET_MINUS + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_ANY; + gep->variable = $2; + gep->labelexpr = $3; + gep->whereClause = $4; + gep->location = @1; + + $$ = (Node *) gep; + } + /* abbreviated edge patterns */ + | LEFT_ARROW + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_LEFT; + gep->location = @1; + + $$ = (Node *) gep; + } + | RIGHT_ARROW + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_RIGHT; + gep->location = @1; + + $$ = (Node *) gep; + } + | '-' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = EDGE_PATTERN_ANY; + gep->location = @1; + + $$ = (Node *) gep; + } + | '(' path_pattern_expression where_clause ')' + { + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = PAREN_EXPR; + gep->subexpr = $2; + gep->whereClause = $3; + gep->location = @1; + + $$ = (Node *) gep; + } + ; + +opt_colid: + ColId { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + ; + +opt_is_label_expression: + IS label_expression { $$ = $2; } + | ':' label_expression { $$ = $2; } + | /*EMPTY*/ { $$ = NULL; } + ; + +/* + * graph pattern quantifier + */ + +opt_graph_pattern_quantifier: + LEFT_BRACE Iconst RIGHT_BRACE { $$ = list_make2_int($2, $2); } + | LEFT_BRACE ',' Iconst RIGHT_BRACE { $$ = list_make2_int(0, $3); } + | LEFT_BRACE Iconst ',' Iconst RIGHT_BRACE { $$ = list_make2_int($2, $4); } + | /*EMPTY*/ { $$ = NULL; } + ; + +/* + * label expression + */ + +label_expression: + label_term + | label_disjunction + ; + +label_disjunction: + label_expression '|' label_term + { $$ = makeOrExpr($1, $3, @2); } + ; + +label_term: + name + { $$ = makeColumnRef($1, NIL, @1, yyscanner); } + ; + + /***************************************************************************** * * target list for SELECT @@ -17201,6 +17856,7 @@ unreserved_keyword: | DELIMITERS | DEPENDS | DEPTH + | DESTINATION | DETACH | DICTIONARY | DISABLE_P @@ -17210,6 +17866,7 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EDGE | ENABLE_P | ENCODING | ENCRYPTED @@ -17237,6 +17894,7 @@ unreserved_keyword: | GENERATED | GLOBAL | GRANTED + | GRAPH | GROUPS | HANDLER | HEADER_P @@ -17299,6 +17957,7 @@ unreserved_keyword: | NFKC | NFKD | NO + | NODE | NORMALIZED | NOTHING | NOTIFY @@ -17337,6 +17996,8 @@ unreserved_keyword: | PROCEDURE | PROCEDURES | PROGRAM + | PROPERTIES + | PROPERTY | PUBLICATION | QUOTE | RANGE @@ -17348,6 +18009,7 @@ unreserved_keyword: | REFERENCING | REFRESH | REINDEX + | RELATIONSHIP | RELATIVE_P | RELEASE | RENAME @@ -17387,6 +18049,7 @@ unreserved_keyword: | SIMPLE | SKIP | SNAPSHOT + | SOURCE | SQL_P | STABLE | STANDALONE_P @@ -17433,6 +18096,7 @@ unreserved_keyword: | VALUE_P | VARYING | VERSION_P + | VERTEX | VIEW | VIEWS | VOLATILE @@ -17471,6 +18135,7 @@ col_name_keyword: | EXISTS | EXTRACT | FLOAT_P + | GRAPH_TABLE | GREATEST | GROUPING | INOUT @@ -17756,6 +18421,7 @@ bare_label_keyword: | DEPENDS | DEPTH | DESC + | DESTINATION | DETACH | DICTIONARY | DISABLE_P @@ -17767,6 +18433,7 @@ bare_label_keyword: | DOUBLE_P | DROP | EACH + | EDGE | ELSE | ENABLE_P | ENCODING @@ -17802,6 +18469,8 @@ bare_label_keyword: | GENERATED | GLOBAL | GRANTED + | GRAPH + | GRAPH_TABLE | GREATEST | GROUPING | GROUPS @@ -17890,6 +18559,7 @@ bare_label_keyword: | NFKC | NFKD | NO + | NODE | NONE | NORMALIZE | NORMALIZED @@ -17941,6 +18611,8 @@ bare_label_keyword: | PROCEDURE | PROCEDURES | PROGRAM + | PROPERTIES + | PROPERTY | PUBLICATION | QUOTE | RANGE @@ -17954,6 +18626,7 @@ bare_label_keyword: | REFERENCING | REFRESH | REINDEX + | RELATIONSHIP | RELATIVE_P | RELEASE | RENAME @@ -18000,6 +18673,7 @@ bare_label_keyword: | SMALLINT | SNAPSHOT | SOME + | SOURCE | SQL_P | STABLE | STANDALONE_P @@ -18064,6 +18738,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 8e8295640bc..4c03346dec5 100644 --- a/src/backend/parser/meson.build +++ b/src/backend/parser/meson.build @@ -10,6 +10,7 @@ backend_sources += files( 'parse_enr.c', 'parse_expr.c', 'parse_func.c', + 'parse_graphtable.c', 'parse_merge.c', 'parse_node.c', 'parse_oper.c', diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 4b50278fd0d..693a49bb7f2 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -37,6 +37,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" @@ -69,6 +70,8 @@ static ParseNamespaceItem *transformRangeFunction(ParseState *pstate, RangeFunction *r); static ParseNamespaceItem *transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf); +static ParseNamespaceItem *transformRangeGraphTable(ParseState *pstate, + RangeGraphTable *rgt); static TableSampleClause *transformRangeTableSample(ParseState *pstate, RangeTableSample *rts); static ParseNamespaceItem *getNSItemForSpecialRelationTypes(ParseState *pstate, @@ -898,6 +901,76 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf) tf, rtf->alias, is_lateral, true); } +/* + * transformRangeGraphTable -- transform a GRAPH_TABLE clause + */ +static ParseNamespaceItem * +transformRangeGraphTable(ParseState *pstate, RangeGraphTable *rgt) +{ + Relation rel; + Oid graphid; + ParseState *pstate2; + GraphTableParseState *gpstate = palloc0_object(GraphTableParseState); + Node *gp; + List *columns = NIL; + List *colnames = NIL; + ListCell *lc; + int resno = 0; + + rel = parserOpenTable(pstate, rgt->graph_name, AccessShareLock); + if (rel->rd_rel->relkind != RELKIND_PROPGRAPH) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a property graph", + RelationGetRelationName(rel)), + parser_errposition(pstate, rgt->graph_name->location)); + + graphid = RelationGetRelid(rel); + + gpstate->graphid = graphid; + + gp = transformGraphPattern(gpstate, rgt->graph_pattern); + + pstate2 = make_parsestate(NULL); + pstate2->p_pre_columnref_hook = graph_table_property_reference; + pstate2->p_ref_hook_state = gpstate; + + foreach(lc, rgt->columns) + { + ResTarget *rt = lfirst_node(ResTarget, lc); + Node *colexpr; + TargetEntry *te; + char *colname; + + colexpr = transformExpr(pstate2, rt->val, EXPR_KIND_OTHER); + + if (rt->name) + colname = rt->name; + else + { + if (IsA(colexpr, GraphPropertyRef)) + colname = pstrdup(castNode(GraphPropertyRef, colexpr)->propname); + else + { + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("complex graph table column must specify an explicit column name"), + parser_errposition(pstate, rt->location)); + colname = NULL; + } + } + + colnames = lappend(colnames, makeString(colname)); + + te = makeTargetEntry((Expr *) colexpr, ++resno, colname, false); + columns = lappend(columns, te); + } + + table_close(rel, NoLock); + + return addRangeTableEntryForGraphTable(pstate, graphid, castNode(GraphPattern, gp), columns, colnames, rgt->alias, false, true); +} + /* * transformRangeTableSample --- transform a TABLESAMPLE clause * @@ -1117,6 +1190,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_graphtable.c b/src/backend/parser/parse_graphtable.c new file mode 100644 index 00000000000..8679b726279 --- /dev/null +++ b/src/backend/parser/parse_graphtable.c @@ -0,0 +1,251 @@ +/*------------------------------------------------------------------------- + * + * parse_graphtable.c + * parsing of GRAPH_TABLE + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/parser/parse_graphtable.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/pg_propgraph_property.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_graphtable.h" +#include "parser/parse_node.h" +#include "utils/fmgroids.h" +#include "utils/relcache.h" + + +/* + * Get the type of a property. + */ +static Oid +get_property_type(Oid graphid, const char *propname) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[2]; + HeapTuple tuple; + Oid result = InvalidOid; + + rel = table_open(PropgraphPropertyRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_property_pgppgid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(graphid)); + ScanKeyInit(&key[1], + Anum_pg_propgraph_property_pgpname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(propname)); + + scan = systable_beginscan(rel, PropgraphPropertyGraphNameIndexId, true, NULL, 2, key); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_propgraph_property prop = (Form_pg_propgraph_property) GETSTRUCT(tuple); + + result = prop->pgptypid; + break; + } + + systable_endscan(scan); + table_close(rel, RowShareLock); + + if (!result) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("property \"%s\" does not exist", propname)); + + return result; +} + +/* + * Resolve a property reference. + */ +Node * +graph_table_property_reference(ParseState *pstate, ColumnRef *cref) +{ + GraphTableParseState *gpstate = pstate->p_ref_hook_state; + GraphPropertyRef *gpr = makeNode(GraphPropertyRef); + + gpr->location = cref->location; + + if (list_length(cref->fields) == 2) + { + Node *field1 = linitial(cref->fields); + Node *field2 = lsecond(cref->fields); + char *elvarname; + char *propname; + + elvarname = strVal(field1); + propname = strVal(field2); + + if (!list_member(gpstate->variables, field1)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("graph pattern variable \"%s\" does not exist", elvarname), + parser_errposition(pstate, cref->location)); + + gpr->elvarname = elvarname; + gpr->propname = propname; + + } + else + elog(ERROR, "invalid property reference"); + + gpr->typeId = get_property_type(gpstate->graphid, gpr->propname); + + return (Node *) gpr; +} + +/* + * Transform a label expression. + */ +static Node * +transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr) +{ + Node *result; + + if (labelexpr == NULL) + return NULL; + + check_stack_depth(); + + switch (nodeTag(labelexpr)) + { + case T_ColumnRef: + { + ColumnRef *cref = (ColumnRef *) labelexpr; + const char *labelname; + GraphLabelRef *lref; + + Assert(list_length(cref->fields) == 1); + labelname = strVal(linitial(cref->fields)); + + lref = makeNode(GraphLabelRef); + lref->labelname = labelname; + lref->location = cref->location; + + result = (Node *) lref; + break; + } + + case T_BoolExpr: + { + BoolExpr *be = (BoolExpr *) labelexpr; + ListCell *lc; + List *args = NIL; + + foreach(lc, be->args) + { + Node *arg = (Node *) lfirst(lc); + + arg = transformLabelExpr(gpstate, arg); + args = lappend(args, arg); + } + + result = (Node *) makeBoolExpr(be->boolop, args, be->location); + break; + } + + default: + /* should not reach here */ + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(labelexpr)); + result = NULL; /* keep compiler quiet */ + break; + } + + return result; +} + +/* + * Transform a GraphElementPattern. + */ +static Node * +transformGraphElementPattern(GraphTableParseState *gpstate, GraphElementPattern *gep) +{ + ParseState *pstate2; + + pstate2 = make_parsestate(NULL); + pstate2->p_pre_columnref_hook = graph_table_property_reference; + pstate2->p_ref_hook_state = gpstate; + + if (gep->variable) + gpstate->variables = lappend(gpstate->variables, makeString(pstrdup(gep->variable))); + + gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr); + + gep->whereClause = transformExpr(pstate2, gep->whereClause, EXPR_KIND_OTHER); + assign_expr_collations(pstate2, gep->whereClause); + + return (Node *) gep; +} + +/* + * Transform a path term (list of GraphElementPattern's). + */ +static Node * +transformPathTerm(GraphTableParseState *gpstate, List *path_term) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, path_term) + { + Node *n = transformGraphElementPattern(gpstate, lfirst_node(GraphElementPattern, lc)); + + result = lappend(result, n); + } + + return (Node *) result; +} + +/* + * Transform a path pattern list (list of path terms). + */ +static Node * +transformPathPatternList(GraphTableParseState *gpstate, List *path_pattern) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, path_pattern) + { + Node *n = transformPathTerm(gpstate, lfirst(lc)); + + result = lappend(result, n); + } + + return (Node *) result; +} + +/* + * Transform a GraphPattern. + */ +Node * +transformGraphPattern(GraphTableParseState *gpstate, GraphPattern *graph_pattern) +{ + ParseState *pstate2; + + pstate2 = make_parsestate(NULL); + pstate2->p_pre_columnref_hook = graph_table_property_reference; + pstate2->p_ref_hook_state = gpstate; + + graph_pattern->path_pattern_list = (List *) transformPathPatternList(gpstate, graph_pattern->path_pattern_list); + graph_pattern->whereClause = transformExpr(pstate2, graph_pattern->whereClause, EXPR_KIND_OTHER); + assign_expr_collations(pstate2, graph_pattern->whereClause); + + return (Node *) graph_pattern; +} diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 34a0ec59019..c4b2b613904 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -2127,6 +2127,98 @@ addRangeTableEntryForTableFunc(ParseState *pstate, rte->colcollations); } +ParseNamespaceItem * +addRangeTableEntryForGraphTable(ParseState *pstate, + Oid graphid, + GraphPattern *graph_pattern, + List *columns, + List *colnames, + Alias *alias, + bool lateral, + bool inFromCl) +{ + RangeTblEntry *rte = makeNode(RangeTblEntry); + char *refname = alias ? alias->aliasname : pstrdup("graph_table"); + Alias *eref; + int numaliases; + int varattno; + ListCell *lc; + List *coltypes = NIL; + List *coltypmods = NIL; + List *colcollations = NIL; + RTEPermissionInfo *perminfo; + ParseNamespaceItem *nsitem; + + Assert(pstate != NULL); + + rte->rtekind = RTE_GRAPH_TABLE; + rte->relid = graphid; + rte->relkind = RELKIND_PROPGRAPH; + rte->graph_pattern = graph_pattern; + rte->graph_table_columns = columns; + rte->alias = alias; + + eref = alias ? copyObject(alias) : makeAlias(refname, NIL); + + if (!eref->colnames) + eref->colnames = colnames; + + numaliases = list_length(eref->colnames); + + /* fill in any unspecified alias columns */ + varattno = 0; + foreach(lc, colnames) + { + varattno++; + if (varattno > numaliases) + eref->colnames = lappend(eref->colnames, lfirst(lc)); + } + if (varattno < numaliases) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("GRAPH_TABLE \"%s\" has %d columns available but %d columns specified", + refname, varattno, numaliases))); + + rte->eref = eref; + + foreach(lc, columns) + { + TargetEntry *te = lfirst_node(TargetEntry, lc); + Node *colexpr = (Node *) te->expr; + + coltypes = lappend_oid(coltypes, exprType(colexpr)); + coltypmods = lappend_int(coltypmods, exprTypmod(colexpr)); + colcollations = lappend_oid(colcollations, exprCollation(colexpr)); + } + + /* + * Set flags and access permissions. + */ + rte->lateral = lateral; + rte->inFromCl = inFromCl; + + perminfo = addRTEPermissionInfo(&pstate->p_rteperminfos, rte); + perminfo->requiredPerms = ACL_SELECT; + + /* + * Add completed RTE to pstate's range table list, so that we know its + * index. But we don't add it to the join list --- caller must do that if + * appropriate. + */ + pstate->p_rtable = lappend(pstate->p_rtable, rte); + + /* + * Build a ParseNamespaceItem, but don't add it to the pstate's namespace + * list --- caller must do that if appropriate. + */ + nsitem = buildNSItemFromLists(rte, list_length(pstate->p_rtable), + coltypes, coltypmods, colcollations); + + nsitem->p_perminfo = perminfo; + + return nsitem; +} + /* * Add an entry for a VALUES list to the pstate's range table (p_rtable). * Then, construct and return a ParseNamespaceItem for the new RTE. @@ -2941,6 +3033,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, case RTE_VALUES: case RTE_CTE: case RTE_NAMEDTUPLESTORE: + case RTE_GRAPH_TABLE: { /* Tablefunc, Values, CTE, or ENR RTE */ ListCell *aliasp_item = list_head(rte->eref->colnames); @@ -3318,6 +3411,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) case RTE_TABLEFUNC: case RTE_VALUES: case RTE_CTE: + case RTE_GRAPH_TABLE: /* * Subselect, Table Functions, Values, CTE RTEs never have dropped diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 0cd904f8dac..a3da95656bf 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -361,6 +361,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) @@ -1565,6 +1569,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 5eaadf53b20..a8a5baef095 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -353,6 +353,11 @@ typecast "::" dot_dot \.\. colon_equals ":=" +bracket_right_arrow "]->" +left_arrow_bracket "<-[" +minus_left_bracket "-[" +right_bracket_minus "]-" + /* * These operator-like tokens (unlike the above ones) also match the {operator} * rule, which means that they might be overridden by a longer match if they @@ -367,6 +372,9 @@ greater_equals ">=" less_greater "<>" not_equals "!=" +left_arrow "<-" +right_arrow "->" + /* * "self" is the set of chars that should be returned as single-character * tokens. "op_chars" is the set of chars that can make up "Op" tokens, @@ -850,6 +858,26 @@ other . return COLON_EQUALS; } +{bracket_right_arrow} { + SET_YYLLOC(); + return BRACKET_RIGHT_ARROW; + } + +{left_arrow_bracket} { + SET_YYLLOC(); + return LEFT_ARROW_BRACKET; + } + +{minus_left_bracket} { + SET_YYLLOC(); + return MINUS_LEFT_BRACKET; + } + +{right_bracket_minus} { + SET_YYLLOC(); + return RIGHT_BRACKET_MINUS; + } + {equals_greater} { SET_YYLLOC(); return EQUALS_GREATER; @@ -877,11 +905,31 @@ other . return NOT_EQUALS; } +{left_arrow} { + SET_YYLLOC(); + return LEFT_ARROW; + } + +{right_arrow} { + SET_YYLLOC(); + return RIGHT_ARROW; + } + {self} { SET_YYLLOC(); return yytext[0]; } +\{ { + SET_YYLLOC(); + return LEFT_BRACE; + } + +\} { + SET_YYLLOC(); + return RIGHT_BRACE; + } + {operator} { /* * Check for embedded slash-star or dash-dash; those diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile index 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..94f1be3de56 --- /dev/null +++ b/src/backend/rewrite/rewriteGraphTable.c @@ -0,0 +1,422 @@ +/*------------------------------------------------------------------------- + * + * rewriteGraphTable.c + * Support for rewriting GRAPH_TABLE clauses. + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/rewrite/rewriteGraphTable.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/table.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_property.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "parser/parsetree.h" +#include "rewrite/rewriteGraphTable.h" +#include "rewrite/rewriteHandler.h" +#include "rewrite/rewriteManip.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + + +static Oid get_labelid(Oid graphid, const char *labelname); +static List *get_elements_for_label(Oid graphid, const char *labelname); +static Oid get_table_for_element(Oid elid); +static Node *replace_property_refs(Node *node, const List *mappings); +static List *build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum); + +struct elvar_rt_mapping +{ + const char *elvarname; + Oid labelid; + int rt_index; +}; + + +/* + * Convert GRAPH_TABLE clause into a subquery using relational + * operators. + */ +Query * +rewriteGraphTable(Query *parsetree, int rt_index) +{ + RangeTblEntry *rte; + Query *newsubquery; + ListCell *lc; + List *element_patterns; + List *element_ids = NIL; + List *fromlist = NIL; + List *qual_exprs = NIL; + List *elvar_rt_mappings = NIL; + + rte = rt_fetch(rt_index, parsetree->rtable); + + newsubquery = makeNode(Query); + newsubquery->commandType = CMD_SELECT; + + if (list_length(rte->graph_pattern->path_pattern_list) != 1) + elog(ERROR, "unsupported path pattern list length"); + + element_patterns = linitial(rte->graph_pattern->path_pattern_list); + + foreach(lc, element_patterns) + { + GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc); + Oid labelid = InvalidOid; + struct elvar_rt_mapping *erm; + + if (!(gep->kind == VERTEX_PATTERN || gep->kind == EDGE_PATTERN_LEFT || gep->kind == EDGE_PATTERN_RIGHT)) + elog(ERROR, "unsupported element pattern kind: %u", gep->kind); + + if (gep->quantifier) + elog(ERROR, "element pattern quantifier not supported yet"); + + if (IsA(gep->labelexpr, GraphLabelRef)) + { + GraphLabelRef *glr = castNode(GraphLabelRef, gep->labelexpr); + RangeTblEntry *r; + Oid elid; + Oid relid; + RTEPermissionInfo *rpi; + RangeTblRef *rtr; + + r = makeNode(RangeTblEntry); + r->rtekind = RTE_RELATION; + elid = linitial_oid(get_elements_for_label(rte->relid, glr->labelname)); + element_ids = lappend_oid(element_ids, elid); + relid = get_table_for_element(elid); + labelid = get_labelid(rte->relid, glr->labelname); + r->relid = relid; + r->relkind = get_rel_relkind(relid); + r->rellockmode = AccessShareLock; + r->inh = true; + newsubquery->rtable = lappend(newsubquery->rtable, r); + + rpi = makeNode(RTEPermissionInfo); + rpi->relid = relid; + rpi->checkAsUser = 0; + rpi->requiredPerms = ACL_SELECT; + newsubquery->rteperminfos = lappend(newsubquery->rteperminfos, rpi); + + r->perminfoindex = list_length(newsubquery->rteperminfos); + + rtr = makeNode(RangeTblRef); + rtr->rtindex = list_length(newsubquery->rtable); + fromlist = lappend(fromlist, rtr); + } + else + elog(ERROR, "unsupported label expression type: %d", (int) nodeTag(gep->labelexpr)); + + erm = palloc0_object(struct elvar_rt_mapping); + + erm->elvarname = gep->variable; + erm->labelid = labelid; + erm->rt_index = list_length(newsubquery->rtable); + + elvar_rt_mappings = lappend(elvar_rt_mappings, erm); + + if (gep->whereClause) + { + Node *tr; + + tr = replace_property_refs(gep->whereClause, list_make1(erm)); + + qual_exprs = lappend(qual_exprs, tr); + } + } + + /* Iterate over edges only */ + for (int k = 1; k < list_length(element_ids); k += 2) + { + Oid elid = list_nth_oid(element_ids, k); + HeapTuple tuple; + Form_pg_propgraph_element pgeform; + GraphElementPattern *gep = list_nth_node(GraphElementPattern, element_patterns, k); + int srcvertexoffset; + int destvertexoffset; + + tuple = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(elid)); + if (!tuple) + elog(ERROR, "cache lookup failed for property graph element %u", elid); + pgeform = ((Form_pg_propgraph_element) GETSTRUCT(tuple)); + + if (gep->kind == EDGE_PATTERN_RIGHT) + { + srcvertexoffset = -1; + destvertexoffset = +1; + } + else if (gep->kind == EDGE_PATTERN_LEFT) + { + srcvertexoffset = +1; + destvertexoffset = -1; + } + else + { + Assert(false); + srcvertexoffset = 0; + destvertexoffset = 0; + } + + /* + * source link + */ + if (pgeform->pgesrcvertexid != list_nth_oid(element_ids, k + srcvertexoffset)) + { + qual_exprs = lappend(qual_exprs, makeBoolConst(false, false)); + } + else + { + qual_exprs = list_concat(qual_exprs, + build_edge_vertex_link_quals(tuple, k + 1, k + 1 + srcvertexoffset, + Anum_pg_propgraph_element_pgesrckey, Anum_pg_propgraph_element_pgesrcref)); + } + + /* + * dest link + */ + if (pgeform->pgedestvertexid != list_nth_oid(element_ids, k + destvertexoffset)) + { + qual_exprs = lappend(qual_exprs, makeBoolConst(false, false)); + } + else + { + qual_exprs = list_concat(qual_exprs, + build_edge_vertex_link_quals(tuple, k + 1, k + 1 + destvertexoffset, + Anum_pg_propgraph_element_pgedestkey, Anum_pg_propgraph_element_pgedestref)); + } + + ReleaseSysCache(tuple); + } + + newsubquery->jointree = makeFromExpr(fromlist, (Node *) makeBoolExpr(AND_EXPR, qual_exprs, -1)); + + foreach(lc, rte->graph_table_columns) + { + TargetEntry *te = lfirst_node(TargetEntry, lc); + Node *nte; + + nte = replace_property_refs((Node *) te, elvar_rt_mappings); + newsubquery->targetList = lappend(newsubquery->targetList, nte); + } + + AcquireRewriteLocks(newsubquery, true, false); + + rte->rtekind = RTE_SUBQUERY; + rte->subquery = newsubquery; + + /* + * Reset no longer applicable fields, to appease + * WRITE_READ_PARSE_PLAN_TREES. + */ + rte->graph_pattern = NULL; + rte->graph_table_columns = NIL; + + return parsetree; +} + +/* + * Get label OID from graph OID and label name. + * + * TODO: This could match more than one entry. Right now it only returns the + * first one. + */ +static Oid +get_labelid(Oid graphid, const char *labelname) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[2]; + HeapTuple tup; + Oid result = InvalidOid; + + rel = table_open(PropgraphLabelRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_pglpgid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graphid)); + ScanKeyInit(&key[1], + Anum_pg_propgraph_label_pgllabel, + BTEqualStrategyNumber, + F_NAMEEQ, CStringGetDatum(labelname)); + + scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, + true, NULL, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + result = ((Form_pg_propgraph_label) GETSTRUCT(tup))->oid; + break; + } + + systable_endscan(scan); + table_close(rel, RowShareLock); + + return result; +} + +/* + * Get list of element OIDs that have a given label. + */ +static List * +get_elements_for_label(Oid graphid, const char *labelname) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[2]; + HeapTuple tup; + List *result = NIL; + + rel = table_open(PropgraphLabelRelationId, RowShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_pglpgid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graphid)); + ScanKeyInit(&key[1], + Anum_pg_propgraph_label_pgllabel, + BTEqualStrategyNumber, + F_NAMEEQ, CStringGetDatum(labelname)); + + scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, + true, NULL, 2, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + result = lappend_oid(result, ((Form_pg_propgraph_label) GETSTRUCT(tup))->pglelid); + } + + systable_endscan(scan); + table_close(rel, RowShareLock); + + return result; +} + +/* + * Get the element table OID for a given element. + */ +static Oid +get_table_for_element(Oid elid) +{ + return GetSysCacheOid1(PROPGRAPHELOID, Anum_pg_propgraph_element_pgerelid, ObjectIdGetDatum(elid)); +} + +/* + * Mutating property references into table variables + */ + +struct replace_property_refs_context +{ + const List *mappings; +}; + +static Node * +replace_property_refs_mutator(Node *node, struct replace_property_refs_context *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, GraphPropertyRef)) + { + GraphPropertyRef *gpr = (GraphPropertyRef *) node; + HeapTuple tup; + Node *n; + ListCell *lc; + struct elvar_rt_mapping *found_mapping = NULL; + + foreach(lc, context->mappings) + { + struct elvar_rt_mapping *m = lfirst(lc); + + if (m->elvarname && strcmp(gpr->elvarname, m->elvarname) == 0) + { + found_mapping = m; + break; + } + } + if (!found_mapping) + elog(ERROR, "undefined element variable \"%s\"", gpr->elvarname); + + tup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(found_mapping->labelid), CStringGetDatum(gpr->propname)); + if (!tup) + elog(ERROR, "property \"%s\" of label %u not found", gpr->propname, found_mapping->labelid); + + n = stringToNode(TextDatumGetCString(SysCacheGetAttrNotNull(PROPGRAPHPROPNAME, tup, Anum_pg_propgraph_property_pgpexpr))); + ChangeVarNodes(n, 1, found_mapping->rt_index, 0); + + ReleaseSysCache(tup); + + return n; + } + return expression_tree_mutator(node, replace_property_refs_mutator, context); +} + +static Node * +replace_property_refs(Node *node, const List *mappings) +{ + struct replace_property_refs_context context; + + context.mappings = mappings; + + return expression_tree_mutator(node, replace_property_refs_mutator, &context); +} + +/* + * Build join qualification expressions between edge and vertex tables. + */ +static List * +build_edge_vertex_link_quals(HeapTuple edgetup, int edgerti, int refrti, AttrNumber catalog_key_attnum, AttrNumber catalog_ref_attnum) +{ + List *quals = NIL; + Form_pg_propgraph_element pgeform; + Datum datum; + Datum *d1, + *d2; + int n1, + n2; + + pgeform = (Form_pg_propgraph_element) GETSTRUCT(edgetup); + + datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_key_attnum); + deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d1, NULL, &n1); + + datum = SysCacheGetAttrNotNull(PROPGRAPHELOID, edgetup, catalog_ref_attnum); + deconstruct_array_builtin(DatumGetArrayTypeP(datum), INT2OID, &d2, NULL, &n2); + + if (n1 != n2) + elog(ERROR, "array size key (%d) vs ref (%d) mismatch for element ID %u", catalog_key_attnum, catalog_ref_attnum, pgeform->oid); + + for (int i = 0; i < n1; i++) + { + AttrNumber keyattn = DatumGetInt16(d1[i]); + AttrNumber refattn = DatumGetInt16(d2[i]); + Oid atttypid; + TypeCacheEntry *typentry; + OpExpr *op; + + /* + * TODO: Assumes types the same on both sides; no collations yet. Some + * of this could probably be shared with foreign key triggers. + */ + atttypid = get_atttype(pgeform->pgerelid, keyattn); + typentry = lookup_type_cache(atttypid, TYPECACHE_EQ_OPR); + + op = makeNode(OpExpr); + op->location = -1; + op->opno = typentry->eq_opr; + op->opresulttype = BOOLOID; + op->args = list_make2(makeVar(edgerti, keyattn, atttypid, -1, 0, 0), + makeVar(refrti, refattn, atttypid, -1, 0, 0)); + quals = lappend(quals, op); + } + + return quals; +} diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index f60b34deb64..6b8192c0748 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -38,6 +38,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" @@ -2027,6 +2028,17 @@ fireRIRrules(Query *parsetree, List *activeRIRs) rte = rt_fetch(rt_index, parsetree->rtable); + /* + * Convert GRAPH_TABLE clause into a subquery using relational + * operators. (This will change the rtekind to subquery, so it must + * be done before the subquery handling below.) + */ + if (rte->rtekind == RTE_GRAPH_TABLE) + { + parsetree = rewriteGraphTable(parsetree, rt_index); + continue; + } + /* * A subquery RTE can't have associated rules, so there's nothing to * do to this level of the query, but we must recurse into the diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 8de821f9607..06c2c3a2b9b 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -47,6 +47,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" @@ -155,6 +156,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: @@ -185,6 +187,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: @@ -1743,6 +1746,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; @@ -2011,6 +2022,7 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel) case OBJECT_VIEW: case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: + case OBJECT_PROPGRAPH: RemoveRelations(stmt); break; default: @@ -2288,6 +2300,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; @@ -2564,6 +2579,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; @@ -2945,6 +2963,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; @@ -3642,6 +3668,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 a928a8c55df..aa11c4def60 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -35,6 +35,9 @@ #include "catalog/pg_operator.h" #include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_label.h" +#include "catalog/pg_propgraph_property.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -346,6 +349,9 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno, bool attrsOnly, bool keysOnly, bool showTblSpc, bool inherits, int prettyFlags, bool missing_ok); +static void make_propgraphdef_elements(StringInfo buf, Oid pgrelid, char pgekind); +static void make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid); +static void make_propgraphdef_properties(StringInfo buf, Oid labelid, Oid elrelid); static char *pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok); static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags, @@ -1570,6 +1576,298 @@ pg_get_querydef(Query *query, bool pretty) return buf.data; } +/* + * pg_get_propgraphdef - get the definition of a property graph + */ +Datum +pg_get_propgraphdef(PG_FUNCTION_ARGS) +{ + Oid pgrelid = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple classtup; + Form_pg_class classform; + char *name; + char *nsp; + + initStringInfo(&buf); + + classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(pgrelid)); + if (!HeapTupleIsValid(classtup)) + PG_RETURN_NULL(); + + classform = (Form_pg_class) GETSTRUCT(classtup); + name = NameStr(classform->relname); + + if (classform->relkind != RELKIND_PROPGRAPH) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a property graph", name))); + + nsp = get_namespace_name(classform->relnamespace); + + appendStringInfo(&buf, "CREATE PROPERTY GRAPH %s", + quote_qualified_identifier(nsp, name)); + + ReleaseSysCache(classtup); + + make_propgraphdef_elements(&buf, pgrelid, PGEKIND_VERTEX); + make_propgraphdef_elements(&buf, pgrelid, PGEKIND_EDGE); + + PG_RETURN_TEXT_P(string_to_text(buf.data)); +} + +/* + * Generates a VERTEX TABLES (...) or EDGE TABLES (...) clause. Pass in the + * property graph relation OID and the element kind (vertex or edge). Result + * is appended to buf. + */ +static void +make_propgraphdef_elements(StringInfo buf, Oid pgrelid, char pgekind) +{ + Relation pgerel; + ScanKeyData scankey[1]; + SysScanDesc scan; + bool first; + HeapTuple tup; + + pgerel = table_open(PropgraphElementRelationId, AccessShareLock); + + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_element_pgepgid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(pgrelid)); + + scan = systable_beginscan(pgerel, PropgraphElementAliasIndexId, true, NULL, 1, scankey); + + first = true; + while ((tup = systable_getnext(scan))) + { + Form_pg_propgraph_element pgeform = (Form_pg_propgraph_element) GETSTRUCT(tup); + char *relname; + Datum datum; + bool isnull; + + if (pgeform->pgekind != pgekind) + continue; + + if (first) + { + appendStringInfo(buf, "\n %s TABLES (\n", pgekind == PGEKIND_VERTEX ? "VERTEX" : "EDGE"); + first = false; + } + else + appendStringInfo(buf, ",\n"); + + relname = get_rel_name(pgeform->pgerelid); + if (relname && strcmp(relname, NameStr(pgeform->pgealias)) == 0) + appendStringInfo(buf, " %s", + generate_relation_name(pgeform->pgerelid, NIL)); + else + appendStringInfo(buf, " %s AS %s", + generate_relation_name(pgeform->pgerelid, NIL), + NameStr(pgeform->pgealias)); + + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgekey, RelationGetDescr(pgerel), &isnull); + if (!isnull) + { + appendStringInfoString(buf, " KEY ("); + decompile_column_index_array(datum, pgeform->pgerelid, buf); + appendStringInfoString(buf, ")"); + } + else + elog(ERROR, "null pgekey for element %u", pgeform->oid); + + if (pgekind == PGEKIND_EDGE) + { + Datum srckey; + Datum srcref; + Datum destkey; + Datum destref; + HeapTuple tup2; + Form_pg_propgraph_element pgeform2; + + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgesrckey, RelationGetDescr(pgerel), &isnull); + srckey = isnull ? 0 : datum; + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgesrcref, RelationGetDescr(pgerel), &isnull); + srcref = isnull ? 0 : datum; + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgedestkey, RelationGetDescr(pgerel), &isnull); + destkey = isnull ? 0 : datum; + datum = heap_getattr(tup, Anum_pg_propgraph_element_pgedestref, RelationGetDescr(pgerel), &isnull); + destref = isnull ? 0 : datum; + + appendStringInfoString(buf, " SOURCE"); + tup2 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(pgeform->pgesrcvertexid)); + if (!tup2) + elog(ERROR, "cache lookup failed for property graph element %u", pgeform->pgesrcvertexid); + pgeform2 = (Form_pg_propgraph_element) GETSTRUCT(tup2); + if (srckey) + { + appendStringInfoString(buf, " KEY ("); + decompile_column_index_array(srckey, pgeform->pgerelid, buf); + appendStringInfo(buf, ") REFERENCES %s (", NameStr(pgeform2->pgealias)); + decompile_column_index_array(srcref, pgeform2->pgerelid, buf); + appendStringInfoString(buf, ")"); + } + else + appendStringInfo(buf, " %s ", NameStr(pgeform2->pgealias)); + ReleaseSysCache(tup2); + + appendStringInfoString(buf, " DESTINATION"); + tup2 = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(pgeform->pgedestvertexid)); + if (!tup2) + elog(ERROR, "cache lookup failed for property graph element %u", pgeform->pgedestvertexid); + pgeform2 = (Form_pg_propgraph_element) GETSTRUCT(tup2); + if (destkey) + { + appendStringInfoString(buf, " KEY ("); + decompile_column_index_array(destkey, pgeform->pgerelid, buf); + appendStringInfo(buf, ") REFERENCES %s (", NameStr(pgeform2->pgealias)); + decompile_column_index_array(destref, pgeform2->pgerelid, buf); + appendStringInfoString(buf, ")"); + } + else + appendStringInfo(buf, " %s", NameStr(pgeform2->pgealias)); + ReleaseSysCache(tup2); + } + + make_propgraphdef_labels(buf, pgeform->oid, NameStr(pgeform->pgealias), pgeform->pgerelid); + } + if (!first) + appendStringInfo(buf, "\n )"); + + systable_endscan(scan); + table_close(pgerel, AccessShareLock); +} + +/* + * Generates label and properties list. Pass in the element OID, the element + * alias, and the graph relation OID. Result is append to buf. + */ +static void +make_propgraphdef_labels(StringInfo buf, Oid elid, const char *elalias, Oid elrelid) +{ + Relation pglrel; + ScanKeyData scankey[1]; + SysScanDesc scan; + int count; + HeapTuple tup; + + pglrel = table_open(PropgraphLabelRelationId, AccessShareLock); + + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_label_pglelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(elid)); + + count = 0; + scan = systable_beginscan(pglrel, PropgraphLabelLabelIndexId, true, NULL, 1, scankey); + while ((tup = systable_getnext(scan))) + { + count++; + } + systable_endscan(scan); + + /* XXX need to re-init scan key for second scan */ + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_label_pglelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(elid)); + + scan = systable_beginscan(pglrel, PropgraphLabelLabelIndexId, true, NULL, 1, scankey); + + while ((tup = systable_getnext(scan))) + { + Form_pg_propgraph_label pglform = (Form_pg_propgraph_label) GETSTRUCT(tup); + + if (strcmp(NameStr(pglform->pgllabel), elalias) == 0) + { + /* If the default label is the only label, don't print anything. */ + if (count != 1) + appendStringInfo(buf, " DEFAULT LABEL"); + } + else + appendStringInfo(buf, " LABEL %s", + quote_identifier(NameStr(pglform->pgllabel))); + + make_propgraphdef_properties(buf, pglform->oid, elrelid); + } + + systable_endscan(scan); + + table_close(pglrel, AccessShareLock); +} + +/* + * Generates element table properties clause (PROPERTIES (...) or NO + * PROPERTIES). Pass in label ODI and element table OID. Result is appended + * to buf. + */ +static void +make_propgraphdef_properties(StringInfo buf, Oid labelid, Oid elrelid) +{ + List *context; + Relation pgprel; + ScanKeyData scankey[1]; + SysScanDesc scan; + HeapTuple tup; + bool first; + + context = deparse_context_for(get_relation_name(elrelid), elrelid); + + pgprel = table_open(PropgraphPropertyRelationId, AccessShareLock); + + ScanKeyInit(&scankey[0], + Anum_pg_propgraph_property_pgplabelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(labelid)); + + /* + * Note: Use of index on property name also ensures that properties are + * put out in a deterministic order. + */ + scan = systable_beginscan(pgprel, PropgraphPropertyNameIndexId, true, NULL, 1, scankey); + + first = true; + while ((tup = systable_getnext(scan))) + { + Form_pg_propgraph_property pgpform = (Form_pg_propgraph_property) GETSTRUCT(tup); + Datum exprDatum; + bool isnull; + char *tmp; + Node *expr; + + if (first) + { + appendStringInfo(buf, " PROPERTIES ("); + first = false; + } + else + appendStringInfo(buf, ", "); + + exprDatum = heap_getattr(tup, Anum_pg_propgraph_property_pgpexpr, RelationGetDescr(pgprel), &isnull); + Assert(!isnull); + tmp = TextDatumGetCString(exprDatum); + expr = stringToNode(tmp); + pfree(tmp); + + if (IsA(expr, Var) && strcmp(NameStr(pgpform->pgpname), get_attname(elrelid, castNode(Var, expr)->varattno, false)) == 0) + appendStringInfo(buf, "%s", + NameStr(pgpform->pgpname)); + else + appendStringInfo(buf, "%s AS %s", + deparse_expression_pretty(expr, context, false, false, 0, 0), + NameStr(pgpform->pgpname)); + } + + if (first) + appendStringInfo(buf, " NO PROPERTIES"); + else + appendStringInfo(buf, ")"); + + systable_endscan(scan); + table_close(pgprel, AccessShareLock); +} + /* * pg_get_statisticsobjdef * Get the definition of an extended statistics object @@ -7232,6 +7530,171 @@ get_utility_query_def(Query *query, deparse_context *context) } } + +/* + * Parse back a graph label expression + */ +static void +get_graph_label_expr(Node *label_expr, deparse_context *context) +{ + StringInfo buf = context->buf; + + check_stack_depth(); + + switch (nodeTag(label_expr)) + { + case T_GraphLabelRef: + { + GraphLabelRef *lref = (GraphLabelRef *) label_expr; + + appendStringInfoString(buf, quote_identifier(lref->labelname)); + break; + } + + case T_BoolExpr: + { + BoolExpr *be = (BoolExpr *) label_expr; + ListCell *lc; + bool first = true; + + Assert(be->boolop == OR_EXPR); + + foreach(lc, be->args) + { + if (!first) + { + if (be->boolop == OR_EXPR) + appendStringInfoString(buf, "|"); + } + else + first = false; + get_graph_label_expr(lfirst(lc), context); + } + + break; + } + + default: + elog(ERROR, "unrecognized node type: %d", (int) nodeTag(label_expr)); + break; + } +} + +/* + * Parse back a path pattern expression + */ +static void +get_path_pattern_expr_def(List *path_pattern_expr, deparse_context *context) +{ + StringInfo buf = context->buf; + ListCell *lc; + + foreach(lc, path_pattern_expr) + { + GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc); + const char *sep = ""; + + switch (gep->kind) + { + case VERTEX_PATTERN: + appendStringInfoString(buf, "("); + break; + case EDGE_PATTERN_LEFT: + appendStringInfoString(buf, "<-["); + break; + case EDGE_PATTERN_RIGHT: + case EDGE_PATTERN_ANY: + appendStringInfoString(buf, "-["); + break; + case PAREN_EXPR: + appendStringInfoString(buf, "("); + break; + } + + if (gep->variable) + { + appendStringInfoString(buf, gep->variable); + sep = " "; + } + + if (gep->labelexpr) + { + appendStringInfoString(buf, sep); + appendStringInfoString(buf, "IS "); + get_graph_label_expr(gep->labelexpr, context); + sep = " "; + } + + if (gep->subexpr) + { + appendStringInfoString(buf, sep); + get_path_pattern_expr_def(gep->subexpr, context); + sep = " "; + } + + if (gep->whereClause) + { + appendStringInfoString(buf, sep); + appendStringInfoString(buf, "WHERE "); + get_rule_expr(gep->whereClause, context, false); + } + + switch (gep->kind) + { + case VERTEX_PATTERN: + appendStringInfoString(buf, ")"); + break; + case EDGE_PATTERN_LEFT: + case EDGE_PATTERN_ANY: + appendStringInfoString(buf, "]-"); + break; + case EDGE_PATTERN_RIGHT: + appendStringInfoString(buf, "]->"); + break; + case PAREN_EXPR: + appendStringInfoString(buf, ")"); + break; + } + + if (gep->quantifier) + { + int lower = linitial_int(gep->quantifier); + int upper = lsecond_int(gep->quantifier); + + appendStringInfo(buf, "{%d,%d}", lower, upper); + } + } +} + +/* + * Parse back a graph pattern + */ +static void +get_graph_pattern_def(GraphPattern *graph_pattern, deparse_context *context) +{ + StringInfo buf = context->buf; + ListCell *lc; + bool first = true; + + foreach(lc, graph_pattern->path_pattern_list) + { + List *path_pattern_expr = lfirst_node(List, lc); + + if (!first) + appendStringInfoString(buf, ", "); + else + first = false; + + get_path_pattern_expr_def(path_pattern_expr, context); + } + + if (graph_pattern->whereClause) + { + appendStringInfoString(buf, "WHERE "); + get_rule_expr(graph_pattern->whereClause, context, false); + } +} + /* * Display a Var appropriately. * @@ -7794,6 +8257,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: /* @@ -9813,6 +10277,14 @@ get_rule_expr(Node *node, deparse_context *context, get_tablefunc((TableFunc *) node, context, showimplicit); break; + case T_GraphPropertyRef: + { + GraphPropertyRef *gpr = (GraphPropertyRef *) node; + + appendStringInfo(buf, "%s.%s", quote_identifier(gpr->elvarname), quote_identifier(gpr->propname)); + break; + } + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); break; @@ -11439,6 +11911,36 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) case RTE_TABLEFUNC: get_tablefunc(rte->tablefunc, context, true); break; + case RTE_GRAPH_TABLE: + appendStringInfoString(buf, "GRAPH_TABLE ("); + appendStringInfoString(buf, generate_relation_name(rte->relid, context->namespaces)); + appendStringInfoString(buf, " MATCH "); + get_graph_pattern_def(rte->graph_pattern, context); + appendStringInfoString(buf, " COLUMNS ("); + { + ListCell *lc; + bool first = true; + + foreach(lc, rte->graph_table_columns) + { + TargetEntry *te = lfirst_node(TargetEntry, lc); + deparse_context context = {0}; + + if (!first) + appendStringInfoString(buf, ", "); + else + first = false; + + context.buf = buf; + + get_rule_expr((Node *) te->expr, &context, false); + appendStringInfoString(buf, " AS "); + appendStringInfoString(buf, quote_identifier(te->resname)); + } + } + appendStringInfoString(buf, ")"); + appendStringInfoString(buf, ")"); + break; case RTE_VALUES: /* Values list RTE */ appendStringInfoChar(buf, '('); diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 0ed18b72d63..bc34fc7d6b9 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -519,7 +519,8 @@ flagInhAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tblinfo, int numTables /* Some kinds never have parents */ if (tbinfo->relkind == RELKIND_SEQUENCE || tbinfo->relkind == RELKIND_VIEW || - tbinfo->relkind == RELKIND_MATVIEW) + tbinfo->relkind == RELKIND_MATVIEW || + tbinfo->relkind == RELKIND_PROPGRAPH) continue; /* Don't bother computing anything for non-target tables, either */ diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index d97ebaff5b8..ebd74e40538 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3456,6 +3456,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te) strcmp(type, "DOMAIN") == 0 || strcmp(type, "FOREIGN TABLE") == 0 || strcmp(type, "MATERIALIZED VIEW") == 0 || + strcmp(type, "PROPERTY GRAPH") == 0 || strcmp(type, "SEQUENCE") == 0 || strcmp(type, "STATISTICS") == 0 || strcmp(type, "TABLE") == 0 || diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 2225a12718b..80f9334e6ce 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1601,10 +1601,10 @@ expand_table_name_patterns(Archive *fout, "\n LEFT JOIN pg_catalog.pg_namespace n" "\n ON n.oid OPERATOR(pg_catalog.=) c.relnamespace" "\nWHERE c.relkind OPERATOR(pg_catalog.=) ANY" - "\n (array['%c', '%c', '%c', '%c', '%c', '%c'])\n", + "\n (array['%c', '%c', '%c', '%c', '%c', '%c', '%c'])\n", RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW, RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE, - RELKIND_PARTITIONED_TABLE); + RELKIND_PARTITIONED_TABLE, RELKIND_PROPGRAPH); initPQExpBuffer(&dbbuf); processSQLNamePattern(GetConnection(fout), query, cell->val, true, false, "n.nspname", "c.relname", NULL, @@ -2748,6 +2748,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo) if (tbinfo->dataObj != NULL) return; + /* Skip property graphs (no data to dump) */ + if (tbinfo->relkind == RELKIND_PROPGRAPH) + return; /* Skip VIEWs (no data to dump) */ if (tbinfo->relkind == RELKIND_VIEW) return; @@ -6805,7 +6808,8 @@ getTables(Archive *fout, int *numTables) CppAsString2(RELKIND_COMPOSITE_TYPE) ", " CppAsString2(RELKIND_MATVIEW) ", " CppAsString2(RELKIND_FOREIGN_TABLE) ", " - CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n" + CppAsString2(RELKIND_PARTITIONED_TABLE) ", " + CppAsString2(RELKIND_PROPGRAPH) ")\n" "ORDER BY c.oid"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -15869,8 +15873,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) reltypename = "VIEW"; - appendPQExpBuffer(delq, "DROP VIEW %s;\n", qualrelname); - if (dopt->binary_upgrade) binary_upgrade_set_pg_class_oids(fout, q, tbinfo->dobj.catId.oid, false); @@ -15896,6 +15898,47 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) appendPQExpBuffer(q, "\n WITH %s CHECK OPTION", tbinfo->checkoption); appendPQExpBufferStr(q, ";\n"); } + else if (tbinfo->relkind == RELKIND_PROPGRAPH) + { + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + int len; + + reltypename = "PROPERTY GRAPH"; + + if (dopt->binary_upgrade) + binary_upgrade_set_pg_class_oids(fout, q, + tbinfo->dobj.catId.oid, false); + + appendPQExpBuffer(query, + "SELECT pg_catalog.pg_get_propgraphdef('%u'::pg_catalog.oid) AS pgdef", + tbinfo->dobj.catId.oid); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + if (PQntuples(res) != 1) + { + if (PQntuples(res) < 1) + pg_fatal("query to obtain definition of property graph \"%s\" returned no data", + tbinfo->dobj.name); + else + pg_fatal("query to obtain definition of property graph \"%s\" returned more than one definition", + tbinfo->dobj.name); + } + + len = PQgetlength(res, 0, 0); + + if (len == 0) + pg_fatal("definition of property graph \"%s\" appears to be empty (length zero)", + tbinfo->dobj.name); + + appendPQExpBufferStr(q, PQgetvalue(res, 0, 0)); + + PQclear(res); + destroyPQExpBuffer(query); + + appendPQExpBufferStr(q, ";\n"); + } else { char *partkeydef = NULL; @@ -15971,8 +16014,6 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) numParents = tbinfo->numParents; parents = tbinfo->parents; - appendPQExpBuffer(delq, "DROP %s %s;\n", reltypename, qualrelname); - if (dopt->binary_upgrade) binary_upgrade_set_pg_class_oids(fout, q, tbinfo->dobj.catId.oid, false); @@ -16590,6 +16631,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) appendPQExpBuffer(q, "\nALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;\n", qualrelname); + appendPQExpBuffer(delq, "DROP %s %s;\n", reltypename, qualrelname); + if (dopt->binary_upgrade) binary_upgrade_extension_member(q, &tbinfo->dobj, reltypename, qrelname, @@ -18423,6 +18466,16 @@ getDependencies(Archive *fout) "classid = 'pg_amproc'::regclass AND objid = p.oid " "AND NOT (refclassid = 'pg_opfamily'::regclass AND amprocfamily = refobjid)\n"); + /* + * Translate dependencies of pg_propgraph_element entries into + * dependencies of their parent pg_class entry. + */ + appendPQExpBufferStr(query, "UNION ALL\n" + "SELECT 'pg_class'::regclass AS classid, pgepgid AS objid, refclassid, refobjid, deptype " + "FROM pg_depend d, pg_propgraph_element pge " + "WHERE deptype NOT IN ('p', 'e', 'i') AND " + "classid = 'pg_propgraph_element'::regclass AND objid = pge.oid\n"); + /* Sort the output for efficiency below */ appendPQExpBufferStr(query, "ORDER BY 1,2"); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 00b5092713d..ea0d6beb092 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2959,6 +2959,17 @@ }, }, + '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 5c906e48068..ea53da0f5ad 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -775,7 +775,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd) success = describeTableDetails(pattern, show_verbose, show_system); else /* standard listing of interesting things */ - success = listTables("tvmsE", NULL, show_verbose, show_system); + success = listTables("tvmsEG", NULL, show_verbose, show_system); break; case 'A': { @@ -906,6 +906,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd) case 'i': case 's': case 'E': + case 'G': success = listTables(&cmd[1], pattern, show_verbose, show_system); break; case 'r': diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index b6a4eb1d565..60d23307613 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1025,6 +1025,7 @@ permissionsList(const char *pattern, bool showSystem) " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'" " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'" + " WHEN " CppAsString2(RELKIND_PROPGRAPH) " THEN '%s'" " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'" " END as \"%s\",\n" " ", @@ -1035,6 +1036,7 @@ permissionsList(const char *pattern, bool showSystem) gettext_noop("materialized view"), gettext_noop("sequence"), gettext_noop("foreign table"), + gettext_noop("property graph"), gettext_noop("partitioned table"), gettext_noop("Type")); @@ -1126,6 +1128,7 @@ permissionsList(const char *pattern, bool showSystem) CppAsString2(RELKIND_MATVIEW) "," CppAsString2(RELKIND_SEQUENCE) "," CppAsString2(RELKIND_FOREIGN_TABLE) "," + CppAsString2(RELKIND_PROPGRAPH) "," CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n"); if (!showSystem && !pattern) @@ -2009,6 +2012,10 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""), schemaname, relationname); break; + case RELKIND_PROPGRAPH: + printfPQExpBuffer(&title, _("Property graph \"%s.%s\""), + schemaname, relationname); + break; default: /* untranslated unknown relkind */ printfPQExpBuffer(&title, "?%c? \"%s.%s\"", @@ -3099,6 +3106,32 @@ describeOneTableDetails(const char *schemaname, } } + /* Add property graph definition in verbose mode */ + if (tableinfo.relkind == RELKIND_PROPGRAPH && verbose) + { + PGresult *result; + char *pgdef = NULL; + + printfPQExpBuffer(&buf, + "SELECT pg_catalog.pg_get_propgraphdef('%s'::pg_catalog.oid);", + oid); + result = PSQLexec(buf.data); + if (!result) + goto error_return; + + if (PQntuples(result) > 0) + pgdef = pg_strdup(PQgetvalue(result, 0, 0)); + + PQclear(result); + + if (pgdef) + { + printTableAddFooter(&cont, _("Property graph definition:")); + printfPQExpBuffer(&buf, " %s", pgdef); + printTableAddFooter(&cont, buf.data); + } + } + /* Get view_def if table is a view or materialized view */ if ((tableinfo.relkind == RELKIND_VIEW || tableinfo.relkind == RELKIND_MATVIEW) && verbose) @@ -3950,6 +3983,7 @@ describeRoleGrants(const char *pattern, bool showSystem) * m - materialized views * s - sequences * E - foreign table (Note: different from 'f', the relkind value) + * G - property graphs * (any order of the above is fine) */ bool @@ -3961,6 +3995,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys bool showMatViews = strchr(tabtypes, 'm') != NULL; bool showSeq = strchr(tabtypes, 's') != NULL; bool showForeign = strchr(tabtypes, 'E') != NULL; + bool showPropGraphs = strchr(tabtypes, 'G') != NULL; PQExpBufferData buf; PGresult *res; @@ -3969,8 +4004,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys bool translate_columns[] = {false, false, true, false, false, false, false, false, false}; /* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */ - if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign)) - showTables = showViews = showMatViews = showSeq = showForeign = true; + if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign || showPropGraphs)) + showTables = showViews = showMatViews = showSeq = showForeign = showPropGraphs = true; initPQExpBuffer(&buf); @@ -3987,6 +4022,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'" + " WHEN " CppAsString2(RELKIND_PROPGRAPH) " THEN '%s'" " END as \"%s\",\n" " pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"", gettext_noop("Schema"), @@ -4000,6 +4036,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys gettext_noop("foreign table"), gettext_noop("partitioned table"), gettext_noop("partitioned index"), + gettext_noop("property graph"), gettext_noop("Type"), gettext_noop("Owner")); cols_so_far = 4; @@ -4083,6 +4120,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */ if (showForeign) appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ","); + if (showPropGraphs) + appendPQExpBufferStr(&buf, CppAsString2(RELKIND_PROPGRAPH) ","); appendPQExpBufferStr(&buf, "''"); /* dummy */ appendPQExpBufferStr(&buf, ")\n"); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 4e79a819d87..a8f04c156b6 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 151a5211ee4..01c3924feef 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 = { @@ -1255,6 +1263,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}, @@ -2304,6 +2313,12 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "WITH", "CHECK")) COMPLETE_WITH("("); + /* ALTER PROPERTY GRAPH */ + else if (Matches("ALTER", "PROPERTY", "GRAPH")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_propgraphs); + else if (Matches("ALTER", "PROPERTY", "GRAPH", MatchAny)) + COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA"); + /* ALTER RULE , add ON */ else if (Matches("ALTER", "RULE", MatchAny)) COMPLETE_WITH("ON"); @@ -2781,7 +2796,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", @@ -2819,6 +2834,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")) @@ -3133,6 +3150,31 @@ psql_completion(const char *text, int start, int end) else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "USING")) COMPLETE_WITH("("); +/* CREATE PROPERTY GRAPH */ + else if (Matches("CREATE", "PROPERTY")) + COMPLETE_WITH("GRAPH"); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny)) + COMPLETE_WITH("VERTEX"); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE")) + COMPLETE_WITH("TABLES"); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES")) + COMPLETE_WITH("("); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + else if (Matches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(", MatchAny)) + COMPLETE_WITH(",", ")"); + else if (HeadMatches("CREATE", "PROPERTY", "GRAPH", MatchAny, "VERTEX|NODE", "TABLES", "(") && + TailMatches(")")) + COMPLETE_WITH("EDGE"); /* FIXME: doesn't seem to work */ + else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && + TailMatches("EDGE|RELATIONSHIP")) + COMPLETE_WITH("TABLES"); + else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && + TailMatches("EDGE|RELATIONSHIP", "TABLES")) + COMPLETE_WITH("("); + else if (HeadMatches("CREATE", "PROPERTY", "GRAPH") && + TailMatches("EDGE|RELATIONSHIP", "TABLES", "(")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* CREATE PUBLICATION */ else if (Matches("CREATE", "PUBLICATION", MatchAny)) @@ -3800,6 +3842,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"); @@ -4034,6 +4082,7 @@ psql_completion(const char *text, int start, int end) "LARGE OBJECT", "PARAMETER", "PROCEDURE", + "PROPERTY GRAPH", "ROUTINE", "SCHEMA", "SEQUENCE", @@ -4160,6 +4209,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"); @@ -4465,8 +4522,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"); @@ -4883,6 +4942,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 c9df0594fda..3a10a924180 100644 --- a/src/fe_utils/psqlscan.l +++ b/src/fe_utils/psqlscan.l @@ -788,6 +788,14 @@ other . ECHO; } +\{ { + ECHO; + } + +\} { + ECHO; + } + {operator} { /* * Check for embedded slash-star or dash-dash; those diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index fd588659014..b269994ffd8 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -123,6 +123,9 @@ typedef enum ObjectClass OCLASS_EVENT_TRIGGER, /* pg_event_trigger */ OCLASS_PARAMETER_ACL, /* pg_parameter_acl */ OCLASS_POLICY, /* pg_policy */ + OCLASS_PROPGRAPH_ELEMENT, /* pg_propgraph_element */ + OCLASS_PROPGRAPH_LABEL, /* pg_propgraph_label */ + OCLASS_PROPGRAPH_PROPERTY, /* pg_propgraph_property */ OCLASS_PUBLICATION, /* pg_publication */ OCLASS_PUBLICATION_NAMESPACE, /* pg_publication_namespace */ OCLASS_PUBLICATION_REL, /* pg_publication_rel */ diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build index 6b3c56c20e8..e4b7b626be3 100644 --- a/src/include/catalog/meson.build +++ b/src/include/catalog/meson.build @@ -69,6 +69,9 @@ catalog_headers = [ 'pg_publication_rel.h', 'pg_subscription.h', 'pg_subscription_rel.h', + 'pg_propgraph_element.h', + 'pg_propgraph_label.h', + 'pg_propgraph_property.h', ] # The .dat files we need can just be listed alphabetically. diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 3b7533e7bb3..1635d2c5b35 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 9c120fc2b7f..207558f97a8 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3780,6 +3780,9 @@ proargtypes => 'oid oid', prosrc => 'oidge' }, # System-view support functions +{ oid => '8302', descr => 'source text of a property graph', + proname => 'pg_get_propgraphdef', provolatile => 's', prorettype => 'text', + proargtypes => 'oid', prosrc => 'pg_get_propgraphdef' }, { oid => '1573', descr => 'source text of a rule', proname => 'pg_get_ruledef', provolatile => 's', prorettype => 'text', proargtypes => 'oid', prosrc => 'pg_get_ruledef' }, diff --git a/src/include/catalog/pg_propgraph_element.h b/src/include/catalog/pg_propgraph_element.h new file mode 100644 index 00000000000..fb4bbf81544 --- /dev/null +++ b/src/include/catalog/pg_propgraph_element.h @@ -0,0 +1,103 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_element.h + * definition of the "property graph elements" system catalog (pg_propgraph_element) + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_element.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_ELEMENT_H +#define PG_PROPGRAPH_ELEMENT_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_element_d.h" + +/* ---------------- + * pg_propgraph_element definition. cpp turns this into + * typedef struct FormData_pg_propgraph_element + * ---------------- + */ +CATALOG(pg_propgraph_element,8299,PropgraphElementRelationId) +{ + Oid oid; + + /* OID of the property graph relation */ + Oid pgepgid BKI_LOOKUP(pg_class); + + /* OID of the element table */ + Oid pgerelid BKI_LOOKUP(pg_class); + + /* element alias */ + NameData pgealias; + + /* vertex or edge? -- see PGEKIND_* below */ + char pgekind; + + /* for edges: source vertex */ + Oid pgesrcvertexid BKI_LOOKUP_OPT(pg_propgraph_element); + + /* for edges: destination vertex */ + Oid pgedestvertexid BKI_LOOKUP_OPT(pg_propgraph_element); + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + /* element key (column numbers in pgerelid relation) */ + int16 pgekey[1] BKI_FORCE_NOT_NULL; + + /* + * for edges: source vertex key (column numbers in pgerelid relation) + */ + int16 pgesrckey[1]; + + /* + * for edges: source vertex table referenced columns (column numbers in + * relation reached via pgesrcvertexid) + */ + int16 pgesrcref[1]; + + /* + * for edges: destination vertex key (column numbers in pgerelid relation) + */ + int16 pgedestkey[1]; + + /* + * for edges: destination vertex table referenced columns (column numbers + * in relation reached via pgedestvertexid) + */ + int16 pgedestref[1]; +#endif +} FormData_pg_propgraph_element; + +/* ---------------- + * Form_pg_propgraph_element corresponds to a pointer to a tuple with + * the format of pg_propgraph_element relation. + * ---------------- + */ +typedef FormData_pg_propgraph_element *Form_pg_propgraph_element; + +DECLARE_TOAST(pg_propgraph_element, 8312, 8313); + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_element_oid_index, 8300, PropgraphElementObjectIndexId, pg_propgraph_element, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_element_alias_index, 8301, PropgraphElementAliasIndexId, pg_propgraph_element, btree(pgepgid oid_ops, pgealias name_ops)); + +MAKE_SYSCACHE(PROPGRAPHELOID, pg_propgraph_element_oid_index, 128); +MAKE_SYSCACHE(PROPGRAPHELALIAS, pg_propgraph_element_alias_index, 128); + +#ifdef EXPOSE_TO_CLIENT_CODE + +/* + * Symbolic values for pgekind column + */ +#define PGEKIND_VERTEX 'v' +#define PGEKIND_EDGE 'e' + +#endif /* EXPOSE_TO_CLIENT_CODE */ + +#endif /* PG_PROPGRAPH_ELEMENT_H */ diff --git a/src/include/catalog/pg_propgraph_label.h b/src/include/catalog/pg_propgraph_label.h new file mode 100644 index 00000000000..a704bdfabd1 --- /dev/null +++ b/src/include/catalog/pg_propgraph_label.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_label.h + * definition of the "property graph labels" system catalog (pg_propgraph_label) + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_label.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_LABEL_H +#define PG_PROPGRAPH_LABEL_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_label_d.h" + +/* ---------------- + * pg_propgraph_label definition. cpp turns this into + * typedef struct FormData_pg_propgraph_label + * ---------------- + */ +CATALOG(pg_propgraph_label,8303,PropgraphLabelRelationId) +{ + Oid oid; + + /* + * OID of the property graph relation. This can also be found out by + * chasing via pglelid, but having it here is more efficient. + */ + Oid pglpgid BKI_LOOKUP(pg_class); + + /* label name */ + NameData pgllabel; + + /* OID of the property graph element */ + Oid pglelid BKI_LOOKUP(pg_propgraph_element); +} FormData_pg_propgraph_label; + +/* ---------------- + * Form_pg_propgraph_label corresponds to a pointer to a tuple with + * the format of pg_propgraph_label relation. + * ---------------- + */ +typedef FormData_pg_propgraph_label *Form_pg_propgraph_label; + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_label_oid_index, 8304, PropgraphLabelObjectIndexId, pg_propgraph_label, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_label_label_index, 8305, PropgraphLabelLabelIndexId, pg_propgraph_label, btree(pglelid oid_ops, pgllabel name_ops)); + +DECLARE_INDEX(pg_propgraph_label_graph_name_index, 8314, PropgraphLabelGraphNameIndexId, pg_propgraph_label, btree(pglpgid oid_ops, pgllabel name_ops)); + +MAKE_SYSCACHE(PROPGRAPHLABELNAME, pg_propgraph_label_label_index, 128); + +#endif /* PG_PROPGRAPH_LABEL_H */ diff --git a/src/include/catalog/pg_propgraph_property.h b/src/include/catalog/pg_propgraph_property.h new file mode 100644 index 00000000000..2d5f2b8fe7e --- /dev/null +++ b/src/include/catalog/pg_propgraph_property.h @@ -0,0 +1,74 @@ +/*------------------------------------------------------------------------- + * + * pg_propgraph_property.h + * definition of the "property graph properties" system catalog (pg_propgraph_property) + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_propgraph_property.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PROPGRAPH_PROPERTY_H +#define PG_PROPGRAPH_PROPERTY_H + +#include "catalog/genbki.h" +#include "catalog/pg_propgraph_property_d.h" + +/* ---------------- + * pg_propgraph_property definition. cpp turns this into + * typedef struct FormData_pg_propgraph_property + * ---------------- + */ +CATALOG(pg_propgraph_property,8306,PropgraphPropertyRelationId) +{ + Oid oid; + + /* + * OID of the property graph relation. This can also be found out by + * chasing via pgplabelid, but having it here is more efficient. + */ + Oid pgppgid BKI_LOOKUP(pg_class); + + /* property name */ + NameData pgpname; + + /* + * Type of the property. (This can be computed from pgpexpr, but storing + * it makes parsing GRAPH_TABLE more efficient.) + */ + Oid pgptypid BKI_LOOKUP_OPT(pg_type); + + /* OID of the label */ + Oid pgplabelid BKI_LOOKUP(pg_propgraph_label); + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + + /* property expression */ + pg_node_tree pgpexpr BKI_FORCE_NOT_NULL; + +#endif +} FormData_pg_propgraph_property; + +/* ---------------- + * Form_pg_propgraph_property corresponds to a pointer to a tuple with + * the format of pg_propgraph_property relation. + * ---------------- + */ +typedef FormData_pg_propgraph_property *Form_pg_propgraph_property; + +DECLARE_TOAST(pg_propgraph_property, 8309, 8310); + +DECLARE_UNIQUE_INDEX_PKEY(pg_propgraph_property_oid_index, 8307, PropgraphPropertyObjectIndexId, pg_propgraph_property, btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_propgraph_property_name_index, 8308, PropgraphPropertyNameIndexId, pg_propgraph_property, btree(pgplabelid oid_ops, pgpname name_ops)); + +DECLARE_INDEX(pg_propgraph_property_graph_name_index, 8311, PropgraphPropertyGraphNameIndexId, pg_propgraph_property, btree(pgppgid oid_ops, pgpname name_ops)); + +MAKE_SYSCACHE(PROPGRAPHPROPNAME, pg_propgraph_property_name_index, 128); + +#endif /* PG_PROPGRAPH_PROPERTY_H */ diff --git a/src/include/commands/propgraphcmds.h b/src/include/commands/propgraphcmds.h new file mode 100644 index 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 d5b08ded446..8fab0daec3d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -669,6 +669,19 @@ typedef struct RangeTableFuncCol int location; /* token location, or -1 if unknown */ } RangeTableFuncCol; +/* + * RangeGraphTable - raw form of GRAPH_TABLE clause + */ +typedef struct RangeGraphTable +{ + NodeTag type; + RangeVar *graph_name; + struct GraphPattern *graph_pattern; + List *columns; + Alias *alias; /* table alias & optional column aliases */ + int location; /* token location, or -1 if unknown */ +} RangeGraphTable; + /* * RangeTableSample - TABLESAMPLE appearing in a raw FROM clause * @@ -934,6 +947,38 @@ typedef struct PartitionCmd bool concurrent; } PartitionCmd; +/* + * Nodes for graph pattern + */ + +typedef struct GraphPattern +{ + NodeTag type; + List *path_pattern_list; + Node *whereClause; +} GraphPattern; + +typedef enum GraphElementPatternKind +{ + VERTEX_PATTERN, + EDGE_PATTERN_LEFT, + EDGE_PATTERN_RIGHT, + EDGE_PATTERN_ANY, + PAREN_EXPR, +} GraphElementPatternKind; + +typedef struct GraphElementPattern +{ + NodeTag type; + GraphElementPatternKind kind; + const char *variable; + Node *labelexpr; + List *subexpr; + Node *whereClause; + List *quantifier; + int location; +} GraphElementPattern; + /**************************************************************************** * Nodes for a Query tree ****************************************************************************/ @@ -1010,6 +1055,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 */ @@ -1144,6 +1190,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): */ @@ -2122,6 +2174,7 @@ typedef enum ObjectType OBJECT_PARAMETER_ACL, OBJECT_POLICY, OBJECT_PROCEDURE, + OBJECT_PROPGRAPH, OBJECT_PUBLICATION, OBJECT_PUBLICATION_NAMESPACE, OBJECT_PUBLICATION_REL, @@ -3861,6 +3914,88 @@ typedef struct CreateCastStmt bool inout; } CreateCastStmt; +/* ---------------------- + * CREATE PROPERTY GRAPH Statement + * ---------------------- + */ +typedef struct CreatePropGraphStmt +{ + NodeTag type; + RangeVar *pgname; + List *vertex_tables; + List *edge_tables; +} CreatePropGraphStmt; + +typedef struct PropGraphVertex +{ + NodeTag type; + RangeVar *vtable; + List *vkey; + List *labels; + int location; +} PropGraphVertex; + +typedef struct PropGraphEdge +{ + NodeTag type; + RangeVar *etable; + List *ekey; + List *esrckey; + char *esrcvertex; + List *esrcvertexcols; + List *edestkey; + char *edestvertex; + List *edestvertexcols; + List *labels; + int location; +} PropGraphEdge; + +typedef struct PropGraphLabelAndProperties +{ + NodeTag type; + const char *label; + struct PropGraphProperties *properties; + int location; +} PropGraphLabelAndProperties; + +typedef struct PropGraphProperties +{ + NodeTag type; + List *properties; + bool all; + int location; +} PropGraphProperties; + +/* ---------------------- + * ALTER PROPERTY GRAPH Statement + * ---------------------- + */ + +typedef enum AlterPropGraphElementKind +{ + PROPGRAPH_ELEMENT_KIND_VERTEX = 1, + PROPGRAPH_ELEMENT_KIND_EDGE = 2, +} AlterPropGraphElementKind; + +typedef struct AlterPropGraphStmt +{ + NodeTag type; + RangeVar *pgname; + bool missing_ok; + List *add_vertex_tables; + List *add_edge_tables; + List *drop_vertex_tables; + List *drop_edge_tables; + DropBehavior drop_behavior; + AlterPropGraphElementKind element_kind; + const char *element_alias; + List *add_labels; + const char *drop_label; + const char *alter_label; + PropGraphProperties *add_properties; + List *drop_properties; +} AlterPropGraphStmt; + /* ---------------------- * CREATE TRANSFORM Statement * ---------------------- diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 4a154606d2b..43c94aa9f97 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1860,6 +1860,28 @@ typedef struct InferenceElem Oid inferopclass; /* OID of att opclass, or InvalidOid */ } InferenceElem; +/* + * GraphLabelRef - label reference in label expression inside GRAPH_TABLE clause + */ +typedef struct GraphLabelRef +{ + NodeTag type; + const char *labelname; + int location; +} GraphLabelRef; + +/* + * GraphPropertyRef - property reference inside GRAPH_TABLE clause + */ +typedef struct GraphPropertyRef +{ + Expr xpr; + const char *elvarname; + const char *propname; + Oid typeId; + int location; +} GraphPropertyRef; + /*-------------------- * TargetEntry - * a target entry (used in query target lists) diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 2331acac091..34092ed075c 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -135,6 +135,7 @@ PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("depth", DEPTH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("desc", DESC, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("destination", DESTINATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) @@ -146,6 +147,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("edge", EDGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL) @@ -187,6 +189,8 @@ PG_KEYWORD("generated", GENERATED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("graph", GRAPH, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("graph_table", GRAPH_TABLE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD, BARE_LABEL) @@ -284,6 +288,7 @@ PG_KEYWORD("nfd", NFD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("nfkc", NFKC, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("nfkd", NFKD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("no", NO, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("node", NODE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("none", NONE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("normalize", NORMALIZE, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("normalized", NORMALIZED, UNRESERVED_KEYWORD, BARE_LABEL) @@ -342,6 +347,8 @@ PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("properties", PROPERTIES, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("property", PROPERTY, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD, BARE_LABEL) @@ -355,6 +362,7 @@ PG_KEYWORD("references", REFERENCES, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("referencing", REFERENCING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("refresh", REFRESH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("relationship", RELATIONSHIP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL) @@ -403,6 +411,7 @@ PG_KEYWORD("skip", SKIP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("smallint", SMALLINT, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("snapshot", SNAPSHOT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("some", SOME, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("source", SOURCE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("sql", SQL_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("stable", STABLE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("standalone", STANDALONE_P, UNRESERVED_KEYWORD, BARE_LABEL) @@ -470,6 +479,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("vertex", VERTEX, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h new file mode 100644 index 00000000000..7de98a71f76 --- /dev/null +++ b/src/include/parser/parse_graphtable.h @@ -0,0 +1,31 @@ +/*------------------------------------------------------------------------- + * + * parse_graphtable.h + * parsing of GRAPH_TABLE + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/parser/parse_graphtable.h + * + *------------------------------------------------------------------------- + */ +#ifndef PARSE_GRAPHTABLE_H +#define PARSE_GRAPHTABLE_H + +#include "nodes/parsenodes.h" +#include "nodes/pg_list.h" +#include "parser/parse_node.h" + +typedef struct GraphTableParseState +{ + Oid graphid; + List *variables; +} GraphTableParseState; + +extern Node *graph_table_property_reference(ParseState *pstate, ColumnRef *cref); + +Node *transformGraphPattern(GraphTableParseState *gpstate, GraphPattern *graph_pattern); + +#endif /* PARSE_GRAPHTABLE_H */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 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 a803eb12910..b659040716c 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -165,6 +165,7 @@ typedef struct ArrayType Acl; #define ACL_ALL_RIGHTS_LANGUAGE (ACL_USAGE) #define ACL_ALL_RIGHTS_LARGEOBJECT (ACL_SELECT|ACL_UPDATE) #define ACL_ALL_RIGHTS_PARAMETER_ACL (ACL_SET|ACL_ALTER_SYSTEM) +#define ACL_ALL_RIGHTS_PROPGRAPH (ACL_SELECT) #define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE) #define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE) #define ACL_ALL_RIGHTS_TYPE (ACL_USAGE) diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index e630498e82c..21e3fc84e81 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -233,6 +233,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token 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..9ad28f6fc07 --- /dev/null +++ b/src/test/regress/expected/create_property_graph.out @@ -0,0 +1,370 @@ +CREATE SCHEMA create_property_graph_tests; +GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC; +SET search_path = create_property_graph_tests; +CREATE ROLE regress_graph_user1; +CREATE ROLE regress_graph_user2; +CREATE PROPERTY GRAPH g1; +COMMENT ON PROPERTY GRAPH g1 IS 'a graph'; +CREATE PROPERTY GRAPH g1; -- error: duplicate +ERROR: relation "g1" already exists +CREATE TABLE t1 (a int, b text); +CREATE TABLE t2 (i int PRIMARY KEY, j int, k int); +CREATE TABLE t3 (x int, y text, z text); +CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i)); +CREATE TABLE e2 (a int, x int, t text); +CREATE PROPERTY GRAPH g2 + VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); +-- like g2 but assembled with ALTER +CREATE PROPERTY GRAPH g3; +ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL); +ALTER PROPERTY GRAPH g3 + ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1) + ADD EDGE TABLES ( + e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS ADD LABEL t3l3 PROPERTIES ALL COLUMNS; +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x; -- error +ERROR: property graph "g3" element "t3" has no label "t3l3x" +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3; +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2); -- fail (TODO: dubious error message) +ERROR: cannot drop element t2 in property graph g3 because other objects depend on it +DETAIL: element e1 in property graph g3 depends on element t2 in property graph g3 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE; +NOTICE: drop cascades to element e1 in property graph g3 +ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2); +CREATE PROPERTY GRAPH g4 + VERTEX TABLES ( + t1 KEY (a) NO PROPERTIES, + t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz) + ) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i) + PROPERTIES ALL COLUMNS, + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + PROPERTIES ALL COLUMNS + ); +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk); +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k); +-- error cases +CREATE PROPERTY GRAPH gx VERTEX TABLES (xx, yy); +ERROR: relation "xx" does not exist +CREATE PROPERTY GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a)); +ERROR: alias "t1" used more than once as element table +LINE 1: ...Y GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a)... + ^ +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION t2 + ); +ERROR: source vertex "t1" of edge "e1" does not exist +LINE 4: e1 SOURCE t1 DESTINATION t2 + ^ +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION tx + ); +ERROR: destination vertex "tx" of edge "e1" does not exist +LINE 4: e1 SOURCE t1 DESTINATION tx + ^ +COMMENT ON PROPERTY GRAPH gx IS 'not a graph'; +ERROR: relation "gx" does not exist +ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1; +SET ROLE regress_graph_user1; +GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2; +GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2; -- fail +ERROR: invalid privilege type UPDATE for property graph +RESET ROLE; +-- information schema +SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name; + property_graph_catalog | property_graph_schema | property_graph_name +------------------------+-----------------------------+--------------------- + regression | create_property_graph_tests | g1 + regression | create_property_graph_tests | g2 + regression | create_property_graph_tests | g3 + regression | create_property_graph_tests | g4 +(4 rows) + +SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | element_table_kind | table_catalog | table_schema | table_name | element_table_definition +------------------------+-----------------------------+---------------------+---------------------+--------------------+---------------+-----------------------------+------------+-------------------------- + regression | create_property_graph_tests | g2 | e1 | EDGE | regression | create_property_graph_tests | e1 | + regression | create_property_graph_tests | g2 | e2 | EDGE | regression | create_property_graph_tests | e2 | + regression | create_property_graph_tests | g2 | t1 | VERTEX | regression | create_property_graph_tests | t1 | + regression | create_property_graph_tests | g2 | t2 | VERTEX | regression | create_property_graph_tests | t2 | + regression | create_property_graph_tests | g2 | t3 | VERTEX | regression | create_property_graph_tests | t3 | + regression | create_property_graph_tests | g3 | t1 | VERTEX | regression | create_property_graph_tests | t1 | + regression | create_property_graph_tests | g3 | t3 | VERTEX | regression | create_property_graph_tests | t3 | + regression | create_property_graph_tests | g4 | e1 | EDGE | regression | create_property_graph_tests | e1 | + regression | create_property_graph_tests | g4 | e2 | EDGE | regression | create_property_graph_tests | e2 | + regression | create_property_graph_tests | g4 | t1 | VERTEX | regression | create_property_graph_tests | t1 | + regression | create_property_graph_tests | g4 | t2 | VERTEX | regression | create_property_graph_tests | t2 | + regression | create_property_graph_tests | g4 | t3 | VERTEX | regression | create_property_graph_tests | t3 | +(12 rows) + +SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | column_name | ordinal_position +------------------------+-----------------------------+---------------------+---------------------+-------------+------------------ + regression | create_property_graph_tests | g2 | e1 | a | 1 + regression | create_property_graph_tests | g2 | e1 | i | 2 + regression | create_property_graph_tests | g2 | e2 | a | 1 + regression | create_property_graph_tests | g2 | e2 | x | 2 + regression | create_property_graph_tests | g2 | t1 | a | 1 + regression | create_property_graph_tests | g2 | t2 | i | 1 + regression | create_property_graph_tests | g2 | t3 | x | 1 + regression | create_property_graph_tests | g3 | t1 | a | 1 + regression | create_property_graph_tests | g3 | t3 | x | 1 + regression | create_property_graph_tests | g4 | e1 | a | 1 + regression | create_property_graph_tests | g4 | e1 | i | 2 + regression | create_property_graph_tests | g4 | e2 | a | 1 + regression | create_property_graph_tests | g4 | e2 | x | 2 + regression | create_property_graph_tests | g4 | t1 | a | 1 + regression | create_property_graph_tests | g4 | t2 | i | 1 + regression | create_property_graph_tests | g4 | t3 | x | 1 +(16 rows) + +SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position; + property_graph_catalog | property_graph_schema | property_graph_name | edge_table_alias | vertex_table_alias | edge_end | edge_table_column_name | vertex_table_column_name | ordinal_position +------------------------+-----------------------------+---------------------+------------------+--------------------+-------------+------------------------+--------------------------+------------------ + regression | create_property_graph_tests | g2 | e1 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g2 | e1 | t2 | DESTINATION | i | i | 1 + regression | create_property_graph_tests | g2 | e2 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g2 | e2 | t3 | DESTINATION | x | x | 1 + regression | create_property_graph_tests | g2 | e2 | t3 | DESTINATION | t | y | 2 + regression | create_property_graph_tests | g4 | e1 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g4 | e1 | t2 | DESTINATION | i | i | 1 + regression | create_property_graph_tests | g4 | e2 | t1 | SOURCE | a | a | 1 + regression | create_property_graph_tests | g4 | e2 | t3 | DESTINATION | x | x | 1 + regression | create_property_graph_tests | g4 | e2 | t3 | DESTINATION | t | y | 2 +(10 rows) + +SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | label_name +------------------------+-----------------------------+---------------------+---------------------+------------ + regression | create_property_graph_tests | g2 | e1 | e1 + regression | create_property_graph_tests | g2 | e2 | e2 + regression | create_property_graph_tests | g2 | t1 | t1 + regression | create_property_graph_tests | g2 | t2 | t2 + regression | create_property_graph_tests | g2 | t3 | t3l1 + regression | create_property_graph_tests | g2 | t3 | t3l2 + regression | create_property_graph_tests | g3 | t1 | t1 + regression | create_property_graph_tests | g3 | t3 | t3l1 + regression | create_property_graph_tests | g3 | t3 | t3l2 + regression | create_property_graph_tests | g4 | e1 | e1 + regression | create_property_graph_tests | g4 | e2 | e2 + regression | create_property_graph_tests | g4 | t1 | t1 + regression | create_property_graph_tests | g4 | t2 | t2 + regression | create_property_graph_tests | g4 | t3 | t3l1 + regression | create_property_graph_tests | g4 | t3 | t3l2 +(15 rows) + +SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name; + property_graph_catalog | property_graph_schema | property_graph_name | element_table_alias | property_name | property_expression +------------------------+-----------------------------+---------------------+---------------------+---------------+--------------------- + regression | create_property_graph_tests | g2 | e1 | a | a + regression | create_property_graph_tests | g2 | e1 | i | i + regression | create_property_graph_tests | g2 | e1 | t | t + regression | create_property_graph_tests | g2 | e2 | a | a + regression | create_property_graph_tests | g2 | e2 | t | t + regression | create_property_graph_tests | g2 | e2 | x | x + regression | create_property_graph_tests | g2 | t1 | a | a + regression | create_property_graph_tests | g2 | t1 | b | b + regression | create_property_graph_tests | g2 | t2 | i | i + regression | create_property_graph_tests | g2 | t2 | j | j + regression | create_property_graph_tests | g2 | t2 | k | k + regression | create_property_graph_tests | g2 | t3 | x | x + regression | create_property_graph_tests | g2 | t3 | y | y + regression | create_property_graph_tests | g2 | t3 | z | z + regression | create_property_graph_tests | g3 | t1 | a | a + regression | create_property_graph_tests | g3 | t1 | b | b + regression | create_property_graph_tests | g3 | t3 | x | x + regression | create_property_graph_tests | g3 | t3 | y | y + regression | create_property_graph_tests | g3 | t3 | z | z + regression | create_property_graph_tests | g4 | e1 | a | a + regression | create_property_graph_tests | g4 | e1 | i | i + regression | create_property_graph_tests | g4 | e1 | t | t + regression | create_property_graph_tests | g4 | e2 | a | a + regression | create_property_graph_tests | g4 | e2 | t | t + regression | create_property_graph_tests | g4 | e2 | x | x + regression | create_property_graph_tests | g4 | t2 | i_j | (i + j) + regression | create_property_graph_tests | g4 | t2 | kk | (k * 2) + regression | create_property_graph_tests | g4 | t3 | x | x + regression | create_property_graph_tests | g4 | t3 | yy | y + regression | create_property_graph_tests | g4 | t3 | zz | z +(30 rows) + +SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name; + property_graph_catalog | property_graph_schema | property_graph_name | label_name | property_name +------------------------+-----------------------------+---------------------+------------+--------------- + regression | create_property_graph_tests | g2 | e1 | a + regression | create_property_graph_tests | g2 | e1 | i + regression | create_property_graph_tests | g2 | e1 | t + regression | create_property_graph_tests | g2 | e2 | a + regression | create_property_graph_tests | g2 | e2 | t + regression | create_property_graph_tests | g2 | e2 | x + regression | create_property_graph_tests | g2 | t1 | a + regression | create_property_graph_tests | g2 | t1 | b + regression | create_property_graph_tests | g2 | t2 | i + regression | create_property_graph_tests | g2 | t2 | j + regression | create_property_graph_tests | g2 | t2 | k + regression | create_property_graph_tests | g2 | t3l1 | x + regression | create_property_graph_tests | g2 | t3l1 | y + regression | create_property_graph_tests | g2 | t3l1 | z + regression | create_property_graph_tests | g2 | t3l2 | x + regression | create_property_graph_tests | g2 | t3l2 | y + regression | create_property_graph_tests | g2 | t3l2 | z + regression | create_property_graph_tests | g3 | t1 | a + regression | create_property_graph_tests | g3 | t1 | b + regression | create_property_graph_tests | g3 | t3l1 | x + regression | create_property_graph_tests | g3 | t3l1 | y + regression | create_property_graph_tests | g3 | t3l1 | z + regression | create_property_graph_tests | g3 | t3l2 | x + regression | create_property_graph_tests | g3 | t3l2 | y + regression | create_property_graph_tests | g3 | t3l2 | z + regression | create_property_graph_tests | g4 | e1 | a + regression | create_property_graph_tests | g4 | e1 | i + regression | create_property_graph_tests | g4 | e1 | t + regression | create_property_graph_tests | g4 | e2 | a + regression | create_property_graph_tests | g4 | e2 | t + regression | create_property_graph_tests | g4 | e2 | x + regression | create_property_graph_tests | g4 | t2 | i_j + regression | create_property_graph_tests | g4 | t2 | kk + regression | create_property_graph_tests | g4 | t3l1 | x + regression | create_property_graph_tests | g4 | t3l1 | yy + regression | create_property_graph_tests | g4 | t3l2 | x + regression | create_property_graph_tests | g4 | t3l2 | zz +(37 rows) + +SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name; + property_graph_catalog | property_graph_schema | property_graph_name | label_name +------------------------+-----------------------------+---------------------+------------ + regression | create_property_graph_tests | g2 | e1 + regression | create_property_graph_tests | g2 | e2 + regression | create_property_graph_tests | g2 | t1 + regression | create_property_graph_tests | g2 | t2 + regression | create_property_graph_tests | g2 | t3l1 + regression | create_property_graph_tests | g2 | t3l2 + regression | create_property_graph_tests | g3 | t1 + regression | create_property_graph_tests | g3 | t3l1 + regression | create_property_graph_tests | g3 | t3l2 + regression | create_property_graph_tests | g4 | e1 + regression | create_property_graph_tests | g4 | e2 + regression | create_property_graph_tests | g4 | t1 + regression | create_property_graph_tests | g4 | t2 + regression | create_property_graph_tests | g4 | t3l1 + regression | create_property_graph_tests | g4 | t3l2 +(15 rows) + +SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name; + property_graph_catalog | property_graph_schema | property_graph_name | property_name | data_type | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | user_defined_type_catalog | user_defined_type_schema | user_defined_type_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier +------------------------+-----------------------------+---------------------+---------------+-----------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+---------------------------+--------------------------+------------------------+---------------+--------------+------------+---------------------+---------------- + regression | create_property_graph_tests | g2 | a | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | a + regression | create_property_graph_tests | g2 | b | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | b + regression | create_property_graph_tests | g2 | i | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | i + regression | create_property_graph_tests | g2 | j | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | j + regression | create_property_graph_tests | g2 | k | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | k + regression | create_property_graph_tests | g2 | t | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | t + regression | create_property_graph_tests | g2 | x | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | x + regression | create_property_graph_tests | g2 | y | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | y + regression | create_property_graph_tests | g2 | z | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | z + regression | create_property_graph_tests | g3 | a | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | a + regression | create_property_graph_tests | g3 | b | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | b + regression | create_property_graph_tests | g3 | x | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | x + regression | create_property_graph_tests | g3 | y | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | y + regression | create_property_graph_tests | g3 | z | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | z + regression | create_property_graph_tests | g4 | a | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | a + regression | create_property_graph_tests | g4 | i | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | i + regression | create_property_graph_tests | g4 | i_j | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | i_j + regression | create_property_graph_tests | g4 | kk | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | kk + regression | create_property_graph_tests | g4 | t | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | t + regression | create_property_graph_tests | g4 | x | integer | | | | | | | | | | | | | | | regression | pg_catalog | int4 | | | | | x + regression | create_property_graph_tests | g4 | yy | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | yy + regression | create_property_graph_tests | g4 | zz | text | | | | | | | | | | | | | | | regression | pg_catalog | text | | | | | zz +(22 rows) + +SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name; + grantor | grantee | property_graph_catalog | property_graph_schema | property_graph_name | privilege_type | is_grantable +---------------------+---------------------+------------------------+-----------------------------+---------------------+----------------+-------------- + regress_graph_user1 | regress_graph_user1 | regression | create_property_graph_tests | g1 | SELECT | YES + regress_graph_user1 | regress_graph_user2 | regression | create_property_graph_tests | g1 | SELECT | NO +(2 rows) + +\a\t +SELECT pg_get_propgraphdef('g2'::regclass); +CREATE PROPERTY GRAPH create_property_graph_tests.g2 + VERTEX TABLES ( + t1 KEY (a) PROPERTIES (a, b), + t2 KEY (i) PROPERTIES (i, j, k), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y, z) LABEL t3l2 PROPERTIES (x, y, z) + ) + EDGE TABLES ( + e1 KEY (a, i) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i) PROPERTIES (a, i, t), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) PROPERTIES (a, t, x) + ) +SELECT pg_get_propgraphdef('g3'::regclass); +CREATE PROPERTY GRAPH create_property_graph_tests.g3 + VERTEX TABLES ( + t1 KEY (a) PROPERTIES (a, b), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y, z) LABEL t3l2 PROPERTIES (x, y, z) + ) +SELECT pg_get_propgraphdef('g4'::regclass); +CREATE PROPERTY GRAPH create_property_graph_tests.g4 + VERTEX TABLES ( + t1 KEY (a) NO PROPERTIES, + t2 KEY (i) PROPERTIES ((i + j) AS i_j, (k * 2) AS kk), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz) + ) + EDGE TABLES ( + e1 KEY (a, i) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i) PROPERTIES (a, i, t), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) PROPERTIES (a, t, x) + ) +SELECT pg_get_propgraphdef('pg_type'::regclass); -- error +ERROR: "pg_type" is not a property graph +\a\t +\dG g1 + List of relations + Schema | Name | Type | Owner +-----------------------------+------+----------------+--------------------- + create_property_graph_tests | g1 | property graph | regress_graph_user1 +(1 row) + +-- TODO +\d g1 +Property graph "create_property_graph_tests.g1" + Column | Type +--------+------ + +\d+ g1 +Property graph "create_property_graph_tests.g1" + Column | Type | Storage +--------+------+--------- +Property graph definition: + CREATE PROPERTY GRAPH create_property_graph_tests.g1 + +DROP TABLE g2; -- error: wrong object type +ERROR: "g2" is not a table +HINT: Use DROP PROPERTY GRAPH to remove a property graph. +DROP PROPERTY GRAPH g1; +DROP PROPERTY GRAPH g1; -- error: does not exist +ERROR: property graph "g1" does not exist +DROP PROPERTY GRAPH IF EXISTS g1; +NOTICE: property graph "g1" does not exist, skipping +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA create_property_graph_tests CASCADE; +DROP ROLE regress_graph_user1, regress_graph_user2; diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out new file mode 100644 index 00000000000..f22ba091a7e --- /dev/null +++ b/src/test/regress/expected/graph_table.out @@ -0,0 +1,122 @@ +CREATE SCHEMA graph_table_tests; +GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC; +SET search_path = graph_table_tests; +CREATE TABLE products ( + product_no integer PRIMARY KEY, + name varchar, + price numeric +); +CREATE TABLE customers ( + customer_id integer PRIMARY KEY, + name varchar, + address varchar +); +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + ordered_when date +); +CREATE TABLE order_items ( + order_items_id integer PRIMARY KEY, + order_id integer REFERENCES orders (order_id), + product_no integer REFERENCES products (product_no), + quantity integer +); +CREATE TABLE customer_orders ( + customer_orders_id integer PRIMARY KEY, + customer_id integer REFERENCES customers (customer_id), + order_id integer REFERENCES orders (order_id) +); +CREATE PROPERTY GRAPH myshop + VERTEX TABLES ( + products, + customers, + orders + ) + EDGE TABLES ( + order_items KEY (order_items_id) + SOURCE KEY (order_id) REFERENCES orders (order_id) + DESTINATION KEY (product_no) REFERENCES products (product_no), + customer_orders KEY (customer_orders_id) + SOURCE KEY (customer_id) REFERENCES customers (customer_id) + DESTINATION KEY (order_id) REFERENCES orders (order_id) + ); +SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +ERROR: relation "xxx" does not exist +LINE 1: SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS custo... + ^ +SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +ERROR: "pg_class" is not a property graph +LINE 1: SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS ... + ^ +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name)); -- error +ERROR: graph pattern variable "cx" does not exist +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name)); -- error +ERROR: property "namex" does not exist +INSERT INTO products VALUES + (1, 'product1', 10), + (2, 'product2', 20), + (3, 'product3', 30); +INSERT INTO customers VALUES + (1, 'customer1', 'US'), + (2, 'customer2', 'CA'), + (3, 'customer3', 'GL'); +INSERT INTO orders VALUES + (1, '2024-01-01'), + (2, '2024-01-02'), + (3, '2024-01-03'); +INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES + (1, 1, 1, 5), + (2, 1, 2, 10), + (3, 2, 1, 7); +INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES + (1, 1, 1), + (2, 2, 2); +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); + customer_name +--------------- + customer1 +(1 row) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (c:customers)-[co:customer_orders]->(o:orders WHERE o.ordered_when = '2024-01-02') COLUMNS (c.name, c.address)); + name | address +-----------+--------- + customer2 | CA +(1 row) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when)); + name | ordered_when +------+-------------- +(0 rows) + +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when)); + name | ordered_when +-----------+-------------- + customer1 | 01-01-2024 + customer2 | 01-02-2024 +(2 rows) + +-- TODO: should approximately match this query: +SET debug_print_parse = on; +SELECT customer_name FROM ( + SELECT c.name AS customer_name + FROM customers c, customer_orders _co, orders o + WHERE _co.customer_id = c.customer_id + AND _co.order_id = o.order_id + AND c.address = 'US' +) myshop; + customer_name +--------------- + customer1 +(1 row) + +RESET debug_print_parse; +CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); +SELECT pg_get_viewdef('customers_us'::regclass); + pg_get_viewdef +------------------------------------------------------------------------------------------------------------------------------------------------------------------- + SELECT customer_name + + FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE ((c.address)::text = 'US'::text))-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); +(1 row) + +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA graph_table_tests CASCADE; diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out index 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..08b84962c8a 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -266,3 +266,12 @@ NOTICE: checking pg_subscription {subdbid} => pg_database {oid} NOTICE: checking pg_subscription {subowner} => pg_authid {oid} NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid} NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid} +NOTICE: checking pg_propgraph_element {pgepgid} => pg_class {oid} +NOTICE: checking pg_propgraph_element {pgerelid} => pg_class {oid} +NOTICE: checking pg_propgraph_element {pgesrcvertexid} => pg_propgraph_element {oid} +NOTICE: checking pg_propgraph_element {pgedestvertexid} => pg_propgraph_element {oid} +NOTICE: checking pg_propgraph_label {pglpgid} => pg_class {oid} +NOTICE: checking pg_propgraph_label {pglelid} => pg_propgraph_element {oid} +NOTICE: checking pg_propgraph_property {pgppgid} => pg_class {oid} +NOTICE: checking pg_propgraph_property {pgptypid} => pg_type {oid} +NOTICE: checking pg_propgraph_property {pgplabelid} => pg_propgraph_label {oid} diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 1d8a414eea7..09e69e16cd8 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -48,7 +48,7 @@ test: create_index create_index_spgist create_view index_including index_includi # ---------- # Another group of parallel tests # ---------- -test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse +test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse create_property_graph # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * @@ -78,7 +78,7 @@ test: brin_bloom brin_multi # psql depends on create_am # amutils depends on geometry, create_index_spgist, hash_index, brin # ---------- -test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role without_overlaps +test: create_table_like alter_generic alter_operator misc async dbsize merge misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role without_overlaps graph_table # collate.*.utf8 tests cannot be run in parallel with each other test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8 collate.windows.win1252 diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql index 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..2f0b42ecd2d --- /dev/null +++ b/src/test/regress/sql/create_property_graph.sql @@ -0,0 +1,130 @@ +CREATE SCHEMA create_property_graph_tests; +GRANT USAGE ON SCHEMA create_property_graph_tests TO PUBLIC; +SET search_path = create_property_graph_tests; + +CREATE ROLE regress_graph_user1; +CREATE ROLE regress_graph_user2; + +CREATE PROPERTY GRAPH g1; + +COMMENT ON PROPERTY GRAPH g1 IS 'a graph'; + +CREATE PROPERTY GRAPH g1; -- error: duplicate + +CREATE TABLE t1 (a int, b text); +CREATE TABLE t2 (i int PRIMARY KEY, j int, k int); +CREATE TABLE t3 (x int, y text, z text); + +CREATE TABLE e1 (a int, i int, t text, PRIMARY KEY (a, i)); +CREATE TABLE e2 (a int, x int, t text); + +CREATE PROPERTY GRAPH g2 + VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL, t3 KEY (x) LABEL t3l1 LABEL t3l2) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); + +-- like g2 but assembled with ALTER +CREATE PROPERTY GRAPH g3; +ALTER PROPERTY GRAPH g3 ADD VERTEX TABLES (t1 KEY (a), t2 DEFAULT LABEL); +ALTER PROPERTY GRAPH g3 + ADD VERTEX TABLES (t3 KEY (x) LABEL t3l1) + ADD EDGE TABLES ( + e1 SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (i) REFERENCES t2 (i), + e2 KEY (a, x) SOURCE KEY (a) REFERENCES t1 (a) DESTINATION KEY (x, t) REFERENCES t3 (x, y) + ); +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 ADD LABEL t3l2 PROPERTIES ALL COLUMNS ADD LABEL t3l3 PROPERTIES ALL COLUMNS; +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3x; -- error +ALTER PROPERTY GRAPH g3 ALTER VERTEX TABLE t3 DROP LABEL t3l3; +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2); -- fail (TODO: dubious error message) +ALTER PROPERTY GRAPH g3 DROP VERTEX TABLES (t2) CASCADE; +ALTER PROPERTY GRAPH g3 DROP EDGE TABLES (e2); + +CREATE PROPERTY GRAPH g4 + VERTEX TABLES ( + t1 KEY (a) NO PROPERTIES, + t2 DEFAULT LABEL PROPERTIES (i + j AS i_j, k), + t3 KEY (x) LABEL t3l1 PROPERTIES (x, y AS yy) LABEL t3l2 PROPERTIES (x, z AS zz) + ) + EDGE TABLES ( + e1 + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (i) REFERENCES t2 (i) + PROPERTIES ALL COLUMNS, + e2 KEY (a, x) + SOURCE KEY (a) REFERENCES t1 (a) + DESTINATION KEY (x, t) REFERENCES t3 (x, y) + PROPERTIES ALL COLUMNS + ); + +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 ADD PROPERTIES (k * 2 AS kk); +ALTER PROPERTY GRAPH g4 ALTER VERTEX TABLE t2 ALTER LABEL t2 DROP PROPERTIES (k); + +-- error cases +CREATE PROPERTY GRAPH gx VERTEX TABLES (xx, yy); +CREATE PROPERTY GRAPH gx VERTEX TABLES (t1 KEY (a), t2 KEY (i), t1 KEY (a)); +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 AS tt KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION t2 + ); +CREATE PROPERTY GRAPH gx + VERTEX TABLES (t1 KEY (a), t2 KEY (i)) + EDGE TABLES ( + e1 SOURCE t1 DESTINATION tx + ); +COMMENT ON PROPERTY GRAPH gx IS 'not a graph'; + + +ALTER PROPERTY GRAPH g1 OWNER TO regress_graph_user1; +SET ROLE regress_graph_user1; +GRANT SELECT ON PROPERTY GRAPH g1 TO regress_graph_user2; +GRANT UPDATE ON PROPERTY GRAPH g1 TO regress_graph_user2; -- fail +RESET ROLE; + + +-- information schema + +SELECT * FROM information_schema.property_graphs ORDER BY property_graph_name; +SELECT * FROM information_schema.pg_element_tables ORDER BY property_graph_name, element_table_alias; +SELECT * FROM information_schema.pg_element_table_key_columns ORDER BY property_graph_name, element_table_alias, ordinal_position; +SELECT * FROM information_schema.pg_edge_table_components ORDER BY property_graph_name, edge_table_alias, edge_end DESC, ordinal_position; +SELECT * FROM information_schema.pg_element_table_labels ORDER BY property_graph_name, element_table_alias, label_name; +SELECT * FROM information_schema.pg_element_table_properties ORDER BY property_graph_name, element_table_alias, property_name; +SELECT * FROM information_schema.pg_label_properties ORDER BY property_graph_name, label_name, property_name; +SELECT * FROM information_schema.pg_labels ORDER BY property_graph_name, label_name; +SELECT * FROM information_schema.pg_property_data_types ORDER BY property_graph_name, property_name; +SELECT * FROM information_schema.pg_property_graph_privileges WHERE grantee LIKE 'regress%' ORDER BY property_graph_name; + + +\a\t +SELECT pg_get_propgraphdef('g2'::regclass); +SELECT pg_get_propgraphdef('g3'::regclass); +SELECT pg_get_propgraphdef('g4'::regclass); + +SELECT pg_get_propgraphdef('pg_type'::regclass); -- error +\a\t + +\dG g1 + +-- TODO +\d g1 +\d+ g1 + +DROP TABLE g2; -- error: wrong object type + +DROP PROPERTY GRAPH g1; + +DROP PROPERTY GRAPH g1; -- error: does not exist + +DROP PROPERTY GRAPH IF EXISTS g1; + +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA create_property_graph_tests CASCADE; + +DROP ROLE regress_graph_user1, regress_graph_user2; diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql new file mode 100644 index 00000000000..0632cbda8b5 --- /dev/null +++ b/src/test/regress/sql/graph_table.sql @@ -0,0 +1,96 @@ +CREATE SCHEMA graph_table_tests; +GRANT USAGE ON SCHEMA graph_table_tests TO PUBLIC; +SET search_path = graph_table_tests; + +CREATE TABLE products ( + product_no integer PRIMARY KEY, + name varchar, + price numeric +); + +CREATE TABLE customers ( + customer_id integer PRIMARY KEY, + name varchar, + address varchar +); + +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + ordered_when date +); + +CREATE TABLE order_items ( + order_items_id integer PRIMARY KEY, + order_id integer REFERENCES orders (order_id), + product_no integer REFERENCES products (product_no), + quantity integer +); + +CREATE TABLE customer_orders ( + customer_orders_id integer PRIMARY KEY, + customer_id integer REFERENCES customers (customer_id), + order_id integer REFERENCES orders (order_id) +); + +CREATE PROPERTY GRAPH myshop + VERTEX TABLES ( + products, + customers, + orders + ) + EDGE TABLES ( + order_items KEY (order_items_id) + SOURCE KEY (order_id) REFERENCES orders (order_id) + DESTINATION KEY (product_no) REFERENCES products (product_no), + customer_orders KEY (customer_orders_id) + SOURCE KEY (customer_id) REFERENCES customers (customer_id) + DESTINATION KEY (order_id) REFERENCES orders (order_id) + ); + +SELECT customer_name FROM GRAPH_TABLE (xxx MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (pg_class MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (cx.name AS customer_name)); -- error +SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.namex AS customer_name)); -- error + +INSERT INTO products VALUES + (1, 'product1', 10), + (2, 'product2', 20), + (3, 'product3', 30); +INSERT INTO customers VALUES + (1, 'customer1', 'US'), + (2, 'customer2', 'CA'), + (3, 'customer3', 'GL'); +INSERT INTO orders VALUES + (1, '2024-01-01'), + (2, '2024-01-02'), + (3, '2024-01-03'); +INSERT INTO order_items (order_items_id, order_id, product_no, quantity) VALUES + (1, 1, 1, 5), + (2, 1, 2, 10), + (3, 2, 1, 7); +INSERT INTO customer_orders (customer_orders_id, customer_id, order_id) VALUES + (1, 1, 1), + (2, 2, 2); + +SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (c:customers)-[co:customer_orders]->(o:orders WHERE o.ordered_when = '2024-01-02') COLUMNS (c.name, c.address)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)-[IS customer_orders]->(c IS customers) COLUMNS (c.name, o.ordered_when)); +SELECT * FROM GRAPH_TABLE (myshop MATCH (o IS orders)<-[IS customer_orders]-(c IS customers) COLUMNS (c.name, o.ordered_when)); + +-- TODO: should approximately match this query: +SET debug_print_parse = on; +SELECT customer_name FROM ( + SELECT c.name AS customer_name + FROM customers c, customer_orders _co, orders o + WHERE _co.customer_id = c.customer_id + AND _co.order_id = o.order_id + AND c.address = 'US' +) myshop; +RESET debug_print_parse; + +CREATE VIEW customers_us AS SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.name AS customer_name)); + +SELECT pg_get_viewdef('customers_us'::regclass); + +-- leave for pg_upgrade/pg_dump tests +--DROP SCHEMA graph_table_tests CASCADE; diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql index 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 d808aad8b05..d2d7250da08 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -4052,3 +4052,22 @@ rfile ws_options ws_file_info PathKeyInfo + +# TODO +AlterPropGraphElementKind +AlterPropGraphStmt +CreatePropGraphStmt +FormData_pg_propgraph_element +FormData_pg_propgraph_label +FormData_pg_propgraph_property +GraphElementPattern +GraphElementPatternKind +GraphLabelRef +GraphPattern +GraphPropertyRef +GraphTableParseState +PropGraphEdge +PropGraphLabelAndProperties +PropGraphProperties +PropGraphVertex +RangeGraphTable base-commit: 0413a556990ba628a3de8a0b58be020fd9a14ed0 -- 2.43.1