This is an automated email from the ASF dual-hosted git repository.

jgemignani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git


The following commit(s) were added to refs/heads/master by this push:
     new 83426d3  Add the PostgreSQL EXPLAIN command into AGE
83426d3 is described below

commit 83426d3a92bd5fee06376a38542d35efb1e6397b
Author: John Gemignani <[email protected]>
AuthorDate: Thu May 6 16:32:32 2021 -0700

    Add the PostgreSQL EXPLAIN command into AGE
    
    Added the PostgreSQL utility command EXPLAIN to AGE's openCypher
    grammar and transform logic. The following forms are allowed -
    
        EXPLAIN
        EXPLAIN VERBOSE
        EXPLAIN ANALYZE
        EXPLAIN ANALYZE VERBOSE
    
    Note: Using ANALYZE with EXPLAIN will cause the execution of the
    openCypher command.
    
    EXPLAIN can be used in AGE as follows -
    
        EXPLAIN MATCH (u)-[e]->(v) RETURN u;
        EXPLAIN VERBOSE MATCH (u)-[e]->(v) RETURN u;
        EXPLAIN ANALYZE MATCH (u)-[e]->(v) RETURN u;
        EXPLAIN ANALYZE VERBOSE MATCH (u)-[e]->(v) RETURN u;
    
    The EXPLAIN command in AGE is implemented through PostgreSQL's
    EXPLAIN command logic. So, the output should be familiar to PG users.
    
    For more information please see PostgreSQL's documention on EXPLAIN.
---
 src/backend/parser/cypher_analyze.c  | 75 +++++++++++++++++++++++++++++++++++-
 src/backend/parser/cypher_gram.y     | 63 +++++++++++++++++++++++++++++-
 src/backend/parser/cypher_keywords.c |  3 ++
 src/backend/parser/cypher_parser.c   |  7 +++-
 src/include/parser/cypher_gram.h     |  5 +++
 5 files changed, 148 insertions(+), 5 deletions(-)

diff --git a/src/backend/parser/cypher_analyze.c 
b/src/backend/parser/cypher_analyze.c
index 9ba3432..171b726 100644
--- a/src/backend/parser/cypher_analyze.c
+++ b/src/backend/parser/cypher_analyze.c
@@ -44,6 +44,8 @@
 #include "utils/ag_func.h"
 #include "utils/agtype.h"
 
+static Node *extra_node = NULL;
+
 static post_parse_analyze_hook_type prev_post_parse_analyze_hook;
 
 static void post_parse_analyze(ParseState *pstate, Query *query);
@@ -157,6 +159,8 @@ static bool convert_cypher_walker(Node *node, ParseState 
*pstate)
     if (IsA(node, Query))
     {
         int flags;
+        bool result = false;
+        Query *query = (Query *)node;
 
         /*
          * QTW_EXAMINE_RTES
@@ -174,8 +178,54 @@ static bool convert_cypher_walker(Node *node, ParseState 
*pstate)
         flags = QTW_EXAMINE_RTES | QTW_IGNORE_RT_SUBQUERIES |
                 QTW_IGNORE_JOINALIASES;
 
-        return query_tree_walker((Query *)node, convert_cypher_walker, pstate,
-                                 flags);
+        /* clear the global variable extra_node */
+        extra_node = NULL;
+
+        /* recurse on query */
+        result = query_tree_walker(query, convert_cypher_walker, pstate, 
flags);
+
+        /* check for EXPLAIN */
+        if (extra_node != NULL && nodeTag(extra_node) == T_ExplainStmt)
+        {
+            ExplainStmt *estmt = NULL;
+            Query *query_copy = NULL;
+            Query *query_node = NULL;
+
+            /*
+             * Create a copy of the query node. This is purposely a shallow 
copy
+             * because we are only moving the contents to another pointer.
+             */
+            query_copy = (Query *) palloc(sizeof(Query));
+            memcpy(query_copy, query, sizeof(Query));
+
+            /* build our Explain node and store the query node copy in it */
+            estmt = makeNode(ExplainStmt);
+            estmt->query = (Node *)query_copy;
+            estmt->options = ((ExplainStmt *)extra_node)->options;
+
+            /* build our replacement query node */
+            query_node = makeNode(Query);
+            query_node->commandType = CMD_UTILITY;
+            query_node->utilityStmt = (Node *)estmt;
+            query_node->canSetTag = true;
+
+            /* now replace the top query node with our replacement query node 
*/
+            memcpy(query, query_node, sizeof(Query));
+
+            /*
+             * We need to free and clear the global variable when done. But, 
not
+             * the ExplainStmt options. Those will get freed by PG when the
+             * query is deleted.
+             */
+            ((ExplainStmt *)extra_node)->options = NULL;
+            pfree(extra_node);
+            extra_node = NULL;
+
+            /* we need to free query_node as it is no longer needed */
+            pfree(query_node);
+        }
+
+        return result;
     }
 
     return expression_tree_walker(node, convert_cypher_walker, pstate);
