From 4baf9162eaf00588c9ee0882e532151c1021313d Mon Sep 17 00:00:00 2001 From: Ayoub Moussaid Date: Mon, 20 May 2024 12:07:51 +0100 Subject: [PATCH 1/4] Add code tabs script --- Gemfile.lock | 157 +- GenerateCodeBlocks.java | 170 ++ css/customstyles.css | 47 + js/customscripts.js | 31 + pre-pages/pgql-2.0-spec.md | 5170 ++++++++++++++++++++++++++++++++++++ 5 files changed, 5500 insertions(+), 75 deletions(-) create mode 100644 GenerateCodeBlocks.java create mode 100644 pre-pages/pgql-2.0-spec.md diff --git a/Gemfile.lock b/Gemfile.lock index 07956b34..44c1ad97 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,47 +1,55 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.5.1) + activesupport (7.1.3.2) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) + bigdecimal (3.1.7) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.11.1) + coffee-script-source (1.12.2) colorator (1.1.0) - commonmarker (0.23.5) - concurrent-ruby (1.1.10) - dnsruby (1.61.9) - simpleidn (~> 0.1) + commonmarker (0.23.10) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + dnsruby (1.72.1) + simpleidn (~> 0.2.1) + drb (2.2.1) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.8.1) - faraday (2.4.0) - faraday-net_http (~> 2.0) - ruby2_keywords (>= 0.0.4) - faraday-net_http (2.1.0) - ffi (1.15.5) + execjs (2.9.1) + faraday (2.9.0) + faraday-net_http (>= 2.0, < 3.2) + faraday-net_http (3.1.0) + net-http + ffi (1.16.3-x64-mingw-ucrt) forwardable-extended (2.6.0) - gemoji (3.0.1) - github-pages (227) - github-pages-health-check (= 1.17.9) - jekyll (= 3.9.2) - jekyll-avatar (= 0.7.0) - jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.2.0) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.15.1) + gemoji (4.1.0) + github-pages (231) + github-pages-health-check (= 1.18.2) + jekyll (= 3.9.5) + jekyll-avatar (= 0.8.0) + jekyll-coffeescript (= 1.2.2) + jekyll-commonmark-ghpages (= 0.4.0) + jekyll-default-layout (= 0.1.5) + jekyll-feed (= 0.17.0) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.13.0) + jekyll-github-metadata (= 2.16.1) jekyll-include-cache (= 0.2.1) jekyll-mentions (= 1.6.0) jekyll-optional-front-matter (= 0.3.2) @@ -68,32 +76,32 @@ GEM jekyll-theme-tactile (= 0.2.0) jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.12.0) - kramdown (= 2.3.2) + jemoji (= 0.13.0) + kramdown (= 2.4.0) kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.3) + liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) nokogiri (>= 1.13.6, < 2.0) - rouge (= 3.26.0) + rouge (= 3.30.0) terminal-table (~> 1.4) - github-pages-health-check (1.17.9) + github-pages-health-check (1.18.2) addressable (~> 2.3) dnsruby (~> 1.60) - octokit (~> 4.0) - public_suffix (>= 3.0, < 5.0) + octokit (>= 4, < 8) + public_suffix (>= 3.0, < 6.0) typhoeus (~> 1.3) - html-pipeline (2.14.2) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (0.9.5) + i18n (1.14.4) concurrent-ruby (~> 1.0) - jekyll (3.9.2) + jekyll (3.9.5) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) @@ -102,27 +110,27 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.7.0) + jekyll-avatar (0.8.0) jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.1.1) + jekyll-coffeescript (1.2.2) coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) + coffee-script-source (~> 1.12) jekyll-commonmark (1.4.0) commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.2.0) - commonmarker (~> 0.23.4) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) jekyll (~> 3.9.0) jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 4.0) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.15.1) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.5) + jekyll (>= 3.0, < 5.0) + jekyll-feed (0.17.0) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.13.0) + jekyll-github-metadata (2.16.1) jekyll (>= 3.4, < 5.0) - octokit (~> 4.0, != 4.4.0) + octokit (>= 4, < 7, != 4.4.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) jekyll-mentions (1.6.0) @@ -193,41 +201,41 @@ GEM jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.12.0) - gemoji (~> 3.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.3.2) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) + liquid (4.0.4) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.8.0) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.16.2) - nokogiri (1.13.8) - mini_portile2 (~> 2.8.0) + minitest (5.22.3) + mutex_m (0.2.0) + net-http (0.4.1) + uri + nokogiri (1.16.4-x64-mingw-ucrt) racc (~> 1.4) octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.7) - racc (1.6.0) - rb-fsevent (0.11.1) + public_suffix (5.0.5) + racc (1.7.3) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) - rouge (3.26.0) - ruby2_keywords (0.0.5) + rexml (3.2.6) + rouge (3.30.0) rubyzip (2.3.2) safe_yaml (1.0.5) sass (3.7.4) @@ -242,20 +250,19 @@ GEM unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) - tzinfo (1.2.10) - thread_safe (~> 0.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.2) + unf_ext (0.0.9.1-x64-mingw-ucrt) unicode-display_width (1.8.0) - webrick (1.7.0) - zeitwerk (2.6.0) + uri (0.13.0) + webrick (1.8.1) PLATFORMS - ruby + x64-mingw-ucrt DEPENDENCIES github-pages @@ -263,4 +270,4 @@ DEPENDENCIES webrick BUNDLED WITH - 2.0.2 + 2.5.9 diff --git a/GenerateCodeBlocks.java b/GenerateCodeBlocks.java new file mode 100644 index 00000000..f42a7bbf --- /dev/null +++ b/GenerateCodeBlocks.java @@ -0,0 +1,170 @@ +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GenerateCodeBlocks { + private static String[] KEYWORDS = { "MATCH", "PATHS", "DATE", "TIME", "TIMEZONE", "INTEGER", + "BOOLEAN", + "STRING", "ARRAY_AGG", "LISTAGG", "LABELS", "HAS_LABEL", "LABEL", "ALL_DIFFERENT", "IN_DEGREE", + "OUT_DEGREE", + "CEILING", "CEIL", "FLOOR", "ROUND", "JAVA_REGEXP_LIKE", "LOWER", "SUBSTRING", "UPPER", "HOUR", "TOP", + "SHORTEST", + "PROPERTIES", "VERTEX", "EDGE", "GRAPH_TABLE", "PROPERTY", "GRAPH", "TABLES", "DESTINATION", "COLUMNS", "CHEAPEST", "COST", + "ONE", "PER", "STEP", "INTERVAL", "PREFIX", "WALK", "ACYCLIC", "SIMPLE", "TRAIL", + "LABELED", "KEEP", "ACCESS", "ADD", "ALL", "ALTER", "AND", "ANY", "ASC", "AUDIT", "BETWEEN", + "BY", "CHAR", "CHECK", "CLUSTER", "COLUMN", "COMMENT", "COMPRESS", "CONNECT", + "CREATE", "CURRENT", "DATE", "DECIMAL", "DEFAULT", "DELETE", "DESC", "DISTINCT", + "DROP", "ELSE", "EXCLUSIVE", "EXISTS", "FILE", "FLOAT", "FOR", "FROM", "GRANT", + "GROUP", "HAVING", "IDENTIFIED", "IN", "INDEX", "INITIAL", + "INSERT", "INTEGER", "INTERSECT", "INTO", "IS", "LEVEL", "LIKE", "LOCK", "LONG", + "MINUS", "MODE", "MODIFY", + "NOT", "NULL", "NUMBER", "OF", "OFFLINE", "ONLINE", "OPTION", + "ORDER", "OR", "PRIVILEGES", "PUBLIC", "RAW", "RENAME", + "RESOURCE", "REVOKE", "ROWID", "ROWNUM", "ROWS", "ROW", "SELECT", "SESSION", + "SET", "SHARE", "SIZE", "SMALLINT", "START", "SUCCESSFUL", "SYNONYM", "SYSDATE", + "TABLE", "THEN", "TO", "TRIGGER", "UID", "UNION", "UNIQUE", "UPDATE", "USER", + "VALIDATE", "VALUES", "VARCHAR", "VARCHAR2", "VIEW", "WHENEVER", "WHERE", "WITH", + "SUM", "ABS", "ID", "PATH", "ON", "AS"}; + + //private static String opRegex = "(?=([^'\"]|'[^']*'|\"[^\"]*\")*$)([\\+\\-\\*\\/=%><\\|])"; + //private static String valRegex = "('.*?'|\\b\\d+\\b)"; + + private static String regex; + + + public static class StringLengthComparator implements Comparator { + @Override + public int compare(String s1, String s2) { + return Integer.compare(s2.length(), s1.length()); + } + } + + public static void convertMarkdownToHTML(String inputFilePath, String outputFilePath) { + try { + // Create BufferedReader to read input file + BufferedReader reader = new BufferedReader(new FileReader(inputFilePath)); + + // Create BufferedWriter to write output file + BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath)); + + // Initialize flag to track if within code block + boolean inCodeBlock = false; + + // Placeholder HTML code + // String htmlStart = "
"; + String buttonCode = "
\n" + + "\n" + + + "\n" + + + "
"; + // String htmlEnd = "
"; + String codeBlock = ""; + String pgql = ""; + String sql = ""; + String[] tosplit; + + // Loop through each line in the file + String line; + while ((line = reader.readLine()) != null) { + // Check if line starts a code block + if (line.startsWith("```sql")) { + inCodeBlock = true; + } else if (inCodeBlock && line.startsWith("```")) { + tosplit = codeBlock.split("(?i)--SQL"); + if (tosplit.length == 2) { + writer.write(buttonCode); + pgql = getHtmlForQuery(tosplit[0].substring(6), true); + sql = getHtmlForQuery(tosplit[1], false); + } else { + pgql = "```sql\n" + tosplit[0] + "```"; + sql = ""; + } + writer.write(sql); + writer.newLine(); + writer.write(pgql); + codeBlock = ""; + inCodeBlock = false;// Reset codeBlock for next block + } else { + // If within a code block, accumulate code lines + if (inCodeBlock) { + codeBlock += line + "\n"; // Add newline character + } else { + // If not in a code block, write the line as is + writer.write(line); + writer.newLine(); + } + } + } + + // Close reader and writer + reader.close(); + writer.close(); + + System.out.println("Conversion completed successfully."); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static String getHtmlForQuery(String query, boolean isPgql) { + return "
" + + + "
"
+                + replaceWithHTML(query)
+                + "
"; + } + + + public static String replaceWithHTML(String input) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(input); + StringBuilder result = new StringBuilder(); + + while (matcher.find()) { + String match = matcher.group(); + String replacement; + + if (match.matches("('.*?'|\\b\\d+\\b)")) { + replacement = "" + match + ""; + } else if (match.matches("(?=([^'\"]|'[^']*'|\"[^\"]*\")*$)([\\+\\-\\*\\/=%><\\|])")) { + replacement = "" + match + ""; + } else if (isKeyword(match)) { + replacement = "" + match + ""; + }else{ + replacement = match; + } + + matcher.appendReplacement(result, replacement); + } + + matcher.appendTail(result); + return result.toString(); + } + + private static boolean isKeyword(String str) { + for (String keyword : KEYWORDS) { + if (str.equals(keyword)) { + return true; + } + } + return false; + } + + public static void main(String[] args) { + // Provide input and output file paths + String inputFilePath = "./pre-pages/pgql-2.0-spec.md"; + String outputFilePath = "./pages/pgql-2.0-spec.md"; + + Arrays.sort(KEYWORDS, new StringLengthComparator()); + regex = "('.*?'|\\b\\d+\\b)|(?=([^'\"]|'[^']*'|\"[^\"]*\")*$)([\\+\\-\\*\\/=%><\\|])|(?:" + String.join("|", KEYWORDS) + ")"; + // Call the convertMarkdownToHTML method + convertMarkdownToHTML(inputFilePath, outputFilePath); + } +} diff --git a/css/customstyles.css b/css/customstyles.css index 9b3ca986..b0115bc1 100644 --- a/css/customstyles.css +++ b/css/customstyles.css @@ -1173,3 +1173,50 @@ h4.panel-title { padding-top: 0px; margin-top: 0px; } + + +.tab-content { + display: none; + padding: 0px; + margin: 0px 0px 0px 0px; + border: 1px solid #ccc; + } + + /* Style the active tab content */ + .tab-content.active { + display: contents; + padding: 0px; + margin: 0px; + } + + div.tab-content div.highlight pre { + margin: 0px 0px 25px 0px; + } + + /* Style the tabs */ + .tab { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; + } + + /* Style the tab buttons */ + .tab button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 5px 10px; + transition: 0.3s; + } + + /* Change background color of buttons on hover */ + .tab button:hover { + background-color: #ddd; + } + + /* Style the active tab button */ + .tab button.active { + background-color: #ccc; + } \ No newline at end of file diff --git a/js/customscripts.js b/js/customscripts.js index cf742edd..2d05b60a 100644 --- a/js/customscripts.js +++ b/js/customscripts.js @@ -53,3 +53,34 @@ $(function() { } }); }); + +// Function to switch between tabs +function openTab(evt, tabName) { + var i, tabcontent, tablinks; + + // Hide all tab content + tabcontent = document.getElementsByClassName("tab-content"); + console.log(tabcontent); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + // Remove 'active' class from all tab buttons + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + + // Show the specific tab content + document.getElementsByName(tabName).forEach(e => e.style.display = "contents") + + // Add 'active' class to the clicked tab button + buttonid = evt.currentTarget.getAttribute("name"); + + buttons = document.getElementsByName(buttonid); + + for (i = 0; i < buttons.length; i++) { + buttons[i].className += " active"; + } + +} diff --git a/pre-pages/pgql-2.0-spec.md b/pre-pages/pgql-2.0-spec.md new file mode 100644 index 00000000..34d1aa03 --- /dev/null +++ b/pre-pages/pgql-2.0-spec.md @@ -0,0 +1,5170 @@ +--- +title: "PGQL 2.0 Specification" +date: "19 May 2023" +permalink: /spec/2.0/ +summary: "PGQL is an SQL-based query language for the property graph data model that allows +you to specify high-level graph patterns which are matched against vertices and edges in a graph. +PGQL has support for grouping (GROUP BY), aggregation (e.g. MIN, MAX, AVG, SUM), sorting (ORDER BY) and many other familiar SQL constructs. +Furthermore, PGQL has powerful regular expression constructs for graph reachability (transitive closure), shortest path finding and +cheapest path finding." +sidebar: spec_2_0_sidebar +toc: false +--- + +# Introduction + +PGQL is a graph pattern-matching query language for the [property graph data model](#property-graph-data-model). This document specifies the syntax and semantics of the language. + +## Changelog + +The following are the changes since PGQL 1.5: + +### New features in PGQL 2.0 + +The new (and fully SQL-compatible) features are: + + - [GRAPH_TABLE Operator](#graph_table-operator) + - [LATERAL Subquery](#lateral-subqueries) + - [Path Modes](#path-modes) (`WALK`, `ACYCLIC`, `SIMPLE`, `TRAIL`) + - [KEEP Clause](#KeepClause) + - [LABELED Predicate](#labeled-predicate), [SOURCE/DESTINATION Predicate](#source--destination-predicate), [MATCHNUM Function](#matchnum-function) and [VERTEX_ID/EDGE_ID Function](#vertex_idedge_id-function) + - [FETCH FIRST Clause](#fetch-first-clause) + +## A note on the Grammar + +This document contains a complete grammar definition of PGQL, spread throughout the different sections. There is a single entry point into the grammar: . + +## Document Outline + + - [Introduction](#introduction) contains a changelog, a note on the grammar, this outline and an introduction to the property graph data model. + - [Creating a Property Graph](#creating-a-property-graph) describes how to create a property graph from an existing set of tables in a relational database. + - [Graph Pattern Matching](#graph-pattern-matching) introduces the basic concepts of graph querying. + - [Variable-Length Paths](#variable-length-paths) introduces the constructs for matching variable-length paths such as shortest paths or cheapests paths. + - [Number of Rows Per Match](#number-of-rows-per-match) describes how to specify the number of rows per match, for example to obtain one row per vertex in a shortest path. + - [Grouping and Aggregation](#grouping-and-aggregation) describes the mechanism to group and aggregate results. + - [Sorting and Row Limiting](#sorting-and-row-limiting) describes the ability to sort and paginate results. + - [Functions and Expressions](#functions-and-expressions) describes the supported data types and corresponding functions and operations. + - [Subqueries](#subqueries) describes the syntax and semantics of subqueries for creating more complex queries that nest other queries. + - [Graph Modification](#graph-modification) describes `INSERT`, `UPDATE` and `DELETE` statements for inserting, updating and deleting vertices and edges in a graph. + - [Other Syntactic rules](#other-syntactic-rules) describes additional syntactic rules that are not covered by the other sections, such as syntax for identifiers and comments. + +## Property graph data model + +A property graph has a name, which is a (character) string, and contains: + + - A set of vertices (or nodes). + + - Each vertex has zero or more labels. + - Each vertex has zero or more properties (or attributes), which are arbitrary key-value pairs. + + - A set of edges (or relationships). + + - Each edge is directed. + - Each edge has zero or more labels. + - Each edge has zero or more properties (or attributes), which are arbitrary key-value pairs. + +Labels as well as names of properties are strings. Property values are scalars such as numerics, strings or booleans. + +### Example 1: Student Network + +An example graph is: + +{% include image.html file="example_graphs/student_network.png" %} + +Here, `student_network` is the name of the graph. The graph has three vertices labeled `Person` and one vertex labeled `University`. There are six directed edges that connect the vertices. Three of them go from person to person vertices, and they have the label `knows`. Three others go from person to university vertices and are labeled `studentOf`. The person vertices have two properties, namely `name` for encoding the name of the person and `dob` for encoding the date of birth of the person. The university vertex has only a single property `name` for encoding the name of the university. The edges have no properties. + +### Example 2: Financial Transactions + +An example graph with financial transactions is: + +{% include image.html file="example_graphs/financial_transactions.png" %} + +Here, `financial_transactions` is the name of the graph. The graph has three types of vertices. Vertices labeled `Person` or `Company` have a property `name`, while vertices labeled `Account` have a property `number`. There are edges labeled `owner` from accounts to persons as well as from accounts to companies, and there are edges labeled `transaction` from accounts to accounts. Note that only `transaction` edges have a property (`amount`) while other edges do not have any properties. + + +# Creating a Property Graph + +The [CREATE PROPERTY GRAPH](#create-property-graph) statement allows for creating a property graph from a set of existing database tables, +while the [DROP PROPERTY GRAPH](#drop-property-graph) statements allows for dropping a graph. + +## CREATE PROPERTY GRAPH + +The `CREATE PROPERTY GRAPH` statement starts with a graph name and is followed by a non-empty set of vertex tables and an optional set of edge tables. + +The syntax is: + +```bash +CreatePropertyGraph ::= 'CREATE' 'PROPERTY' 'GRAPH' + + ? + +GraphName ::= + +SchemaQualifiedName ::= ? + +SchemaIdentifierPart ::= '.' + +VertexTables ::= 'VERTEX' 'TABLES' '(' ( ',' )* ')' + +EdgeTables ::= 'EDGE' 'TABLES' '(' ( ',' )* ')' +``` + +It is possible to have no edge tables such that the resulting graph only has vertices that are all disconnected from each other. +However, it is not possible to have a graph with edge tables but no vertex tables. + +The following example shows a schema with a set of tables. Each table has a name and a list of columns, some of which form the primary key for the table (in red) while others form foreign keys that reference rows of other tables. + +{% include image.html file="example_graphs/financial_transactions_schema.png" %} + +The following is a complete example of how a graph can be created from these tables: + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + Persons LABEL Person PROPERTIES ( name ), + Companies LABEL Company PROPERTIES ( name ), + Accounts LABEL Account PROPERTIES ( number ) + ) + EDGE TABLES ( + Transactions + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Persons + LABEL owner NO PROPERTIES, + Accounts AS CompanyOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Companies + LABEL owner NO PROPERTIES, + Persons AS worksFor + SOURCE KEY ( id ) REFERENCES Persons ( id ) + DESTINATION Companies + NO PROPERTIES + ) +``` + +Above, `financial_transactions` is the name of the graph. +The graph has three vertex tables: `Persons`, `Companies` and `Accounts`. +The graph also has four edge tables: `Transactions`, `PersonOwner`, `CompanyOwner` and `worksFor`. + +Underlying foreign keys are used to establish the connections between the two endpoints of the edges and the corresponding vertices. +Note that the "source" of an edge is the vertex where the edge points _from_ while the "destination" of an edge is the vertex where the edge point _to_. + +If foreign keys cannot be used or are not present, the necessary keys can be defined as part of the `CREATE PROPERTY GRAPH` statement. +Labels and properties can also be defined, all of which is explained in more detail in the next sections. + +### Vertex tables + +A vertex table provides a vertex for each row of the underlying table. + +The syntax is: + +```bash +VertexTable ::= ? ? ? + +LabelAndPropertiesClause ::= ? ? + +TableName ::= +``` + +The [table alias](#table-aliases) is required only if the underlying table is used as vertex table more than once, to provide a unique name for each table. +It can be used for specifying a [label](#labels) for the vertices too. + +The key of the vertex table uniquely identifies a row in the table. +If a key is not explicitly specified then it defaults to the primary key of the underlying table. +A key is always required so a primary key needs to exist if no key is specified. +See the section on [keys](#keys) for more details. + +The label clause provides a label for the vertices. +If a label is not defined, the label defaults to the alias. +Since the alias defaults to the name of the underlying table, if no alias is provided, the label defaults to the name of the underlying table. +See the section on [labels](#labels) for details. + +The properties clause defines the mapping from columns of the underlying table into properties of the vertices. +See the section on [properties](#properties) for more details. + +### Edge tables + +An edge table provides an edge for each row of the underlying table. + +```bash +EdgeTable ::= ? ? + + ? + +SourceVertexTable ::= 'SOURCE' ? + +DestinationVertexTable ::= 'DESTINATION' ? + +ReferencedVertexTableKeyClause ::= 'REFERENCES' +``` + +The [table alias](#table-aliases) is required only if the underlying table is used as edge table more than once, to provide a unique name for each table. +It can be used for specifying a [label](#labels) for the edges too. + +The source vertex table and destination vertex table are mandatory for defining the two endpoints of the edge. +A key is optional if there is a single foreign key from the edge table to the source or destination vertex table. +If a key is not provided, it will default to the existing foreign key. + +Take the following example from before: + +{% include image.html file="example_graphs/financial_transactions_schema.png" %} + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + Persons LABEL Person PROPERTIES ( name ), + Companies LABEL Company PROPERTIES ( name ), + Accounts LABEL Account PROPERTIES ( number ) + ) + EDGE TABLES ( + Transactions + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Persons + LABEL owner NO PROPERTIES, + Accounts AS CompanyOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Companies + LABEL owner NO PROPERTIES, + Persons AS worksFor + SOURCE KEY ( id ) REFERENCES Persons ( id ) + DESTINATION Companies + NO PROPERTIES + ) +``` + +The key of the edge table uniquely identifies a row in the table. +If a key is not explicitly specified (in case of all four edge tables above) then it defaults to the primary key of the underlying table. +A key is always required so a primary key needs to exist if no key is specified. +See the section on [keys](#keys) for more details. + +In case of edge tables `PersonOwner`, `CompanyOwner` and `worksFor`, the destination vertex table is the same table as the edge table itself. +This means that rows in the table are mapped into both vertices and edges. It is also possible that the source vertex table is the edge table itself or that both the source and destination tables are the edge table itself. +This is explained in more detail in [Source or destination is self](#source-or-destination-is-self). + +Keys for the destinations of `PersonOwner`, `CompanyOwner` and `worksFor` are omitted because we can default to the existing foreign keys. +Keys for their sources cannot be omitted because there exist no foreign key to default to (e.g. in case of `PersonOwner` there are zero foreign keys from `Accounts` to `Accounts` hence `SOURCE KEY ( number ) REFERENCES Accounts ( number )` needs to be specified). +Furthermore, keys for the source and destination of `Transactions` cannot be omitted because _two_ foreign keys exist between `Transactions` and `Accounts` so it is necessary to specify which one to use. + +If a row in an edge table has a NULL value for any of its source key columns or its destination key columns then no edge is created. +Note that in case of the `Accounts` table from the example, it is assumed that either the `person_id` or the `company_id` is NULL, so that each time the row is mapped into either a "company owner" or a "person owner" edge but never into two types of edges at once. + +The label clause provides a label for the edges. +If a label is not defined, the label defaults to the alias. +Since the alias defaults to the name of the underlying table, if no alias is provided, the label defaults to the name of the underlying table. +See the section on [labels](#labels) for details. + +The properties clause defines the mapping from columns of the underlying table to properties of the edges. +See the section on [properties](#properties) for more details + +### Table aliases + +Vertex and edge tables can have aliases for uniquely naming the tables. +If no alias is defined, then the alias defaults to the name of the underlying database table of the vertex or edge table. + +The syntax is: + +```bash +TableAlias ::= ( 'AS' )? +``` + +For example: + +```sql +... + EDGE TABLES ( Persons AS worksFor ... ) +... +``` + +Above, the underlying table of the edge table is `Persons`, while the alias is `worksFor`. + +All vertex and edge tables are required to have unique names. +Therefore, if multiple vertex tables use the same underlying table, then at least one of them requires an alias. +Similarly, if multiple edge tables use the same underlying table, then at least one of them requires an alias. +The restriction does not apply across vertex and edge tables, so, there may exist a vertex table with the same name as an edge table, +but there may not exist two vertex tables with the same name, or two edge tables with the same name. + +If the alias is not provided then it defaults to the name of the underlying table. +For example: + +```sql +... + VERTEX TABLES ( Person ) +... +``` + +Above is equivalent to: + +```sql +... + VERTEX TABLES ( Person AS Person ) +... +``` + +Finally, in addition to providing unique names for vertex and edge tables, the aliases can also serve as a means to provide [labels](#labels) for vertices and edges: +if no label is defined then the label defaults to the table alias. +Note that although table aliases are required to be unique, labels are not. +In other words, multiple vertex tables and multiple edge tables can have the same label. + +### Keys + +By default, existing primary and foreign keys of underlying tables are used to connect the end points of the edges to the appropriate vertices, but the following scenarios +require manual specification of keys: + + - Multiple foreign keys exists between an edge table and its source vertex table or its destination vertex tables such that it would be ambiguous which foreign key to use. + - Primary and/or foreign keys on underlying tables were not defined or the underlying tables are views which means that primary and foreign keys cannot be defined. + +The syntax for keys is: + +```bash +KeyClause ::= '(' ( ',' )* ')' + +ColumnName ::= +``` + +Take the example from before: + +{% include image.html file="example_graphs/financial_transactions_schema.png" %} + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + ... + ) + EDGE TABLES ( + Transactions + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION Persons + LABEL owner NO PROPERTIES, + ... + ) +``` + +Above, a key is defined for the source and destination of `Transactions` because two foreign keys exist between `Transactions` and `Accounts` so it would be ambiguous which one to use without explicit specification. +In case of `PersonOwner`, no foreign key exists between `Accounts` and `Accounts` so a key for the source (`KEY ( number )`) has to be explicitly specified. However, for the destination it is possible to omit the key and default to the existing foreign key between `Accounts` and `Persons`. + +The keys for source and destination vertex tables consist of one or more columns of the underlying edge table that uniquely identify a vertex in the corresponding vertex table. If no key is defined for the vertex table, the key defaults to the underlying primary key, which is required to exist in such a case. + +The following example has a schema that has no primary and foreign keys defined at all: + +{% include image.html file="example_graphs/financial_transactions_schema_no_keys.png" %} + +Note that above, we have the same schema as before, but this time the primary and foreign keys are missing. + +Even though primary and foreign keys are missing, the graph can still be created by specifying the necessary keys in the `CREATE PROPERTY GRAPH` statement itself: + +```sql +CREATE PROPERTY GRAPH financial_transactions + VERTEX TABLES ( + Persons + KEY ( id ) + LABEL Person + PROPERTIES ( name ), + Companies + KEY ( id ) + LABEL Company + PROPERTIES ( name ), + Accounts + KEY ( number ) + LABEL Account + PROPERTIES ( number ) + ) + EDGE TABLES ( + Transactions + KEY ( from_account, to_account, date ) + SOURCE KEY ( from_account ) REFERENCES Accounts ( number ) + DESTINATION KEY ( to_account ) REFERENCES Accounts ( number ) + LABEL transaction PROPERTIES ( amount ), + Accounts AS PersonOwner + KEY ( number ) + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION KEY ( person_id ) REFERENCES Persons ( id ) + LABEL owner NO PROPERTIES, + Accounts AS CompanyOwner + KEY ( number ) + SOURCE KEY ( number ) REFERENCES Accounts ( number ) + DESTINATION KEY ( company_id ) REFERENCES Companies ( id ) + LABEL owner NO PROPERTIES, + Persons AS worksFor + KEY ( id ) + SOURCE KEY ( id ) REFERENCES Persons ( id ) + DESTINATION KEY ( company_id ) REFERENCES Companies ( id ) + NO PROPERTIES + ) +``` + +Above, keys were defined for each vertex table (e.g. `KEY ( id )`), edge table (e.g. `KEY ( from_account, to_account, date )`), source vertex table reference (e.g. `KEY ( from_account )`) and destination table reference (e.g. `KEY ( to_account )`). + +Each vertex and edge table is required to have a key so that if a key is not explicitly specified then the underlying table needs to have a primary key defined. + +### Labels + +In graphs created through `CREATE PROPERTY GRAPH`, each vertex has exactly one label and each edge has exactly one label. +This restriction may be lifted in future PGQL version. + +The syntax for labels is: + +```bash +LabelClause ::= 'LABEL'