This is an automated email from the ASF dual-hosted git repository.
dehowef 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 a389bbc Implement CASE statement
a389bbc is described below
commit a389bbcf1979cb0c991441dad4faced81f7a2923
Author: Dehowe Feng <[email protected]>
AuthorDate: Mon May 24 12:53:15 2021 -0700
Implement CASE statement
Implemented PostgreSQL CASE statements into AGE.
CASE has the following forms:
CASE WHEN condition THEN result END
CASE expression WHEN value THEN result END
The logic that CASE uses is largely similar to that of PostgreSQL's
CASE logic.
---
regress/expected/expr.out | 93 ++++++++++++++++++++++
regress/sql/expr.sql | 37 +++++++++
src/backend/parser/cypher_expr.c | 144 ++++++++++++++++++++++++++++++++++-
src/backend/parser/cypher_gram.y | 85 +++++++++++++++++++--
src/backend/parser/cypher_keywords.c | 5 ++
5 files changed, 356 insertions(+), 8 deletions(-)
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index 384bff3..68ec278 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -5086,6 +5086,89 @@ $$) AS (i agtype);
{"key": "value"}
(9 rows)
+--CASE
+SELECT create_graph('case_statement');
+NOTICE: graph "case_statement" has been created
+ create_graph
+--------------
+
+(1 row)
+
+SELECT * FROM cypher('case_statement', $$CREATE ({i: 1, j: null})$$) AS
(result agtype);
+ result
+--------
+(0 rows)
+
+SELECT * FROM cypher('case_statement', $$CREATE ({i: 'a', j: 'b'})$$) AS
(result agtype);
+ result
+--------
+(0 rows)
+
+SELECT * FROM cypher('case_statement', $$CREATE ({i: 0, j: 1})$$) AS (result
agtype);
+ result
+--------
+(0 rows)
+
+SELECT * FROM cypher('case_statement', $$CREATE ({i: true, j: false})$$) AS
(result agtype);
+ result
+--------
+(0 rows)
+
+SELECT * FROM cypher('case_statement', $$CREATE ({i: [], j: [0,1,2]})$$) AS
(result agtype);
+ result
+--------
+(0 rows)
+
+SELECT * FROM cypher('case_statement', $$CREATE ({i: {}, j: {i:1}})$$) AS
(result agtype);
+ result
+--------
+(0 rows)
+
+--CASE WHEN condition THEN result END
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n.i, n.j, CASE
+ WHEN null THEN 'should not return me'
+ WHEN n.i = 1 THEN 'i is 1'
+ WHEN n.j = 'b' THEN 'j is b'
+ WHEN n.i = 0 AND n.j = 1 THEN '0 AND 1'
+ WHEN n.i = true OR n.j = true THEN 'i or j true'
+ ELSE 'default'
+ END
+$$ ) AS (i agtype, j agtype, case_statement agtype);
+ i | j | case_statement
+------+-----------+----------------
+ 1 | null | "i is 1"
+ "a" | "b" | "j is b"
+ 0 | 1 | "0 AND 1"
+ true | false | "i or j true"
+ [] | [0, 1, 2] | "default"
+ {} | {"i": 1} | "default"
+(6 rows)
+
+--CASE expression WHEN value THEN result END
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n.j, CASE n.j
+ WHEN null THEN 'should not return me'
+ WHEN 'b' THEN 'b'
+ WHEN 1 THEN 1
+ WHEN false THEN false
+ WHEN [0,1,2] THEN [0,1,2]
+ WHEN {i:1} THEN {i:1}
+ ELSE 'not a or b'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+ j | case_statement
+-----------+----------------
+ null | "not a or b"
+ "b" | "b"
+ 1 | 1
+ false | false
+ [0, 1, 2] | [0, 1, 2]
+ {"i": 1} | {"i": 1}
+(6 rows)
+
-- RETURN * and (u)--(v) optional forms
SELECT create_graph('opt_forms');
NOTICE: graph "opt_forms" has been created
@@ -5215,6 +5298,16 @@ NOTICE: graph "VLE" has been dropped
(1 row)
+SELECT * FROM drop_graph('case_statement', true);
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table case_statement._ag_label_vertex
+drop cascades to table case_statement._ag_label_edge
+NOTICE: graph "case_statement" has been dropped
+ drop_graph
+------------
+
+(1 row)
+
SELECT * FROM drop_graph('opt_forms', true);
NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to table opt_forms._ag_label_vertex
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index 41cee8e..b46de52 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -2113,6 +2113,42 @@ SELECT * FROM cypher('order_by', $$
ORDER BY u.i DESC
$$) AS (i agtype);
+--CASE
+SELECT create_graph('case_statement');
+SELECT * FROM cypher('case_statement', $$CREATE ({i: 1, j: null})$$) AS
(result agtype);
+SELECT * FROM cypher('case_statement', $$CREATE ({i: 'a', j: 'b'})$$) AS
(result agtype);
+SELECT * FROM cypher('case_statement', $$CREATE ({i: 0, j: 1})$$) AS (result
agtype);
+SELECT * FROM cypher('case_statement', $$CREATE ({i: true, j: false})$$) AS
(result agtype);
+SELECT * FROM cypher('case_statement', $$CREATE ({i: [], j: [0,1,2]})$$) AS
(result agtype);
+SELECT * FROM cypher('case_statement', $$CREATE ({i: {}, j: {i:1}})$$) AS
(result agtype);
+
+--CASE WHEN condition THEN result END
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n.i, n.j, CASE
+ WHEN null THEN 'should not return me'
+ WHEN n.i = 1 THEN 'i is 1'
+ WHEN n.j = 'b' THEN 'j is b'
+ WHEN n.i = 0 AND n.j = 1 THEN '0 AND 1'
+ WHEN n.i = true OR n.j = true THEN 'i or j true'
+ ELSE 'default'
+ END
+$$ ) AS (i agtype, j agtype, case_statement agtype);
+
+--CASE expression WHEN value THEN result END
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n.j, CASE n.j
+ WHEN null THEN 'should not return me'
+ WHEN 'b' THEN 'b'
+ WHEN 1 THEN 1
+ WHEN false THEN false
+ WHEN [0,1,2] THEN [0,1,2]
+ WHEN {i:1} THEN {i:1}
+ ELSE 'not a or b'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+
-- RETURN * and (u)--(v) optional forms
SELECT create_graph('opt_forms');
SELECT * FROM cypher('opt_forms', $$CREATE
({i:1})-[:KNOWS]->({i:2})<-[:KNOWS]-({i:3})$$)AS (result agtype);
@@ -2145,6 +2181,7 @@ SELECT * FROM cypher('VLE', $$MATCH (u)-[*..5]-(v) RETURN
u, v$$) AS (u agtype,
-- Cleanup
--
SELECT * FROM drop_graph('VLE', true);
+SELECT * FROM drop_graph('case_statement', true);
SELECT * FROM drop_graph('opt_forms', true);
SELECT * FROM drop_graph('type_coercion', true);
SELECT * FROM drop_graph('order_by', true);
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index cd43874..ea4d888 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -28,6 +28,7 @@
#include "nodes/value.h"
#include "optimizer/tlist.h"
#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/cypher_clause.h"
@@ -78,6 +79,8 @@ static Node *transform_cypher_string_match(cypher_parsestate
*cpstate,
cypher_string_match *csm_node);
static Node *transform_cypher_typecast(cypher_parsestate *cpstate,
cypher_typecast *ctypecast);
+static Node *transform_CaseExpr(cypher_parsestate *cpstate,
+ CaseExpr *cexpr);
static Node *transform_CoalesceExpr(cypher_parsestate *cpstate,
CoalesceExpr *cexpr);
static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink);
@@ -145,6 +148,10 @@ static Node
*transform_cypher_expr_recurse(cypher_parsestate *cpstate,
return expr;
}
+ case T_CaseExpr:
+ return transform_CaseExpr(cpstate, (CaseExpr *) expr);
+ case T_CaseTestExpr:
+ return expr;
case T_CoalesceExpr:
return transform_CoalesceExpr(cpstate, (CoalesceExpr *) expr);
case T_ExtensibleNode:
@@ -840,13 +847,141 @@ static Node *transform_CoalesceExpr(cypher_parsestate
*cpstate, CoalesceExpr
return (Node *) newcexpr;
}
+/*
+ * Code borrowed from PG's transformCaseExpr and updated for AGE
+ */
+static Node *transform_CaseExpr(cypher_parsestate *cpstate,CaseExpr
+ *cexpr)
+{
+ ParseState *pstate = &cpstate->pstate;
+ CaseExpr *newcexpr = makeNode(CaseExpr);
+ Node *last_srf = pstate->p_last_srf;
+ Node *arg;
+ CaseTestExpr *placeholder;
+ List *newargs;
+ List *resultexprs;
+ ListCell *l;
+ Node *defresult;
+ Oid ptype;
+
+ /* transform the test expression, if any */
+ arg = transform_cypher_expr_recurse(cpstate, (Node *) cexpr->arg);
+
+ /* generate placeholder for test expression */
+ if (arg)
+ {
+ if (exprType(arg) == UNKNOWNOID)
+ arg = coerce_to_common_type(pstate, arg, TEXTOID, "CASE");
+
+ assign_expr_collations(pstate, arg);
+
+ placeholder = makeNode(CaseTestExpr);
+ placeholder->typeId = exprType(arg);
+ placeholder->typeMod = exprTypmod(arg);
+ placeholder->collation = exprCollation(arg);
+ }
+ else
+ {
+ placeholder = NULL;
+ }
+
+ newcexpr->arg = (Expr *) arg;
+
+ /* transform the list of arguments */
+ newargs = NIL;
+ resultexprs = NIL;
+ foreach(l, cexpr->args)
+ {
+ CaseWhen *w = lfirst_node(CaseWhen, l);
+ CaseWhen *neww = makeNode(CaseWhen);
+ Node *warg;
+
+ warg = (Node *) w->expr;
+ if (placeholder)
+ {
+ /* shorthand form was specified, so expand... */
+ warg = (Node *) makeSimpleA_Expr(AEXPR_OP, "=",
+ (Node *) placeholder,
+ warg,
+ w->location);
+ }
+ neww->expr = (Expr *) transform_cypher_expr_recurse(cpstate, warg);
+
+ neww->expr = (Expr *) coerce_to_boolean(pstate,
+ (Node *) neww->expr,
+ "CASE/WHEN");
+
+ warg = (Node *) w->result;
+ neww->result = (Expr *) transform_cypher_expr_recurse(cpstate, warg);
+ neww->location = w->location;
+
+ newargs = lappend(newargs, neww);
+ resultexprs = lappend(resultexprs, neww->result);
+ }
+
+ newcexpr->args = newargs;
+
+ /* transform the default clause */
+ defresult = (Node *) cexpr->defresult;
+ if (defresult == NULL)
+ {
+ A_Const *n = makeNode(A_Const);
+
+ n->val.type = T_Null;
+ n->location = -1;
+ defresult = (Node *) n;
+ }
+ newcexpr->defresult = (Expr *) transform_cypher_expr_recurse(cpstate,
defresult);
+
+ resultexprs = lcons(newcexpr->defresult, resultexprs);
+
+ ptype = select_common_type(pstate, resultexprs, "CASE", NULL);
+ Assert(OidIsValid(ptype));
+ newcexpr->casetype = ptype;
+ /* casecollid will be set by parse_collate.c */
+
+ /* Convert default result clause, if necessary */
+ newcexpr->defresult = (Expr *)
+ coerce_to_common_type(pstate,
+ (Node *) newcexpr->defresult,
+ ptype,
+ "CASE/ELSE");
+
+ /* Convert when-clause results, if necessary */
+ foreach(l, newcexpr->args)
+ {
+ CaseWhen *w = (CaseWhen *) lfirst(l);
+
+ w->result = (Expr *)
+ coerce_to_common_type(pstate,
+ (Node *) w->result,
+ ptype,
+ "CASE/WHEN");
+ }
+
+ /* if any subexpression contained a SRF, complain */
+ if (pstate->p_last_srf != last_srf)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is name of a SQL construct, eg GROUP BY */
+ errmsg("set-returning functions are not allowed in %s",
+ "CASE"),
+ errhint("You might be able to move the set-returning function
into a LATERAL FROM item."),
+ parser_errposition(pstate,
+ exprLocation(pstate->p_last_srf))));
+
+ newcexpr->location = cexpr->location;
+
+ return (Node *) newcexpr;
+}
+
/* from PG's transformSubLink but reduced and hooked into our parser */
static Node *transform_SubLink(cypher_parsestate *cpstate, SubLink *sublink)
{
Node *result = (Node*)sublink;
Query *qtree;
ParseState *pstate = (ParseState*)cpstate;
-
+ const char *err = NULL;
/*
* Check to see if the sublink is in an invalid place within the query. We
* allow sublinks everywhere in SELECT/INSERT/UPDATE/DELETE, but generally
@@ -859,7 +994,9 @@ static Node *transform_SubLink(cypher_parsestate *cpstate,
SubLink *sublink)
break;
case EXPR_KIND_OTHER:
/* Accept sublink here; caller must throw error if wanted */
+
break;
+ case EXPR_KIND_SELECT_TARGET:
case EXPR_KIND_FROM_SUBSELECT:
case EXPR_KIND_WHERE:
/* okay */
@@ -869,6 +1006,11 @@ static Node *transform_SubLink(cypher_parsestate
*cpstate, SubLink *sublink)
errmsg_internal("unsupported SubLink"),
parser_errposition(pstate, sublink->location)));
}
+ if (err)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg_internal("%s", err),
+ parser_errposition(pstate, sublink->location)));
pstate->p_hasSubLinks = true;
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 430da84..b8e7878 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -79,9 +79,9 @@
/* keywords in alphabetical order */
%token <keyword> ANALYZE AND AS ASC ASCENDING
BY
- COALESCE CONTAINS CREATE
+ CASE COALESCE CONTAINS CREATE
DELETE DESC DESCENDING DETACH DISTINCT
- ENDS EXISTS EXPLAIN
+ ELSE END_P ENDS EXISTS EXPLAIN
FALSE_P
IN IS
LIMIT
@@ -90,9 +90,9 @@
OR ORDER
REMOVE RETURN
SET SKIP STARTS
- TRUE_P
+ THEN TRUE_P
VERBOSE
- WHERE WITH
+ WHEN WHERE WITH
/* query */
%type <list> single_query query_part_init query_part_last
@@ -131,6 +131,10 @@
/* expression */
%type <node> expr expr_opt expr_atom expr_literal map list
+
+%type <node> expr_case expr_case_when expr_case_default
+%type <list> expr_case_when_list
+
%type <node> expr_var expr_func expr_func_norm expr_func_subexpr
%type <list> expr_list expr_list_opt map_keyval_list_opt map_keyval_list
%type <node> property_value
@@ -1347,6 +1351,7 @@ expr_atom:
{
$$ = $2;
}
+ | expr_case
| expr_var
| expr_func
;
@@ -1423,6 +1428,67 @@ list:
}
;
+expr_case:
+ CASE expr expr_case_when_list expr_case_default END_P
+ {
+ CaseExpr *n;
+
+ n = makeNode(CaseExpr);
+ n->casetype = InvalidOid;
+ n->arg = (Expr *) $2;
+ n->args = $3;
+ n->defresult = (Expr *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | CASE expr_case_when_list expr_case_default END_P
+ {
+ CaseExpr *n;
+
+ n = makeNode(CaseExpr);
+ n->casetype = InvalidOid;
+ n->args = $2;
+ n->defresult = (Expr *) $3;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+expr_case_when_list:
+ expr_case_when
+ {
+ $$ = list_make1($1);
+ }
+ | expr_case_when_list expr_case_when
+ {
+ $$ = lappend($1, $2);
+ }
+ ;
+
+expr_case_when:
+ WHEN expr THEN expr
+ {
+ CaseWhen *n;
+
+ n = makeNode(CaseWhen);
+ n->expr = (Expr *) $2;
+ n->result = (Expr *) $4;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ ;
+
+expr_case_default:
+ ELSE expr
+ {
+ $$ = $2;
+ }
+ | /* EMPTY */
+ {
+ $$ = NULL;
+ }
+ ;
+
expr_var:
var_name
{
@@ -1505,6 +1571,7 @@ safe_keywords:
| ASC { $$ = pnstrdup($1, 3); }
| ASCENDING { $$ = pnstrdup($1, 9); }
| BY { $$ = pnstrdup($1, 2); }
+ | CASE { $$ = pnstrdup($1, 4); }
| COALESCE { $$ = pnstrdup($1, 8); }
| CONTAINS { $$ = pnstrdup($1, 8); }
| CREATE { $$ = pnstrdup($1, 6); }
@@ -1513,6 +1580,7 @@ safe_keywords:
| DESCENDING { $$ = pnstrdup($1, 10); }
| DETACH { $$ = pnstrdup($1, 6); }
| DISTINCT { $$ = pnstrdup($1, 8); }
+ | ELSE { $$ = pnstrdup($1, 4); }
| ENDS { $$ = pnstrdup($1, 4); }
| EXISTS { $$ = pnstrdup($1, 6); }
| IN { $$ = pnstrdup($1, 2); }
@@ -1527,14 +1595,17 @@ safe_keywords:
| SET { $$ = pnstrdup($1, 3); }
| SKIP { $$ = pnstrdup($1, 4); }
| STARTS { $$ = pnstrdup($1, 6); }
+ | THEN { $$ = pnstrdup($1, 4); }
+ | WHEN { $$ = pnstrdup($1, 4); }
| WHERE { $$ = pnstrdup($1, 5); }
| WITH { $$ = pnstrdup($1, 4); }
;
conflicted_keywords:
- FALSE_P { $$ = pnstrdup($1, 7); }
- | NULL_P { $$ = pnstrdup($1, 6); }
- | TRUE_P { $$ = pnstrdup($1, 6); }
+ END_P { $$ = pnstrdup($1, 5); }
+ | FALSE_P { $$ = pnstrdup($1, 7); }
+ | NULL_P { $$ = pnstrdup($1, 6); }
+ | TRUE_P { $$ = pnstrdup($1, 6); }
;
%%
diff --git a/src/backend/parser/cypher_keywords.c
b/src/backend/parser/cypher_keywords.c
index 0ff6129..64c6711 100644
--- a/src/backend/parser/cypher_keywords.c
+++ b/src/backend/parser/cypher_keywords.c
@@ -40,6 +40,7 @@ const ScanKeyword cypher_keywords[] = {
{"asc", ASC, RESERVED_KEYWORD},
{"ascending", ASCENDING, RESERVED_KEYWORD},
{"by", BY, RESERVED_KEYWORD},
+ {"case", CASE, RESERVED_KEYWORD},
{"coalesce", COALESCE, RESERVED_KEYWORD},
{"contains", CONTAINS, RESERVED_KEYWORD},
{"create", CREATE, RESERVED_KEYWORD},
@@ -48,6 +49,8 @@ const ScanKeyword cypher_keywords[] = {
{"descending", DESCENDING, RESERVED_KEYWORD},
{"detach", DETACH, RESERVED_KEYWORD},
{"distinct", DISTINCT, RESERVED_KEYWORD},
+ {"else", ELSE, RESERVED_KEYWORD},
+ {"end", END_P, RESERVED_KEYWORD},
{"ends", ENDS, RESERVED_KEYWORD},
{"exists", EXISTS, RESERVED_KEYWORD},
{"explain", EXPLAIN, RESERVED_KEYWORD},
@@ -65,8 +68,10 @@ const ScanKeyword cypher_keywords[] = {
{"set", SET, RESERVED_KEYWORD},
{"skip", SKIP, RESERVED_KEYWORD},
{"starts", STARTS, RESERVED_KEYWORD},
+ {"then", THEN, RESERVED_KEYWORD},
{"true", TRUE_P, RESERVED_KEYWORD},
{"verbose", VERBOSE, RESERVED_KEYWORD},
+ {"when", WHEN, RESERVED_KEYWORD},
{"where", WHERE, RESERVED_KEYWORD},
{"with", WITH, RESERVED_KEYWORD}
};