@@ -318,6 +368,27 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, 
ParseState *pstate)
 
     stmt = parse_cypher(query_str);
 
+    /*
+     * Extract any extra node passed up and assign it to the global variable
+     * 'extra_node' - if it wasn't already set. It will be at the end of the
+     * stmt list and needs to be removed for normal processing, regardless.
+     * It is done this way to allow utility commands to be processed against 
the
+     * AGE query tree. Currently, only EXPLAIN is passed here. But, it need not
+     * just be EXPLAIN - so long as it is carefully documented and carefully
+     * done.
+     */
+    if (extra_node == NULL)
+    {
+        extra_node = llast(stmt);
+        list_delete_ptr(stmt, extra_node);
+    }
+    else
+    {
+        Node *temp = llast(stmt);
+
+        list_delete_ptr(stmt, temp);
+    }
+
     cancel_errpos_ecb(&ecb_state);
 
     Assert(pstate->p_expr_kind == EXPR_KIND_NONE);
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 9990965..430da84 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -77,11 +77,11 @@
 %token NOT_EQ LT_EQ GT_EQ DOT_DOT TYPECAST PLUS_EQ EQ_TILDE
 
 /* keywords in alphabetical order */
-%token <keyword> AND AS ASC ASCENDING
+%token <keyword> ANALYZE AND AS ASC ASCENDING
                  BY
                  COALESCE CONTAINS CREATE
                  DELETE DESC DESCENDING DETACH DISTINCT
-                 ENDS EXISTS
+                 ENDS EXISTS EXPLAIN
                  FALSE_P
                  IN IS
                  LIMIT
@@ -91,6 +91,7 @@
                  REMOVE RETURN
                  SET SKIP STARTS
                  TRUE_P
+                 VERBOSE
                  WHERE WITH
 
 /* query */
@@ -208,6 +209,64 @@ stmt:
                 yyerror(&yylloc, scanner, extra, "syntax error");
 
             extra->result = $1;
+            extra->extra = NULL;
+        }
+    | EXPLAIN single_query semicolon_opt
+        {
+            ExplainStmt *estmt = NULL;
+
+            if (yychar != YYEOF)
+                yyerror(&yylloc, scanner, extra, "syntax error");
+
+            extra->result = $2;
+
+            estmt = makeNode(ExplainStmt);
+            estmt->query = NULL;
+            estmt->options = NIL;
+            extra->extra = (Node *)estmt;
+        }
+    | EXPLAIN VERBOSE single_query semicolon_opt
+        {
+            ExplainStmt *estmt = NULL;
+
+            if (yychar != YYEOF)
+                yyerror(&yylloc, scanner, extra, "syntax error");
+
+            extra->result = $3;
+
+            estmt = makeNode(ExplainStmt);
+            estmt->query = NULL;
+            estmt->options = list_make1(makeDefElem("verbose", NULL, @2));;
+            extra->extra = (Node *)estmt;
+        }
+    | EXPLAIN ANALYZE single_query semicolon_opt
+        {
+            ExplainStmt *estmt = NULL;
+
+            if (yychar != YYEOF)
+                yyerror(&yylloc, scanner, extra, "syntax error");
+
+            extra->result = $3;
+
+            estmt = makeNode(ExplainStmt);
+            estmt->query = NULL;
+            estmt->options = list_make1(makeDefElem("analyze", NULL, @2));;
+            extra->extra = (Node *)estmt;
+        }
+    | EXPLAIN ANALYZE VERBOSE single_query semicolon_opt
+        {
+            ExplainStmt *estmt = NULL;
+
+            if (yychar != YYEOF)
+                yyerror(&yylloc, scanner, extra, "syntax error");
+
+            extra->result = $4;
+
+            estmt = makeNode(ExplainStmt);
+            estmt->query = NULL;
+            estmt->options = list_make2(makeDefElem("analyze", NULL, @2),
+                                        makeDefElem("verbose", NULL, @3));;
+            extra->extra = (Node *)estmt;
         }
     ;
 
