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;
/*