This is an automated email from the ASF dual-hosted git repository.
rafsun42 pushed a commit to branch PG13
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/PG13 by this push:
new 40c3b529 Modify COUNT() to output agtype (#1311) (#1336)
40c3b529 is described below
commit 40c3b529454560c97a60aa940bbb7df98232be04
Author: Dehowe Feng <[email protected]>
AuthorDate: Tue Oct 31 16:38:50 2023 -0700
Modify COUNT() to output agtype (#1311) (#1336)
Modified the make_function_expr logic to wrap the PG COUNT()
function with a cast to agtype. This enables COUNT() to be used as
a subquery in CASE.
Also added logic for casting for future PG function
additions.
This modification passes all regression tests. Also added
regression tests for COUNT in CASE statements.
---
regress/expected/expr.out | 109 ++++++++++++++++++++++++
regress/sql/expr.sql | 56 ++++++++++++
src/backend/parser/cypher_gram.y | 179 +++++++++++++++++++++++++++++++++++++--
3 files changed, 335 insertions(+), 9 deletions(-)
diff --git a/regress/expected/expr.out b/regress/expected/expr.out
index 373c243e..42fcd3dd 100644
--- a/regress/expected/expr.out
+++ b/regress/expected/expr.out
@@ -6325,6 +6325,115 @@ $$ ) AS (case_statement agtype);
{"id": 844424930131970, "label": "connected_to", "end_id": 281474976710660,
"start_id": 281474976710659, "properties": {"k": 1, "id": 2}}::edge
(2 rows)
+--CASE with count()
+--count(*)
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(*)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+ j
| case_statement
+------------------------------------------------------------------------------------------------+----------------
+ {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id":
2}}::vertex | "not count"
+ {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2],
"id": 5}}::vertex | "not count"
+ {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex
| "not count"
+ {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id":
3}}::vertex | 1
+ {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false,
"id": 4}}::vertex | "not count"
+ {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1},
"id": 6}}::vertex | "not count"
+(6 rows)
+
+--concatenated
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n) MATCH (m)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(*)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+ j
| case_statement
+------------------------------------------------------------------------------------------------+----------------
+ {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id":
2}}::vertex | "not count"
+ {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2],
"id": 5}}::vertex | "not count"
+ {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex
| "not count"
+ {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id":
3}}::vertex | 6
+ {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false,
"id": 4}}::vertex | "not count"
+ {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1},
"id": 6}}::vertex | "not count"
+(6 rows)
+
+--count(n)
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(n)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+ j
| case_statement
+------------------------------------------------------------------------------------------------+----------------
+ {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id":
2}}::vertex | "not count"
+ {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2],
"id": 5}}::vertex | "not count"
+ {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex
| "not count"
+ {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id":
3}}::vertex | 1
+ {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false,
"id": 4}}::vertex | "not count"
+ {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1},
"id": 6}}::vertex | "not count"
+(6 rows)
+
+--concatenated
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n) MATCH (m)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(n)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+ j
| case_statement
+------------------------------------------------------------------------------------------------+----------------
+ {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id":
2}}::vertex | "not count"
+ {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2],
"id": 5}}::vertex | "not count"
+ {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex
| "not count"
+ {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id":
3}}::vertex | 6
+ {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false,
"id": 4}}::vertex | "not count"
+ {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1},
"id": 6}}::vertex | "not count"
+(6 rows)
+
+--count(1)
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(1)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+ j
| case_statement
+------------------------------------------------------------------------------------------------+----------------
+ {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id":
2}}::vertex | "not count"
+ {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2],
"id": 5}}::vertex | "not count"
+ {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex
| "not count"
+ {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id":
3}}::vertex | 1
+ {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false,
"id": 4}}::vertex | "not count"
+ {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1},
"id": 6}}::vertex | "not count"
+(6 rows)
+
+--concatenated
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n) MATCH (m)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(1)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+ j
| case_statement
+------------------------------------------------------------------------------------------------+----------------
+ {"id": 281474976710658, "label": "", "properties": {"i": "a", "j": "b", "id":
2}}::vertex | "not count"
+ {"id": 281474976710661, "label": "", "properties": {"i": [], "j": [0, 1, 2],
"id": 5}}::vertex | "not count"
+ {"id": 281474976710657, "label": "", "properties": {"i": 1, "id": 1}}::vertex
| "not count"
+ {"id": 281474976710659, "label": "", "properties": {"i": 0, "j": 1, "id":
3}}::vertex | 6
+ {"id": 281474976710660, "label": "", "properties": {"i": true, "j": false,
"id": 4}}::vertex | "not count"
+ {"id": 281474976710662, "label": "", "properties": {"i": {}, "j": {"i": 1},
"id": 6}}::vertex | "not count"
+(6 rows)
+
-- RETURN * and (u)--(v) optional forms
SELECT create_graph('opt_forms');
NOTICE: graph "opt_forms" has been created
diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql
index 7bba60fe..436a02eb 100644
--- a/regress/sql/expr.sql
+++ b/regress/sql/expr.sql
@@ -2649,6 +2649,62 @@ SELECT * FROM cypher('case_statement', $$
END
$$ ) AS (case_statement agtype);
+--CASE with count()
+
+--count(*)
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(*)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+
+--concatenated
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n) MATCH (m)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(*)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+
+--count(n)
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(n)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+
+--concatenated
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n) MATCH (m)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(n)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+
+--count(1)
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(1)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+
+--concatenated
+SELECT * FROM cypher('case_statement', $$
+ MATCH (n) MATCH (m)
+ RETURN n, CASE n.j
+ WHEN 1 THEN count(1)
+ ELSE 'not count'
+ END
+$$ ) AS (j agtype, case_statement agtype);
+
-- RETURN * and (u)--(v) optional forms
SELECT create_graph('opt_forms');
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index e3f4013f..1ec932e7 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -225,6 +225,9 @@ static Node *make_typecast_expr(Node *expr, char *typecast,
int location);
// functions
static Node *make_function_expr(List *func_name, List *exprs, int location);
+static Node *make_star_function_expr(List *func_name, List *exprs, int
location);
+static Node *make_distinct_function_expr(List *func_name, List *exprs, int
location);
+static FuncCall *wrap_pg_funccall_to_agtype(Node* fnode, char *type, int
location);
// setops
static Node *make_set_op(SetOperation op, bool all_or_distinct, List *larg,
@@ -1685,19 +1688,16 @@ expr_func_norm:
* and there are no other aggregates in SQL that accept
* '*' as parameter.
*
- * The FuncCall node is also marked agg_star = true,
+ * The FuncCall node is marked agg_star = true by
make_star_function_expr,
* so that later processing can detect what the argument
* really was.
*/
- FuncCall *n = (FuncCall *)make_function_expr($1, NIL, @1);
- n->agg_star = true;
- $$ = (Node *)n;
+ FuncCall *n = (FuncCall *)make_star_function_expr($1, NIL, @1);
+ $$ = (Node *)n;
}
| func_name '(' DISTINCT expr_list ')'
{
- FuncCall *n = (FuncCall *)make_function_expr($1, $4, @1);
- n->agg_order = NIL;
- n->agg_distinct = true;
+ FuncCall *n = (FuncCall *)make_distinct_function_expr($1, $4, @1);
$$ = (Node *)n;
}
;
@@ -2259,25 +2259,186 @@ static Node *make_function_expr(List *func_name, List
*exprs, int location)
* could be many.
*/
if (pg_strcasecmp(name, "count") == 0)
+ {
+ funcname = SystemFuncName("count");
+
+ /* build the function call */
+ fnode = makeFuncCall(funcname, exprs, location);
+
+ /* build the cast to wrap the function call to return agtype. */
+ fnode = wrap_pg_funccall_to_agtype((Node *)fnode, "integer",
location);
+
+ return (Node *)fnode;
+ }
+ else
+ {
+ /*
+ * We don't qualify AGE functions here. This is done in the
+ * transform layer and allows us to know which functions are ours.
+ */
+ funcname = func_name;
+
+ /* build the function call */
+ fnode = makeFuncCall(funcname, exprs, location);
+ }
+ }
+ /* all other functions are passed as is */
+ else
+ {
+ fnode = makeFuncCall(func_name, exprs, location);
+ }
+
+ /* return the node */
+ return (Node *)fnode;
+}
+
+/*
+ * function to make a function that has received a star-argument
+ */
+static Node *make_star_function_expr(List *func_name, List *exprs, int
location)
+{
+ FuncCall *fnode;
+
+ /* AGE function names are unqualified. So, their list size = 1 */
+ if (list_length(func_name) == 1)
+ {
+ List *funcname;
+ char *name;
+
+ /* get the name of the function */
+ name = ((Value*)linitial(func_name))->val.str;
+
+ /*
+ * Check for openCypher functions that are directly mapped to PG
+ * functions. We may want to find a better way to do this, as there
+ * could be many.
+ */
+ if (pg_strcasecmp(name, "count") == 0)
+ {
+ funcname = SystemFuncName("count");
+
+ /* build the function call */
+ fnode = makeFuncCall(funcname, exprs, location);
+ fnode->agg_star = true;
+
+ /* build the cast to wrap the function call to return agtype. */
+ fnode = wrap_pg_funccall_to_agtype((Node *)fnode, "integer",
location);
+
+ return (Node *)fnode;
+ }
+ else
+ {
+ /*
+ * We don't qualify AGE functions here. This is done in the
+ * transform layer and allows us to know which functions are ours.
+ */
+ funcname = func_name;
+
+ /* build the function call */
+ fnode = makeFuncCall(funcname, exprs, location);
+ }
+ }
+ /* all other functions are passed as is */
+ else
+ {
+ fnode = makeFuncCall(func_name, exprs, location);
+ }
+
+ /* return the node */
+ fnode->agg_star = true;
+ return (Node *)fnode;
+}
+
+/*
+ * function to make a function that has received a distinct keyword
+ */
+static Node *make_distinct_function_expr(List *func_name, List *exprs, int
location)
+{
+ FuncCall *fnode;
+
+ /* AGE function names are unqualified. So, their list size = 1 */
+ if (list_length(func_name) == 1)
+ {
+ List *funcname;
+ char *name;
+
+ /* get the name of the function */
+ name = ((Value*)linitial(func_name))->val.str;
+
+ /*
+ * Check for openCypher functions that are directly mapped to PG
+ * functions. We may want to find a better way to do this, as there
+ * could be many.
+ */
+ if (pg_strcasecmp(name, "count") == 0)
+ {
funcname = SystemFuncName("count");
+
+ /* build the function call */
+ fnode = makeFuncCall(funcname, exprs, location);
+ fnode->agg_order = NIL;
+ fnode->agg_distinct = true;
+
+ /* build the cast to wrap the function call to return agtype. */
+ fnode = wrap_pg_funccall_to_agtype((Node *)fnode, "integer",
location);
+ return (Node *)fnode;
+ }
else
+ {
/*
* We don't qualify AGE functions here. This is done in the
* transform layer and allows us to know which functions are ours.
*/
funcname = func_name;
- /* build the function call */
- fnode = makeFuncCall(funcname, exprs, location);
+ /* build the function call */
+ fnode = makeFuncCall(funcname, exprs, location);
+ }
}
/* all other functions are passed as is */
else
+ {
fnode = makeFuncCall(func_name, exprs, location);
+ }
/* return the node */
+ fnode->agg_order = NIL;
+ fnode->agg_distinct = true;
return (Node *)fnode;
}
+/*
+ * helper function to wrap pg_function in the appropiate typecast function to
+ * interface with AGE components
+ */
+static FuncCall *wrap_pg_funccall_to_agtype(Node * fnode, char *type, int
location)
+{
+ List *funcname = list_make1(makeString("ag_catalog"));
+
+ if (pg_strcasecmp(type, "float") == 0)
+ {
+ funcname = lappend(funcname, makeString("float8_to_agtype"));
+ }
+ else if (pg_strcasecmp(type, "int") == 0 ||
+ pg_strcasecmp(type, "integer") == 0)
+ {
+ funcname = lappend(funcname, makeString("int8_to_agtype"));
+ }
+ else if (pg_strcasecmp(type, "bool") == 0 ||
+ pg_strcasecmp(type, "boolean") == 0)
+ {
+ funcname = lappend(funcname, makeString("bool_to_agtype"));
+ }
+ else
+ {
+ ereport(ERROR,
+ (errmsg_internal("type \'%s\' not supported by AGE functions",
+ type)));
+ }
+
+ return makeFuncCall(funcname, list_make1(fnode), location);
+}
+
/* function to create a unique name given a prefix */
static char *create_unique_name(char *prefix_name)
{