diff --git a/src/backend/parser/cypher_keywords.c 
b/src/backend/parser/cypher_keywords.c
index 1f136e6..0ff6129 100644
--- a/src/backend/parser/cypher_keywords.c
+++ b/src/backend/parser/cypher_keywords.c
@@ -34,6 +34,7 @@
  * locate entries.
  */
 const ScanKeyword cypher_keywords[] = {
+    {"analyze", ANALYZE, RESERVED_KEYWORD},
     {"and", AND, RESERVED_KEYWORD},
     {"as", AS, RESERVED_KEYWORD},
     {"asc", ASC, RESERVED_KEYWORD},
@@ -49,6 +50,7 @@ const ScanKeyword cypher_keywords[] = {
     {"distinct", DISTINCT, RESERVED_KEYWORD},
     {"ends", ENDS, RESERVED_KEYWORD},
     {"exists", EXISTS, RESERVED_KEYWORD},
+    {"explain", EXPLAIN, RESERVED_KEYWORD},
     {"false", FALSE_P, RESERVED_KEYWORD},
     {"in", IN, RESERVED_KEYWORD},
     {"is", IS, RESERVED_KEYWORD},
@@ -64,6 +66,7 @@ const ScanKeyword cypher_keywords[] = {
     {"skip", SKIP, RESERVED_KEYWORD},
     {"starts", STARTS, RESERVED_KEYWORD},
     {"true", TRUE_P, RESERVED_KEYWORD},
+    {"verbose", VERBOSE, RESERVED_KEYWORD},
     {"where", WHERE, RESERVED_KEYWORD},
     {"with", WITH, RESERVED_KEYWORD}
 };
diff --git a/src/backend/parser/cypher_parser.c 
b/src/backend/parser/cypher_parser.c
index 4e1a7cf..68bb728 100644
--- a/src/backend/parser/cypher_parser.c
+++ b/src/backend/parser/cypher_parser.c
@@ -128,6 +128,7 @@ List *parse_cypher(const char *s)
 
     scanner = ag_scanner_create(s);
     extra.result = NIL;
+    extra.extra = NULL;
 
     yyresult = cypher_yyparse(scanner, &extra);
 
@@ -140,5 +141,9 @@ List *parse_cypher(const char *s)
     if (yyresult)
         return NIL;
 
-    return extra.result;
+    /*
+     * Append the extra node node regardless of its value. Currently the extra
+     * node is only used by EXPLAIN
+    */
+    return lappend(extra.result, extra.extra);
 }
diff --git a/src/include/parser/cypher_gram.h b/src/include/parser/cypher_gram.h
index 3c448ee..cffbba3 100644
--- a/src/include/parser/cypher_gram.h
+++ b/src/include/parser/cypher_gram.h
@@ -42,6 +42,11 @@
 typedef struct cypher_yy_extra
 {
     List *result;
+    /*
+     * This node currently holds the EXPLAIN ExplainStmt node. It is generic in
+     * the event we need to allow more than just EXPLAIN to be passed up.
+     */
+    Node *extra;
 } cypher_yy_extra;
 
 /*

Reply via email to