This is an automated email from the ASF dual-hosted git repository.
dehowef pushed a commit to branch PG15
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/PG15 by this push:
new 74723a37 Implement map projection (#1710) (#1797)
74723a37 is described below
commit 74723a37cd9f757e4ebf91553d5382cab3a2f4e3
Author: Rafsun Masud <[email protected]>
AuthorDate: Thu Apr 25 16:54:06 2024 -0700
Implement map projection (#1710) (#1797)
Adds support for openCypher Map Projection specification.
---
Makefile | 3 +-
regress/expected/map_projection.out | 171 ++++++++++++++++++++++++++++++++++++
regress/sql/map_projection.sql | 74 ++++++++++++++++
src/backend/nodes/ag_nodes.c | 3 +
src/backend/nodes/cypher_outfuncs.c | 9 ++
src/backend/parser/cypher_expr.c | 169 ++++++++++++++++++++++++++++++++++-
src/backend/parser/cypher_gram.y | 80 +++++++++++++++++
src/include/nodes/ag_nodes.h | 2 +
src/include/nodes/cypher_nodes.h | 34 +++++++
src/include/nodes/cypher_outfuncs.h | 1 +
10 files changed, 544 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index ef97774b..1224fc2a 100644
--- a/Makefile
+++ b/Makefile
@@ -102,7 +102,7 @@ REGRESS = scan \
cypher_union \
cypher_call \
cypher_merge \
- cypher_subquery \
+ cypher_subquery \
age_global_graph \
age_load \
index \
@@ -111,6 +111,7 @@ REGRESS = scan \
name_validation \
jsonb_operators \
list_comprehension \
+ map_projection \
drop
srcdir=`pwd`
diff --git a/regress/expected/map_projection.out
b/regress/expected/map_projection.out
new file mode 100644
index 00000000..dcb7f0e7
--- /dev/null
+++ b/regress/expected/map_projection.out
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+LOAD 'age';
+SET search_path TO ag_catalog;
+SELECT create_graph('map_proj');
+NOTICE: graph "map_proj" has been created
+ create_graph
+--------------
+
+(1 row)
+
+SELECT * FROM cypher('map_proj',
+$$
+ CREATE
+ (tom:Actor {name:'Tom Hanks', age:60}),
+ (bale:Actor {name:'Christian Bale', age:50}),
+ (tom)-[:ACTED_IN {during: 1990}]->(:Movie {title:'Forrest Gump'}),
+ (tom)-[:ACTED_IN {during: 1995}]->(:Movie {title:'Finch'}),
+ (tom)-[:ACTED_IN {during: 1999}]->(:Movie {title:'The Circle'}),
+ (bale)-[:ACTED_IN {during: 2002}]->(:Movie {title:'The Prestige'}),
+ (bale)-[:ACTED_IN {during: 2008}]->(:Movie {title:'The Dark Knight'})
+$$) as (a agtype);
+ a
+---
+(0 rows)
+
+-- all property selection
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .* } $$) as (a agtype);
+ a
+----------------------------
+ {"age": 50, "name": "Bob"}
+(1 row)
+
+-- property selector
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name } $$) as (a agtype);
+ a
+-----------------
+ {"name": "Bob"}
+(1 row)
+
+-- literal entry
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { name:'Tom' } $$) as (a agtype);
+ a
+-----------------
+ {"name": "Tom"}
+(1 row)
+
+-- variable selector
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map, 'Tom' as
name RETURN map { name } $$) as (a agtype);
+ a
+-----------------
+ {"name": "Tom"}
+(1 row)
+
+-- duplicate all property selector
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .*, .* } $$) as (a agtype);
+ a
+----------------------------
+ {"age": 50, "name": "Bob"}
+(1 row)
+
+-- name being selected twice
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name, .* } $$) as (a agtype);
+ a
+----------------------------
+ {"age": 50, "name": "Bob"}
+(1 row)
+
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name, .name } $$) as (a agtype);
+ a
+-----------------
+ {"name": "Bob"}
+(1 row)
+
+-- name being selected twice with different value
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { name:'Tom', .* } $$) as (a agtype);
+ a
+----------------------------
+ {"age": 50, "name": "Tom"}
+(1 row)
+
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map, 'Tom' as
name RETURN map { name, .* } $$) as (a agtype);
+ a
+----------------------------
+ {"age": 50, "name": "Tom"}
+(1 row)
+
+-- new entry added
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name, .age, height:180 } $$) as (a agtype);
+ a
+-------------------------------------------
+ {"age": 50, "name": "Bob", "height": 180}
+(1 row)
+
+-- NULL as a map
+SELECT * FROM cypher('map_proj', $$ WITH NULL AS map RETURN map { .name } $$)
as (a agtype);
+ a
+----
+ {}
+(1 row)
+
+-- vertex as a map
+SELECT * FROM cypher('map_proj', $$ MATCH (n:Actor) RETURN n { .name } $$) as
(a agtype);
+ a
+----------------------------
+ {"name": "Tom Hanks"}
+ {"name": "Christian Bale"}
+(2 rows)
+
+-- edge as a map
+SELECT * FROM cypher('map_proj', $$ MATCH ()-[e:ACTED_IN]->() RETURN e {
.during } $$) as (a agtype);
+ a
+------------------
+ {"during": 1990}
+ {"during": 1995}
+ {"during": 1999}
+ {"during": 2002}
+ {"during": 2008}
+(5 rows)
+
+-- syntax error
+SELECT * FROM cypher('map_proj', $$ WITH 12 AS map RETURN map { .name } $$) as
(a agtype);
+ERROR: properties() argument must be a vertex, an edge or null
+SELECT * FROM cypher('map_proj', $$ WITH [] AS map RETURN map { .name } $$) as
(a agtype);
+ERROR: properties() argument must resolve to an object
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob'} AS map RETURN map {
'name' } $$) as (a agtype);
+ERROR: syntax error at or near "'name'"
+LINE 1: ...p_proj', $$ WITH {name:'Bob'} AS map RETURN map { 'name' } $...
+ ^
+-- advanced
+SELECT * FROM cypher('map_proj',
+$$
+ MATCH (a:Actor)-[:ACTED_IN]->(m:Movie)
+ WITH a, collect(m { .title }) AS movies
+ RETURN collect(a { .name, movies })
+$$) as (a agtype);
+
a
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ [{"name": "Christian Bale", "movies": [{"title": "The Prestige"}, {"title":
"The Dark Knight"}]}, {"name": "Tom Hanks", "movies": [{"title": "Forrest
Gump"}, {"title": "Finch"}, {"title": "The Circle"}]}]
+(1 row)
+
+-- drop
+SELECT drop_graph('map_proj', true);
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table map_proj._ag_label_vertex
+drop cascades to table map_proj._ag_label_edge
+drop cascades to table map_proj."Actor"
+drop cascades to table map_proj."ACTED_IN"
+drop cascades to table map_proj."Movie"
+NOTICE: graph "map_proj" has been dropped
+ drop_graph
+------------
+
+(1 row)
+
diff --git a/regress/sql/map_projection.sql b/regress/sql/map_projection.sql
new file mode 100644
index 00000000..72fc2fb2
--- /dev/null
+++ b/regress/sql/map_projection.sql
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+LOAD 'age';
+SET search_path TO ag_catalog;
+
+SELECT create_graph('map_proj');
+
+SELECT * FROM cypher('map_proj',
+$$
+ CREATE
+ (tom:Actor {name:'Tom Hanks', age:60}),
+ (bale:Actor {name:'Christian Bale', age:50}),
+ (tom)-[:ACTED_IN {during: 1990}]->(:Movie {title:'Forrest Gump'}),
+ (tom)-[:ACTED_IN {during: 1995}]->(:Movie {title:'Finch'}),
+ (tom)-[:ACTED_IN {during: 1999}]->(:Movie {title:'The Circle'}),
+ (bale)-[:ACTED_IN {during: 2002}]->(:Movie {title:'The Prestige'}),
+ (bale)-[:ACTED_IN {during: 2008}]->(:Movie {title:'The Dark Knight'})
+$$) as (a agtype);
+
+-- all property selection
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .* } $$) as (a agtype);
+-- property selector
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name } $$) as (a agtype);
+-- literal entry
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { name:'Tom' } $$) as (a agtype);
+-- variable selector
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map, 'Tom' as
name RETURN map { name } $$) as (a agtype);
+-- duplicate all property selector
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .*, .* } $$) as (a agtype);
+-- name being selected twice
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name, .* } $$) as (a agtype);
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name, .name } $$) as (a agtype);
+-- name being selected twice with different value
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { name:'Tom', .* } $$) as (a agtype);
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map, 'Tom' as
name RETURN map { name, .* } $$) as (a agtype);
+-- new entry added
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob', age:50} AS map RETURN
map { .name, .age, height:180 } $$) as (a agtype);
+-- NULL as a map
+SELECT * FROM cypher('map_proj', $$ WITH NULL AS map RETURN map { .name } $$)
as (a agtype);
+-- vertex as a map
+SELECT * FROM cypher('map_proj', $$ MATCH (n:Actor) RETURN n { .name } $$) as
(a agtype);
+-- edge as a map
+SELECT * FROM cypher('map_proj', $$ MATCH ()-[e:ACTED_IN]->() RETURN e {
.during } $$) as (a agtype);
+-- syntax error
+SELECT * FROM cypher('map_proj', $$ WITH 12 AS map RETURN map { .name } $$) as
(a agtype);
+SELECT * FROM cypher('map_proj', $$ WITH [] AS map RETURN map { .name } $$) as
(a agtype);
+SELECT * FROM cypher('map_proj', $$ WITH {name:'Bob'} AS map RETURN map {
'name' } $$) as (a agtype);
+-- advanced
+SELECT * FROM cypher('map_proj',
+$$
+ MATCH (a:Actor)-[:ACTED_IN]->(m:Movie)
+ WITH a, collect(m { .title }) AS movies
+ RETURN collect(a { .name, movies })
+$$) as (a agtype);
+
+-- drop
+SELECT drop_graph('map_proj', true);
diff --git a/src/backend/nodes/ag_nodes.c b/src/backend/nodes/ag_nodes.c
index a41cdd0b..858ca5a5 100644
--- a/src/backend/nodes/ag_nodes.c
+++ b/src/backend/nodes/ag_nodes.c
@@ -45,6 +45,8 @@ const char *node_names[] = {
"cypher_bool_const",
"cypher_param",
"cypher_map",
+ "cypher_map_projection",
+ "cypher_map_projection_element",
"cypher_list",
"cypher_comparison_aexpr",
"cypher_comparison_boolexpr",
@@ -111,6 +113,7 @@ const ExtensibleNodeMethods node_methods[] = {
DEFINE_NODE_METHODS(cypher_bool_const),
DEFINE_NODE_METHODS(cypher_param),
DEFINE_NODE_METHODS(cypher_map),
+ DEFINE_NODE_METHODS(cypher_map_projection),
DEFINE_NODE_METHODS(cypher_list),
DEFINE_NODE_METHODS(cypher_comparison_aexpr),
DEFINE_NODE_METHODS(cypher_comparison_boolexpr),
diff --git a/src/backend/nodes/cypher_outfuncs.c
b/src/backend/nodes/cypher_outfuncs.c
index 2904503f..5c175a30 100644
--- a/src/backend/nodes/cypher_outfuncs.c
+++ b/src/backend/nodes/cypher_outfuncs.c
@@ -251,6 +251,15 @@ void out_cypher_map(StringInfo str, const ExtensibleNode
*node)
WRITE_LOCATION_FIELD(location);
}
+void out_cypher_map_projection(StringInfo str, const ExtensibleNode *node)
+{
+ DEFINE_AG_NODE(cypher_map_projection);
+
+ WRITE_NODE_FIELD(map_var);
+ WRITE_NODE_FIELD(map_elements);
+ WRITE_LOCATION_FIELD(location);
+}
+
// serialization function for the cypher_list ExtensibleNode.
void out_cypher_list(StringInfo str, const ExtensibleNode *node)
{
diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c
index 40944028..195ecfda 100644
--- a/src/backend/parser/cypher_expr.c
+++ b/src/backend/parser/cypher_expr.c
@@ -73,6 +73,8 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate,
A_Expr *a);
static Node *transform_cypher_param(cypher_parsestate *cpstate,
cypher_param *cp);
static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm);
+static Node *transform_cypher_map_projection(cypher_parsestate *cpstate,
+ cypher_map_projection *cmp);
static Node *transform_cypher_list(cypher_parsestate *cpstate,
cypher_list *cl);
static Node *transform_cypher_string_match(cypher_parsestate *cpstate,
@@ -187,6 +189,11 @@ static Node
*transform_cypher_expr_recurse(cypher_parsestate *cpstate,
{
return transform_cypher_map(cpstate, (cypher_map *)expr);
}
+ if (is_ag_node(expr, cypher_map_projection))
+ {
+ return transform_cypher_map_projection(
+ cpstate, (cypher_map_projection *)expr);
+ }
if (is_ag_node(expr, cypher_list))
{
return transform_cypher_list(cpstate, (cypher_list *)expr);
@@ -220,7 +227,7 @@ static Node
*transform_cypher_expr_recurse(cypher_parsestate *cpstate,
ereport(ERROR,
(errmsg_internal("unrecognized ExtensibleNode: %s",
((ExtensibleNode *)expr)->extnodename)));
-
+
return NULL;
}
case T_FuncCall:
@@ -825,6 +832,166 @@ static Node *transform_cypher_param(cypher_parsestate
*cpstate,
return (Node *)func_expr;
}
+static Node *transform_cypher_map_projection(cypher_parsestate *cpstate,
+ cypher_map_projection *cmp)
+{
+ ParseState *pstate;
+ ListCell *lc;
+ List *keyvals;
+ Oid foid_agtype_build_map;
+ FuncExpr *fexpr_new_map;
+ bool has_all_prop_selector;
+ Node *transformed_map_var;
+ Oid foid_age_properties;
+ FuncExpr *fexpr_orig_map;
+
+ pstate = (ParseState *)cpstate;
+ keyvals = NIL;
+ has_all_prop_selector = false;
+ fexpr_new_map = NULL;
+
+ /*
+ * Builds the original map: `age_properties(cmp->map_var)`. Whether map_var
+ * is compatible (map, vertex or edge) is checked during the execution of
+ * age_properties().
+ */
+ transformed_map_var = transform_cypher_expr_recurse(cpstate,
+ (Node *)cmp->map_var);
+ foid_age_properties = get_ag_func_oid("age_properties", 1, AGTYPEOID);
+ fexpr_orig_map = makeFuncExpr(foid_age_properties, AGTYPEOID,
+ list_make1(transformed_map_var), InvalidOid,
+ InvalidOid, COERCE_EXPLICIT_CALL);
+ fexpr_orig_map->location = cmp->location;
+
+ /*
+ * Builds a new map. Each map projection element is transformed into a key
+ * value pair (except for the ALL_PROPERTIES_SELECTOR type).
+ */
+ foreach (lc, cmp->map_elements)
+ {
+ cypher_map_projection_element *elem;
+ Const *key;
+ Node *val;
+
+ elem = lfirst(lc);
+ key = NULL;
+ val = NULL;
+
+ if (elem->type == ALL_PROPERTIES_SELECTOR)
+ {
+ has_all_prop_selector = true;
+ continue;
+ }
+
+ /* Makes key and val based on elem->type */
+ switch (elem->type)
+ {
+ case PROPERTY_SELECTOR:
+ {
+ Oid foid_access_op;
+ FuncExpr *fexpr_access_op;
+ ArrayExpr *args_access_op;
+ Const *key_agtype;
+
+ /* Makes key from elem->key */
+ key = makeConst(TEXTOID, -1, InvalidOid, -1,
+ CStringGetTextDatum(elem->key), false, false);
+
+ /* Makes val from `age_properties(cmp->map_var).key` */
+ key_agtype = makeConst(AGTYPEOID, -1, InvalidOid, -1,
+ string_to_agtype(elem->key), false,
+ false);
+ foid_access_op = get_ag_func_oid("agtype_access_operator", 1,
+ AGTYPEARRAYOID);
+ args_access_op = make_agtype_array_expr(
+ list_make2(fexpr_orig_map, key_agtype));
+ fexpr_access_op = makeFuncExpr(foid_access_op, AGTYPEOID,
+ list_make1(args_access_op),
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr_access_op->funcvariadic = true;
+ fexpr_access_op->location = -1;
+ val = (Node *)fexpr_access_op;
+
+ break;
+ }
+ case LITERAL_ENTRY:
+ {
+ key = makeConst(TEXTOID, -1, InvalidOid, -1,
+ CStringGetTextDatum(elem->key), false, false);
+ val = transform_cypher_expr_recurse(cpstate, elem->value);
+ break;
+ }
+ case VARIABLE_SELECTOR:
+ {
+ char *key_str;
+ List *fields;
+
+ Assert(IsA(elem->value, ColumnRef));
+
+ /* Makes key from the ColumnRef's field */
+ fields = ((ColumnRef *)elem->value)->fields;
+ key_str = strVal(lfirst(list_head(fields)));
+ key = makeConst(TEXTOID, -1, InvalidOid, -1,
+ CStringGetTextDatum(key_str), false, false);
+
+ val = transform_cypher_expr_recurse(cpstate, elem->value);
+ break;
+ }
+ case ALL_PROPERTIES_SELECTOR:
+ {
+ /*
+ * Key value pairs of the original map are added later outside
+ * the loop. Control never reaches this block.
+ */
+ break;
+ }
+ default:
+ {
+ elog(ERROR, "unknown map projection element type");
+ }
+ }
+
+ Assert(key);
+ Assert(val);
+ keyvals = lappend(lappend(keyvals, key), val);
+ }
+
+ if (keyvals)
+ {
+ foid_agtype_build_map = get_ag_func_oid("agtype_build_map_nonull", 1,
+ ANYOID);
+ fexpr_new_map = makeFuncExpr(foid_agtype_build_map, AGTYPEOID, keyvals,
+ InvalidOid, InvalidOid,
+ COERCE_EXPLICIT_CALL);
+ fexpr_new_map->location = cmp->location;
+ }
+
+ /*
+ * In case .* is present, returns age_properties(cmp->map_var) + the new
+ * map. Else, returns the new map.
+ */
+ if (has_all_prop_selector)
+ {
+ if (!keyvals)
+ {
+ return (Node *)fexpr_orig_map;
+ }
+ else
+ {
+ return (Node *)make_op(pstate, list_make1(makeString("+")),
+ (Node *)fexpr_orig_map,
+ (Node *)fexpr_new_map,
+ pstate->p_last_srf, -1);
+ }
+ }
+ else
+ {
+ Assert(!has_all_prop_selector && fexpr_new_map);
+ return (Node *)fexpr_new_map;
+ }
+}
+
static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm)
{
ParseState *pstate = (ParseState *)cpstate;
diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y
index 932378ee..359c3a85 100644
--- a/src/backend/parser/cypher_gram.y
+++ b/src/backend/parser/cypher_gram.y
@@ -152,6 +152,9 @@
%type <node> expr_case expr_case_when expr_case_default
%type <list> expr_case_when_list
+%type <node> map_projection map_projection_elem
+%type <list> map_projection_elem_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
@@ -2006,6 +2009,7 @@ expr_literal:
$$ = make_null_const(@1);
}
| map
+ | map_projection
| list
;
@@ -2040,6 +2044,82 @@ map_keyval_list:
}
;
+map_projection:
+ expr_var '{' map_projection_elem_list '}'
+ {
+ cypher_map_projection *n;
+
+ n = make_ag_node(cypher_map_projection);
+ n->map_var = (ColumnRef *)$1;
+ n->map_elements = $3;
+ n->location = @1;
+
+ $$ = (Node *)n;
+ }
+ ;
+
+map_projection_elem_list:
+ map_projection_elem
+ {
+ $$ = list_make1($1);
+ }
+ | map_projection_elem_list ',' map_projection_elem
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+map_projection_elem:
+ '.' property_key_name
+ {
+ cypher_map_projection_element *n;
+
+ n = make_ag_node(cypher_map_projection_element);
+ n->type = PROPERTY_SELECTOR;
+ n->key = $2;
+ n->value = NULL;
+ n->location = @1;
+
+ $$ = (Node *)n;
+ }
+ | expr_var
+ {
+ cypher_map_projection_element *n;
+
+ n = make_ag_node(cypher_map_projection_element);
+ n->type = VARIABLE_SELECTOR;
+ n->key = NULL;
+ n->value = (Node *)$1;
+ n->location = @1;
+
+ $$ = (Node *)n;
+ }
+ | property_key_name ':' expr
+ {
+ cypher_map_projection_element *n;
+
+ n = make_ag_node(cypher_map_projection_element);
+ n->type = LITERAL_ENTRY;
+ n->key = $1;
+ n->value = (Node *)$3;
+ n->location = @1;
+
+ $$ = (Node *)n;
+ }
+ | '.' '*'
+ {
+ cypher_map_projection_element *n;
+
+ n = make_ag_node(cypher_map_projection_element);
+ n->type = ALL_PROPERTIES_SELECTOR;
+ n->key = NULL;
+ n->value = NULL;
+ n->location = @1;
+
+ $$ = (Node *)n;
+ }
+ ;
+
list:
'[' expr_list_opt ']'
{
diff --git a/src/include/nodes/ag_nodes.h b/src/include/nodes/ag_nodes.h
index 23d68393..84f54154 100644
--- a/src/include/nodes/ag_nodes.h
+++ b/src/include/nodes/ag_nodes.h
@@ -47,6 +47,8 @@ typedef enum ag_node_tag
cypher_bool_const_t,
cypher_param_t,
cypher_map_t,
+ cypher_map_projection_t,
+ cypher_map_projection_element_t,
cypher_list_t,
// comparison expression
cypher_comparison_aexpr_t,
diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h
index 1af2390f..b4c363f6 100644
--- a/src/include/nodes/cypher_nodes.h
+++ b/src/include/nodes/cypher_nodes.h
@@ -210,6 +210,40 @@ typedef struct cypher_map
bool keep_null; // if false, keyvals with null value are removed
} cypher_map;
+typedef struct cypher_map_projection
+{
+ ExtensibleNode extensible;
+ ColumnRef *map_var; /* must be a map, vertex or an edge */
+ List *map_elements; /* list of cypher_map_projection_element */
+ int location;
+} cypher_map_projection;
+
+typedef enum cypher_map_projection_element_type
+{
+ PROPERTY_SELECTOR = 0, /* map_var { .key } */
+ VARIABLE_SELECTOR, /* map_var { value } */
+ LITERAL_ENTRY, /* map_var { key: value } */
+ ALL_PROPERTIES_SELECTOR /* map_var { .* } */
+} cypher_map_projection_element_type;
+
+typedef struct cypher_map_projection_element
+{
+ ExtensibleNode extensible;
+ cypher_map_projection_element_type type;
+
+ /*
+ * key and/or value can be null depending on the type
+ *
+ * For PROPERTY_SELECTOR, value is null.
+ * For VARIABLE_SELECTOR, key is null, and value is a ColumnRef.
+ * For LITERAL_ENTRY, none is null (value is an Expr).
+ * For ALL_PROPERTIES_SELECTOR, both are null.
+ */
+ char *key;
+ Node *value;
+ int location;
+} cypher_map_projection_element;
+
typedef struct cypher_list
{
ExtensibleNode extensible;
diff --git a/src/include/nodes/cypher_outfuncs.h
b/src/include/nodes/cypher_outfuncs.h
index 3963be0d..fc4782d2 100644
--- a/src/include/nodes/cypher_outfuncs.h
+++ b/src/include/nodes/cypher_outfuncs.h
@@ -46,6 +46,7 @@ void out_cypher_relationship(StringInfo str, const
ExtensibleNode *node);
void out_cypher_bool_const(StringInfo str, const ExtensibleNode *node);
void out_cypher_param(StringInfo str, const ExtensibleNode *node);
void out_cypher_map(StringInfo str, const ExtensibleNode *node);
+void out_cypher_map_projection(StringInfo str, const ExtensibleNode *node);
void out_cypher_list(StringInfo str, const ExtensibleNode *node);
// comparison expression