diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 43e723378e9..dcfcba085af 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -49,6 +49,9 @@ #include "utils/datum.h" #include "utils/lsyscache.h" #include "utils/typcache.h" +#include "parser/parse_type.h" +#include "utils/fmgroids.h" +#include "utils/syscache.h" typedef struct LastAttnumInfo @@ -4163,8 +4166,16 @@ ExecInitCypherListComp(ExprEvalStep *scratch, CypherListCompExpr *listcompexpr, ExprEvalStep elem_step; ExprEvalStep loop_step; int end_stepno; + bool is_array_type; - Assert(exprType((Node *) listcompexpr->list) == JSONBOID); + Type targetType; + Form_pg_type typeForm; + + targetType = typeidType(exprType((const Node *) listcompexpr->list)); + typeForm = (Form_pg_type) GETSTRUCT(targetType); + + is_array_type = IsTrueArrayType(typeForm); + ReleaseSysCache(targetType); ExecInitExprRec(listcompexpr->list, state, scratch->resvalue, scratch->resnull); @@ -4186,8 +4197,24 @@ ExecInitCypherListComp(ExprEvalStep *scratch, CypherListCompExpr *listcompexpr, init_step.opcode = EEOP_CYPHERLISTCOMP_ITER_INIT; init_step.d.cypherlistcomp_iter.listvalue = scratch->resvalue; init_step.d.cypherlistcomp_iter.listnull = scratch->resnull; - init_step.d.cypherlistcomp_iter.listiter = - (JsonbIterator **) palloc(sizeof(JsonbIterator *)); + + init_step.d.cypherlistcomp_iter.is_null_list_or_array = + (bool *) palloc(sizeof(bool)); + *init_step.d.cypherlistcomp_iter.is_null_list_or_array = false; + + if (is_array_type) + { + init_step.d.cypherlistcomp_iter.jsonb_list_iterator = NULL; + init_step.d.cypherlistcomp_iter.array_iterator = + (CypherListCompArrayIterator *) palloc(sizeof(CypherListCompArrayIterator)); + } + else + { + init_step.d.cypherlistcomp_iter.jsonb_list_iterator = + (JsonbIterator **) palloc(sizeof(JsonbIterator *)); + init_step.d.cypherlistcomp_iter.array_iterator = NULL; + } + ExprEvalPushStep(state, &init_step); elem_resvalue = (Datum *) palloc(sizeof(Datum)); @@ -4196,8 +4223,13 @@ ExecInitCypherListComp(ExprEvalStep *scratch, CypherListCompExpr *listcompexpr, next_step.opcode = EEOP_CYPHERLISTCOMP_ITER_NEXT; next_step.resvalue = elem_resvalue; next_step.resnull = elem_resnull; - next_step.d.cypherlistcomp_iter.listiter = - init_step.d.cypherlistcomp_iter.listiter; + next_step.d.cypherlistcomp_iter.is_null_list_or_array = + init_step.d.cypherlistcomp_iter.is_null_list_or_array; + next_step.d.cypherlistcomp_iter.jsonb_list_iterator = + init_step.d.cypherlistcomp_iter.jsonb_list_iterator; + next_step.d.cypherlistcomp_iter.array_iterator = + init_step.d.cypherlistcomp_iter.array_iterator; + ExprEvalPushStep(state, &next_step); next_stepno = state->steps_len - 1; diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index a725064696b..5336c6b19b7 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -5277,43 +5277,101 @@ ExecEvalCypherListCompEnd(ExprState *state, ExprEvalStep *op) void ExecEvalCypherListCompIterInit(ExprState *state, ExprEvalStep *op) { - Jsonb *listjb; - JsonbIterator **ji; - JsonbValue jv; + if (op->d.cypherlistcomp_iter.jsonb_list_iterator != NULL) + { + Jsonb *listjb; + JsonbIterator **ji; + JsonbValue jv; - Assert(!*op->d.cypherlistcomp_iter.listnull); + Assert(!*op->d.cypherlistcomp_iter.listnull); - listjb = DatumGetJsonbP(*op->d.cypherlistcomp_iter.listvalue); - if (!JB_ROOT_IS_ARRAY(listjb) || JB_ROOT_IS_SCALAR(listjb)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("list is expected but %s", - JsonbToCString(NULL, &listjb->root, - VARSIZE(listjb))))); - - ji = op->d.cypherlistcomp_iter.listiter; - *ji = JsonbIteratorInit(&listjb->root); - JsonbIteratorNext(ji, &jv, false); + listjb = DatumGetJsonbP(*op->d.cypherlistcomp_iter.listvalue); + *op->d.cypherlistcomp_iter.is_null_list_or_array = !JB_ROOT_IS_ARRAY(listjb) || JB_ROOT_IS_SCALAR(listjb); + + ji = op->d.cypherlistcomp_iter.jsonb_list_iterator; + *ji = JsonbIteratorInit(&listjb->root); + JsonbIteratorNext(ji, &jv, false); + } + else + { + CypherListCompArrayIterator *cypher_array_iter = op->d.cypherlistcomp_iter.array_iterator; + array_iter *it = &cypher_array_iter->array_iter; + AnyArrayType *array = DatumGetAnyArrayP(*op->d.cypherlistcomp_iter.listvalue); + + cypher_array_iter->array_size = ArrayGetNItems(AARR_NDIM(array), + AARR_DIMS(array)); + cypher_array_iter->array_position = 0; + cypher_array_iter->array_typid = AARR_ELEMTYPE(array); + + get_typlenbyvalalign(cypher_array_iter->array_typid, + &cypher_array_iter->typlen, + &cypher_array_iter->typbyval, + &cypher_array_iter->typalign); + array_iter_setup(it, array); + } } void ExecEvalCypherListCompIterInitNext(ExprState *state, ExprEvalStep *op) { - JsonbIterator **ji; - JsonbValue jv; - JsonbIteratorToken jt; - - ji = op->d.cypherlistcomp_iter.listiter; - jt = JsonbIteratorNext(ji, &jv, true); - if (jt == WJB_ELEM) + if (op->d.cypherlistcomp_iter.jsonb_list_iterator != NULL) { - *op->resvalue = JsonbPGetDatum(JsonbValueToJsonb(&jv)); - *op->resnull = false; + JsonbIterator **ji; + JsonbValue jv; + JsonbIteratorToken jt; + + if (*op->d.cypherlistcomp_iter.is_null_list_or_array) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + return; + } + + ji = op->d.cypherlistcomp_iter.jsonb_list_iterator; + jt = JsonbIteratorNext(ji, &jv, true); + if (jt == WJB_ELEM) + { + *op->resvalue = JsonbPGetDatum(JsonbValueToJsonb(&jv)); + *op->resnull = false; + } + else + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + } } else { - *op->resvalue = (Datum) 0; - *op->resnull = true; + CypherListCompArrayIterator *array_iter = op->d.cypherlistcomp_iter.array_iterator; + Datum vertex_or_edge_datum; + + if (array_iter->array_position >= array_iter->array_size) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + return; + } + + vertex_or_edge_datum = array_iter_next(&array_iter->array_iter, + op->resnull, + array_iter->array_position++, + array_iter->typlen, + array_iter->typbyval, + array_iter->typalign); + + if (*op->resnull) + { + return; + } + + if (array_iter->array_typid == VERTEXOID) + { + *op->resvalue = getVertexPropDatum(vertex_or_edge_datum); + } + else + { + *op->resvalue = getEdgePropDatum(vertex_or_edge_datum); + } } } diff --git a/src/backend/parser/parse_cypher_expr.c b/src/backend/parser/parse_cypher_expr.c index 030c8289d36..ce159d4020d 100644 --- a/src/backend/parser/parse_cypher_expr.c +++ b/src/backend/parser/parse_cypher_expr.c @@ -722,9 +722,15 @@ transformCypherListComp(ParseState *pstate, CypherListComp *clc) list = transformCypherExprRecurse(pstate, (Node *) clc->list); type = exprType(list); - if (type != JSONBOID) - { - list = coerce_all_to_jsonb(pstate, list); + + switch (type) { + case JSONBOID: + case VERTEXARRAYOID: + case EDGEARRAYOID: + break; + default: + list = coerce_all_to_jsonb(pstate, list); + break; } save_varname = pstate->p_lc_varname; diff --git a/src/backend/utils/adt/cypher_empty_funcs.c b/src/backend/utils/adt/cypher_empty_funcs.c index 27c39a90ff4..5cf3d3be8c2 100644 --- a/src/backend/utils/adt/cypher_empty_funcs.c +++ b/src/backend/utils/adt/cypher_empty_funcs.c @@ -11,9 +11,36 @@ #include "utils/cypher_empty_funcs.h" #include "utils/fmgrprotos.h" +#include "utils/jsonb.h" Datum cypher_to_jsonb(PG_FUNCTION_ARGS) { return to_jsonb(fcinfo); } + +Datum +cypher_isempty_jsonb(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + + if (JB_ROOT_IS_SCALAR(jb)) + { + JsonbValue *sjv; + + sjv = getIthJsonbValueFromContainer(&jb->root, 0); + if (sjv->type == jbvString) + { + PG_RETURN_BOOL(sjv->val.string.len <= 0); + } + } + else if (JB_ROOT_IS_ARRAY(jb) || JB_ROOT_IS_OBJECT(jb)) + { + PG_RETURN_BOOL(JB_ROOT_COUNT(jb) <= 0); + } + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("isEmpty(): list or object or string is expected but %s", + JsonbToCString(NULL, &jb->root, VARSIZE(jb))))); +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4f2d69079e7..30e4ca3fac3 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12013,6 +12013,9 @@ { oid => '7247', descr => 'map input to jsonb (cypher)', proname => 'cypher_to_jsonb', prorettype => 'jsonb', proargtypes => 'anyelement', prosrc => 'cypher_to_jsonb' }, +{ oid => '7248', descr => 'is jsonb empty?', + proname => 'isempty', prorettype => 'bool', + proargtypes => 'jsonb', prosrc => 'cypher_isempty_jsonb' }, # agensgraph array functions { oid => '7300', descr => 'the first element in a array', diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 7c45c7709b6..92c760fe905 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -17,12 +17,14 @@ #include "executor/nodeAgg.h" #include "nodes/execnodes.h" #include "utils/jsonb.h" +#include "utils/arrayaccess.h" /* forward references to avoid circularity */ struct ExprEvalStep; struct SubscriptingRefState; struct ScalarArrayOpExprHashTable; struct CypherAccessPathElem; +struct CypherListCompArrayIterator; /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */ /* expression's interpreter has been initialized */ @@ -712,7 +714,9 @@ typedef struct ExprEvalStep { Datum *listvalue; bool *listnull; - JsonbIterator **listiter; + JsonbIterator **jsonb_list_iterator; + struct CypherListCompArrayIterator *array_iterator; + bool *is_null_list_or_array; } cypherlistcomp_iter; struct @@ -794,6 +798,17 @@ typedef struct CypherAccessPathElem CypherIndexResult uidx; } CypherAccessPathElem; +typedef struct CypherListCompArrayIterator +{ + array_iter array_iter; + int array_size; + int array_position; + Oid array_typid; + int16 typlen; + bool typbyval; + char typalign; +} CypherListCompArrayIterator; + /* functions in execExpr.c */ extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s); diff --git a/src/include/utils/cypher_empty_funcs.h b/src/include/utils/cypher_empty_funcs.h index d211afbd8e3..6b5f7731008 100644 --- a/src/include/utils/cypher_empty_funcs.h +++ b/src/include/utils/cypher_empty_funcs.h @@ -13,4 +13,6 @@ #include "fmgr.h" extern Datum cypher_to_jsonb(PG_FUNCTION_ARGS); +extern Datum cypher_isempty_jsonb(PG_FUNCTION_ARGS); + #endif diff --git a/src/test/regress/expected/cypher_dml2.out b/src/test/regress/expected/cypher_dml2.out index 62cf1317414..c8b313cf267 100644 --- a/src/test/regress/expected/cypher_dml2.out +++ b/src/test/regress/expected/cypher_dml2.out @@ -20,15 +20,43 @@ RETURN max(col); (1 row) CREATE ELABEL e1; +CREATE (a: v1 {id: 1}) +CREATE (b: v1 {id: 2}) +CREATE (a)-[r:e1 {text: 'text'}]->(b) +RETURN r; + r +---------------------------------- + e1[4.1][3.1,3.2]{"text": "text"} +(1 row) + +CREATE (a: v1 {id: 3}) +CREATE (b: v1 {id: 4}) +CREATE (a)-[r:e1 {id: 5, text: 'text'}]->(b) +RETURN r; + r +------------------------------------------- + e1[4.2][3.3,3.4]{"id": 5, "text": "text"} +(1 row) + -- AGV2-29, Predicates functions want jsonb, not list -MATCH p=(n1)-[r:e1*2]->(n2) +MATCH p=(n1)-[r:e1*1]->(n2) WHERE all(x in r where x.id is null) -RETURN count(p); - count -------- - 0 +RETURN count(p), p; + count | p +-------+---------------------------------------------------------------------- + 1 | [v1[3.1]{"id": 1},e1[4.1][3.1,3.2]{"text": "text"},v1[3.2]{"id": 2}] (1 row) +MATCH p=(n1)-[r:e1*1]->(n2) +WHERE all(x in r where x.text is not null) +RETURN count(p), p; + count | p +-------+------------------------------------------------------------------------------- + 1 | [v1[3.1]{"id": 1},e1[4.1][3.1,3.2]{"text": "text"},v1[3.2]{"id": 2}] + 1 | [v1[3.3]{"id": 3},e1[4.2][3.3,3.4]{"id": 5, "text": "text"},v1[3.4]{"id": 4}] +(2 rows) + +MATCH (n) DETACH DELETE n; -- AGV2-26, head/tail/last returns array CREATE(:v_user{name:'userA'}); CREATE(:v_title{name:'TitleA'}); @@ -64,6 +92,127 @@ MATCH(n)-[e*3]->(n3) RETURN last(e); e_title_type[10.2][7.1,8.1]{"val": 3, "name": "(3)"} (1 row) +MATCH (n) DETACH DELETE n; +CREATE (a:person); +create (a:person {name: 'Alice', age: 51, eyes: 'brown'}), +(b:person {name: 'Frank', age: 61, eyes: '', liked_colors: ['blue','green']}), +(c:person {name: 'Charlie', age: 53, eyes: 'green'}), +(d:person {name: 'Bob', age: 25, eyes: 'blue'}), +(e:person {name: 'Daniel', age: 54, eyes: 'brown', liked_colors: ''}), +(f:person {name: 'Eskil', age: 41, eyes: 'blue', liked_colors: ['pink','yellow','black']}), +(a)-[:knows]->(c), +(a)-[:knows]->(d), +(c)-[:knows]->(e), +(d)-[:knows]->(e), +(d)-[:married]->(f); +-- all(..) +MATCH p = (a)-[*1..3]->(b) +WHERE +a.name = 'Alice' +AND b.name = 'Daniel' +AND all(x IN nodes(p) WHERE x.age > 30) +RETURN [x in nodes(p) | x.age]; + ?column? +-------------- + [51, 53, 54] +(1 row) + +-- any(..) +MATCH (n) +WHERE any(color IN n.liked_colors WHERE color = 'yellow') +RETURN n ; + n +------------------------------------------------------------------------------------------------------- + person[11.7]{"age": 41, "eyes": "blue", "name": "Eskil", "liked_colors": ["pink", "yellow", "black"]} +(1 row) + +-- exists(..) +MATCH (n) +WHERE n.name IS NOT NULL +RETURN +n.name AS name, +exists((n)-[:MARRIED]->()) AS is_married; + name | is_married +-----------+------------ + "Alice" | f + "Frank" | f + "Charlie" | f + "Bob" | t + "Daniel" | f + "Eskil" | f +(6 rows) + +-- isEmpty(..) +-- List +MATCH (n) +WHERE NOT isEmpty(n.liked_colors) +RETURN n ; + n +------------------------------------------------------------------------------------------------------- + person[11.3]{"age": 61, "eyes": "", "name": "Frank", "liked_colors": ["blue", "green"]} + person[11.7]{"age": 41, "eyes": "blue", "name": "Eskil", "liked_colors": ["pink", "yellow", "black"]} +(2 rows) + +-- Map +MATCH (n) +WHERE isEmpty(properties(n)) +RETURN n ; + n +---------------- + person[11.1]{} +(1 row) + +MATCH (n) +WHERE NOT isEmpty(properties(n)) +RETURN n ; + n +------------------------------------------------------------------------------------------------------- + person[11.2]{"age": 51, "eyes": "brown", "name": "Alice"} + person[11.3]{"age": 61, "eyes": "", "name": "Frank", "liked_colors": ["blue", "green"]} + person[11.4]{"age": 53, "eyes": "green", "name": "Charlie"} + person[11.5]{"age": 25, "eyes": "blue", "name": "Bob"} + person[11.6]{"age": 54, "eyes": "brown", "name": "Daniel", "liked_colors": ""} + person[11.7]{"age": 41, "eyes": "blue", "name": "Eskil", "liked_colors": ["pink", "yellow", "black"]} +(6 rows) + +-- String +MATCH (n) +WHERE isEmpty(n.eyes) +RETURN n.age AS age ; + age +----- + 61 +(1 row) + +-- none(..) +MATCH p = (n)-[*1..3]->(b) +WHERE +n.name = 'Alice' +AND none(x IN nodes(p) WHERE x.age = 25) +RETURN p ; + p +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [person[11.2]{"age": 51, "eyes": "brown", "name": "Alice"},knows[12.1][11.2,11.4]{},person[11.4]{"age": 53, "eyes": "green", "name": "Charlie"}] + [person[11.2]{"age": 51, "eyes": "brown", "name": "Alice"},knows[12.1][11.2,11.4]{},person[11.4]{"age": 53, "eyes": "green", "name": "Charlie"},knows[12.3][11.4,11.6]{},person[11.6]{"age": 54, "eyes": "brown", "name": "Daniel", "liked_colors": ""}] +(2 rows) + +-- single(..) +MATCH p = (n)-[]->(b) +WHERE +n.name = 'Alice' +AND single(var IN nodes(p) WHERE var.eyes = 'blue') +RETURN p ; + p +--------------------------------------------------------------------------------------------------------------------------------------------- + [person[11.2]{"age": 51, "eyes": "brown", "name": "Alice"},knows[12.2][11.2,11.5]{},person[11.5]{"age": 25, "eyes": "blue", "name": "Bob"}] +(1 row) + +MATCH (n) DETACH DELETE n; +MATCH (n) RETURN n; + n +--- +(0 rows) + -- Trigger CREATE TEMPORARY TABLE _trigger_history( id graphid, @@ -112,66 +261,62 @@ create trigger v1_test_trigger_af for each row execute procedure v1_test_trigger_func_af(); CREATE (v1:v1 {name: 'trigger_item'}) RETURN v1; -NOTICE: Bf: (3.1,"{""name"": ""trigger_item""}") -NOTICE: Af: (3.1,"{""name"": ""trigger_item""}") +NOTICE: Bf: (3.5,"{""name"": ""trigger_item""}") +NOTICE: Af: (3.5,"{""name"": ""trigger_item""}") v1 --------------------------------- - v1[3.1]{"name": "trigger_item"} + v1[3.5]{"name": "trigger_item"} (1 row) SELECT * FROM _trigger_history; id | is_before -----+----------- - 3.1 | t - 3.1 | f + 3.5 | t + 3.5 | f (2 rows) -- Must fail MATCH (n) SET n.name = 'trigger_item_updated' RETURN n; -NOTICE: Bf: (3.1,"{""name"": ""trigger_item_updated""}") (3.1,"{""name"": ""trigger_item""}") +NOTICE: Bf: (3.5,"{""name"": ""trigger_item_updated""}") (3.5,"{""name"": ""trigger_item""}") ERROR: duplicate key value violates unique constraint "pk_history" -DETAIL: Key (id, is_before)=(3.1, t) already exists. +DETAIL: Key (id, is_before)=(3.5, t) already exists. CONTEXT: SQL statement "INSERT INTO _trigger_history(id, is_before) VALUES (new.id, true)" PL/pgSQL function v1_test_trigger_func_be() line 11 at SQL statement SELECT * FROM _trigger_history; id | is_before -----+----------- - 3.1 | t - 3.1 | f + 3.5 | t + 3.5 | f (2 rows) -- Should pass DELETE FROM _trigger_history; MATCH (n) SET n.name = 'trigger_item_updated' RETURN n; -NOTICE: Bf: (3.1,"{""name"": ""trigger_item_updated""}") (3.1,"{""name"": ""trigger_item""}") -NOTICE: Af: (3.1,"{""name"": ""trigger_item_updated""}") (3.1,"{""name"": ""trigger_item""}") - n ----------------------------------------------- - v1[3.1]{"name": "trigger_item_updated"} - v_user[5.1]{"name": "trigger_item_updated"} - v_title[6.1]{"name": "trigger_item_updated"} - v_type[7.1]{"name": "trigger_item_updated"} - v_sub[8.1]{"name": "trigger_item_updated"} -(5 rows) +NOTICE: Bf: (3.5,"{""name"": ""trigger_item_updated""}") (3.5,"{""name"": ""trigger_item""}") +NOTICE: Af: (3.5,"{""name"": ""trigger_item_updated""}") (3.5,"{""name"": ""trigger_item""}") + n +----------------------------------------- + v1[3.5]{"name": "trigger_item_updated"} +(1 row) SELECT * FROM _trigger_history; id | is_before -----+----------- - 3.1 | t - 3.1 | f + 3.5 | t + 3.5 | f (2 rows) -- Must empty MATCH (n) DETACH DELETE n; -NOTICE: Bf: (3.1,"{""name"": ""trigger_item_updated""}") -NOTICE: Af: (3.1,"{""name"": ""trigger_item_updated""}") +NOTICE: Bf: (3.5,"{""name"": ""trigger_item_updated""}") +NOTICE: Af: (3.5,"{""name"": ""trigger_item_updated""}") SELECT * FROM _trigger_history; id | is_before ----+----------- (0 rows) DROP GRAPH cypher_dml2 CASCADE; -NOTICE: drop cascades to 11 other objects +NOTICE: drop cascades to 14 other objects DETAIL: drop cascades to sequence cypher_dml2.ag_label_seq drop cascades to vlabel ag_vertex drop cascades to elabel ag_edge @@ -183,6 +328,9 @@ drop cascades to vlabel v_type drop cascades to vlabel v_sub drop cascades to elabel e_user_title drop cascades to elabel e_title_type +drop cascades to vlabel person +drop cascades to elabel knows +drop cascades to elabel married -- #589 ( Cypher read clauses cannot follow update clauses ) CREATE GRAPH cypher_dml2; SET GRAPH_PATH to cypher_dml2; diff --git a/src/test/regress/sql/cypher_dml2.sql b/src/test/regress/sql/cypher_dml2.sql index 76df411a977..6c9a9e733b3 100644 --- a/src/test/regress/sql/cypher_dml2.sql +++ b/src/test/regress/sql/cypher_dml2.sql @@ -15,10 +15,26 @@ RETURN max(col); CREATE ELABEL e1; +CREATE (a: v1 {id: 1}) +CREATE (b: v1 {id: 2}) +CREATE (a)-[r:e1 {text: 'text'}]->(b) +RETURN r; + +CREATE (a: v1 {id: 3}) +CREATE (b: v1 {id: 4}) +CREATE (a)-[r:e1 {id: 5, text: 'text'}]->(b) +RETURN r; + -- AGV2-29, Predicates functions want jsonb, not list -MATCH p=(n1)-[r:e1*2]->(n2) +MATCH p=(n1)-[r:e1*1]->(n2) WHERE all(x in r where x.id is null) -RETURN count(p); +RETURN count(p), p; + +MATCH p=(n1)-[r:e1*1]->(n2) +WHERE all(x in r where x.text is not null) +RETURN count(p), p; + +MATCH (n) DETACH DELETE n; -- AGV2-26, head/tail/last returns array CREATE(:v_user{name:'userA'}); @@ -40,6 +56,78 @@ MATCH(n)-[e*3]->(n3) RETURN head(e); MATCH(n)-[e*3]->(n3) RETURN tail(e); MATCH(n)-[e*3]->(n3) RETURN last(e); +MATCH (n) DETACH DELETE n; + +CREATE (a:person); +create (a:person {name: 'Alice', age: 51, eyes: 'brown'}), +(b:person {name: 'Frank', age: 61, eyes: '', liked_colors: ['blue','green']}), +(c:person {name: 'Charlie', age: 53, eyes: 'green'}), +(d:person {name: 'Bob', age: 25, eyes: 'blue'}), +(e:person {name: 'Daniel', age: 54, eyes: 'brown', liked_colors: ''}), +(f:person {name: 'Eskil', age: 41, eyes: 'blue', liked_colors: ['pink','yellow','black']}), +(a)-[:knows]->(c), +(a)-[:knows]->(d), +(c)-[:knows]->(e), +(d)-[:knows]->(e), +(d)-[:married]->(f); + +-- all(..) +MATCH p = (a)-[*1..3]->(b) +WHERE +a.name = 'Alice' +AND b.name = 'Daniel' +AND all(x IN nodes(p) WHERE x.age > 30) +RETURN [x in nodes(p) | x.age]; + +-- any(..) +MATCH (n) +WHERE any(color IN n.liked_colors WHERE color = 'yellow') +RETURN n ; + +-- exists(..) +MATCH (n) +WHERE n.name IS NOT NULL +RETURN +n.name AS name, +exists((n)-[:MARRIED]->()) AS is_married; + +-- isEmpty(..) +-- List +MATCH (n) +WHERE NOT isEmpty(n.liked_colors) +RETURN n ; + +-- Map +MATCH (n) +WHERE isEmpty(properties(n)) +RETURN n ; + +MATCH (n) +WHERE NOT isEmpty(properties(n)) +RETURN n ; + +-- String +MATCH (n) +WHERE isEmpty(n.eyes) +RETURN n.age AS age ; + +-- none(..) +MATCH p = (n)-[*1..3]->(b) +WHERE +n.name = 'Alice' +AND none(x IN nodes(p) WHERE x.age = 25) +RETURN p ; + +-- single(..) +MATCH p = (n)-[]->(b) +WHERE +n.name = 'Alice' +AND single(var IN nodes(p) WHERE var.eyes = 'blue') +RETURN p ; + +MATCH (n) DETACH DELETE n; +MATCH (n) RETURN n; + -- Trigger CREATE TEMPORARY TABLE _trigger_history( id graphid, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index d061d851436..53ce85aed0d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3855,3 +3855,4 @@ GraphVLEPath GraphVLE GraphVLEState VLEDepthCtx +CypherListCompArrayIterator