From a0060f415a2c564695e6d79cad20e3ed1be14135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:09:08 +0200 Subject: [PATCH 1/3] Restructuring Expressions (#697) Current Expressions section is nested in Syntax chapter. This PR will integrate them into more helpful places in the Manual (and not create a new Expressions chapter, the title of which is too vague/encompassing to be helpful). Specific details: - Expressions page deleted - New Queries section (containing CASE and expressions in general from old Expressions page) - New Subqueries section (containing COLLECT, COUNT and EXISTS subquery expressions from old Expressions page, and the CALL subquery page (previously in Clauses)). - Type predicate expressions moved from old Expressions page as its own page in Values and types section Additionally: - New Queries section contains old Tutorial page from introduction (renamed Basic queries) - Queries section also contains new Core concepts pafe --------- Co-authored-by: Stefano Ottolenghi --- modules/ROOT/content-nav.adoc | 33 +- modules/ROOT/images/call_subquery_graph.svg | 9 + modules/ROOT/images/case_graph.svg | 1 + modules/ROOT/images/graph3.svg | 98 - .../images/graph_call_subquery_clause.svg | 71 - .../images/graph_expression_subqueries.svg | 121 - .../graph_expression_type_predicate.svg | 40 - modules/ROOT/images/subqueries_graph.svg | 1 + .../type_predicate_expression_graph.svg | 1 + modules/ROOT/pages/clauses/call-subquery.adoc | 965 ------- modules/ROOT/pages/clauses/call.adoc | 2 +- .../pages/clauses/clause_composition.adoc | 2 +- modules/ROOT/pages/clauses/delete.adoc | 2 +- modules/ROOT/pages/clauses/index.adoc | 6 +- modules/ROOT/pages/clauses/load-csv.adoc | 4 +- modules/ROOT/pages/clauses/match.adoc | 2 +- .../pages/clauses/transaction-clauses.adoc | 2 +- modules/ROOT/pages/clauses/use.adoc | 2 +- modules/ROOT/pages/clauses/where.adoc | 2 +- ...ions-additions-removals-compatibility.adoc | 10 +- modules/ROOT/pages/functions/predicate.adoc | 2 +- .../ROOT/pages/introduction/cypher_neo4j.adoc | 2 +- modules/ROOT/pages/introduction/index.adoc | 1 - modules/ROOT/pages/keyword-glossary.adoc | 14 +- modules/ROOT/pages/patterns/index.adoc | 2 +- modules/ROOT/pages/patterns/primer.adoc | 2 +- modules/ROOT/pages/patterns/reference.adoc | 2 +- .../basic.adoc} | 87 +- modules/ROOT/pages/queries/case.adoc | 249 ++ modules/ROOT/pages/queries/concepts.adoc | 82 + modules/ROOT/pages/queries/expressions.adoc | 84 + modules/ROOT/pages/queries/index.adoc | 10 + .../ROOT/pages/subqueries/call-subquery.adoc | 369 +++ modules/ROOT/pages/subqueries/collect.adoc | 325 +++ modules/ROOT/pages/subqueries/count.adoc | 299 +++ .../ROOT/pages/subqueries/existential.adoc | 258 ++ modules/ROOT/pages/subqueries/index.adoc | 12 + .../subqueries-in-transactions.adoc | 552 ++++ modules/ROOT/pages/syntax/expressions.adoc | 2349 ----------------- modules/ROOT/pages/syntax/index.adoc | 1 - modules/ROOT/pages/syntax/operators.adoc | 6 +- modules/ROOT/pages/syntax/parsing.adoc | 2 +- .../ROOT/pages/values-and-types/index.adoc | 3 +- .../property-structural-constructed.adoc | 8 +- .../values-and-types/type-predicate.adoc | 305 +++ .../values-and-types/working-with-null.adoc | 2 +- 46 files changed, 2670 insertions(+), 3732 deletions(-) create mode 100644 modules/ROOT/images/call_subquery_graph.svg create mode 100644 modules/ROOT/images/case_graph.svg delete mode 100644 modules/ROOT/images/graph3.svg delete mode 100644 modules/ROOT/images/graph_call_subquery_clause.svg delete mode 100644 modules/ROOT/images/graph_expression_subqueries.svg delete mode 100644 modules/ROOT/images/graph_expression_type_predicate.svg create mode 100644 modules/ROOT/images/subqueries_graph.svg create mode 100644 modules/ROOT/images/type_predicate_expression_graph.svg delete mode 100644 modules/ROOT/pages/clauses/call-subquery.adoc rename modules/ROOT/pages/{introduction/cypher_tutorial.adoc => queries/basic.adoc} (94%) create mode 100644 modules/ROOT/pages/queries/case.adoc create mode 100644 modules/ROOT/pages/queries/concepts.adoc create mode 100644 modules/ROOT/pages/queries/expressions.adoc create mode 100644 modules/ROOT/pages/queries/index.adoc create mode 100644 modules/ROOT/pages/subqueries/call-subquery.adoc create mode 100644 modules/ROOT/pages/subqueries/collect.adoc create mode 100644 modules/ROOT/pages/subqueries/count.adoc create mode 100644 modules/ROOT/pages/subqueries/existential.adoc create mode 100644 modules/ROOT/pages/subqueries/index.adoc create mode 100644 modules/ROOT/pages/subqueries/subqueries-in-transactions.adoc create mode 100644 modules/ROOT/pages/values-and-types/type-predicate.adoc diff --git a/modules/ROOT/content-nav.adoc b/modules/ROOT/content-nav.adoc index 7590e3771..2010ac6a9 100644 --- a/modules/ROOT/content-nav.adoc +++ b/modules/ROOT/content-nav.adoc @@ -1,18 +1,13 @@ * xref:introduction/index.adoc[] ** xref:introduction/cypher_overview.adoc[] -** xref:introduction/cypher_tutorial.adoc[] ** xref:introduction/cypher_neo4j.adoc[] ** xref:introduction/cypher_aura.adoc[] -* xref:syntax/index.adoc[] -** xref:syntax/parsing.adoc[] -** xref:syntax/naming.adoc[] -** xref:syntax/expressions.adoc[] -** xref:syntax/variables.adoc[] -** xref:syntax/reserved.adoc[] -** xref:syntax/parameters.adoc[] -** xref:syntax/operators.adoc[] -** xref:syntax/comments.adoc[] +* xref:queries/index.adoc[] +** xref:queries/concepts.adoc[] +** xref:queries/basic.adoc[] +** xref:queries/expressions.adoc[] +** xref:queries/case.adoc[] * xref:clauses/index.adoc[] ** xref:clauses/clause_composition.adoc[] @@ -31,7 +26,6 @@ ** xref:clauses/remove.adoc[] ** xref:clauses/foreach.adoc[] ** xref:clauses/merge.adoc[] -** xref:clauses/call-subquery.adoc[] ** xref:clauses/call.adoc[] ** xref:clauses/union.adoc[] ** xref:clauses/use.adoc[] @@ -42,6 +36,12 @@ ** xref:clauses/transaction-clauses.adoc#query-listing-transactions[SHOW TRANSACTIONS] ** xref:clauses/transaction-clauses.adoc#query-terminate-transactions[TERMINATE TRANSACTIONS] +* xref:subqueries/index.adoc[] +** xref:subqueries/call-subquery.adoc[] +** xref:subqueries/subqueries-in-transactions.adoc[] +** xref:subqueries/existential.adoc[] +** xref:subqueries/count.adoc[] +** xref:subqueries/collect.adoc[] * xref:patterns/index.adoc[] ** xref:patterns/concepts.adoc[] @@ -57,7 +57,7 @@ ** xref:values-and-types/lists.adoc[] ** xref:values-and-types/maps.adoc[] ** xref:values-and-types/casting-data.adoc[] - +** xref:values-and-types/type-predicate.adoc[] * xref:functions/index.adoc[] ** xref:functions/predicate.adoc[] @@ -117,6 +117,15 @@ *** xref:administration/access-control/limitations.adoc[] *** xref:administration/access-control/privileges-immutable.adoc[] +* xref:syntax/index.adoc[] +** xref:syntax/parsing.adoc[] +** xref:syntax/naming.adoc[] +** xref:syntax/variables.adoc[] +** xref:syntax/reserved.adoc[] +** xref:syntax/parameters.adoc[] +** xref:syntax/operators.adoc[] +** xref:syntax/comments.adoc[] + * xref:deprecations-additions-removals-compatibility.adoc[] * xref:keyword-glossary.adoc[] diff --git a/modules/ROOT/images/call_subquery_graph.svg b/modules/ROOT/images/call_subquery_graph.svg new file mode 100644 index 000000000..10e7f2ae6 --- /dev/null +++ b/modules/ROOT/images/call_subquery_graph.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/modules/ROOT/images/case_graph.svg b/modules/ROOT/images/case_graph.svg new file mode 100644 index 000000000..98bfc6ba1 --- /dev/null +++ b/modules/ROOT/images/case_graph.svg @@ -0,0 +1 @@ +KNOWSKNOWSKNOWSKNOWSMARRIEDPersonname:'Alice'age:38eyes:'brown'Personname:'Charlie'age:53eyes:'green'Personname:'Bob'age:25eyes:'blue'Personname:'Daniel'eyes:'brown'Personname:'Eskil'age:41eyes:'blue' diff --git a/modules/ROOT/images/graph3.svg b/modules/ROOT/images/graph3.svg deleted file mode 100644 index a8a1c2697..000000000 --- a/modules/ROOT/images/graph3.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - -L - - - -N0 - -A - -name = 'Alice' -age = 38 -eyes = 'brown' - - - -N2 - -C - -name = 'Charlie' -age = 53 -eyes = 'green' - - - -N0->N2 - - -KNOWS - - - -N1 - -B - -name = 'Bob' -age = 25 -eyes = 'blue' - - - -N0->N1 - - -KNOWS - - - -N3 - -D - -name = 'Daniel' -eyes = 'brown' - - - -N2->N3 - - -KNOWS - - - -N1->N3 - - -KNOWS - - - -N4 - -E - -eyes = 'blue' -array = ['one', 'two', 'three'] -name = 'Eskil' -age = 41 - - - -N1->N4 - - -MARRIED - - - diff --git a/modules/ROOT/images/graph_call_subquery_clause.svg b/modules/ROOT/images/graph_call_subquery_clause.svg deleted file mode 100644 index 17483f2b5..000000000 --- a/modules/ROOT/images/graph_call_subquery_clause.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - -L - - - -N0 - -Person, Child - -age = 20 -name = 'Alice' - - - -N2 - -Person, Parent - -age = 65 -name = 'Charlie' - - - -N0->N2 - - -CHILD_OF - - - -N1 - -Person - -age = 27 -name = 'Bob' - - - -N0->N1 - - -FRIEND_OF - - - -N3 - -Person - -age = 30 -name = 'Dora' - - - -N4 - -Counter - -count = 0 - - - diff --git a/modules/ROOT/images/graph_expression_subqueries.svg b/modules/ROOT/images/graph_expression_subqueries.svg deleted file mode 100644 index 0b116c781..000000000 --- a/modules/ROOT/images/graph_expression_subqueries.svg +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - -H - - - -Andy - -Swedish, Person - -age = 36 - name = 'Andy' - - - -AndyDog - -Dog - -name = 'Andy' - - - -Andy->AndyDog - - -HAS_DOG -  since = 2016 - - - -Timothy - -Person - -age = 25 - name = 'Timothy' - nickname = 'Tim' - - - -Mittens - -Cat - -name = 'Mittens' - - - -Timothy->Mittens - - -HAS_CAT -  since = 2019 - - - -Peter - -Person - -age = 25 - name = 'Peter' - nickname = 'Pete' - - - -Ozzy - -Dog - -name = 'Ozzy' - - - -Peter->Ozzy - - -HAS_DOG -  since = 2018 - - - -Fido - -Dog - -name = 'Fido' - - - -Peter->Fido - - -HAS_DOG -  since = 2010 - - - -Banana - -Toy - -name = 'Banana' - - - -Fido->Banana - - -  HAS_TOY - - - diff --git a/modules/ROOT/images/graph_expression_type_predicate.svg b/modules/ROOT/images/graph_expression_type_predicate.svg deleted file mode 100644 index e0ef5ddf7..000000000 --- a/modules/ROOT/images/graph_expression_type_predicate.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - -H - - - -Alice - -Person - - name = 'Alice' - age = 14 - - - -Bob - -Person - - name = 'Bob' - age = '20' - - - -Charlie - -Person - - name = 'Charlie' - age = 21 - - - diff --git a/modules/ROOT/images/subqueries_graph.svg b/modules/ROOT/images/subqueries_graph.svg new file mode 100644 index 000000000..ca8c8b786 --- /dev/null +++ b/modules/ROOT/images/subqueries_graph.svg @@ -0,0 +1 @@ +HAS_DOGsince:2016HAS_CATsince:2019HAS_DOGsince:2018HAS_DOGsince:2010HAS TOYPersonSwedishname:'Andy'age:36Dogname:'Andy'Personname:'Timothy'age:25nickname:'Tim'Catname:'Mittens'Personname:'Peterage:25nickname:'Pete'Dogname:'Ozzy'Dogname:'Fido'Toyname:'Banana' \ No newline at end of file diff --git a/modules/ROOT/images/type_predicate_expression_graph.svg b/modules/ROOT/images/type_predicate_expression_graph.svg new file mode 100644 index 000000000..200d41d07 --- /dev/null +++ b/modules/ROOT/images/type_predicate_expression_graph.svg @@ -0,0 +1 @@ +Personname:'Alice'age:18Personname:'Bob'age:'20'Personname:'Charlie'age:21 \ No newline at end of file diff --git a/modules/ROOT/pages/clauses/call-subquery.adoc b/modules/ROOT/pages/clauses/call-subquery.adoc deleted file mode 100644 index a98458dd0..000000000 --- a/modules/ROOT/pages/clauses/call-subquery.adoc +++ /dev/null @@ -1,965 +0,0 @@ -:description: The `+CALL {}+` clause evaluates a subquery that returns some values. - -[[query-call-subquery]] -= +CALL {}+ (subquery) - -[abstract] --- -The `+CALL {}+` clause evaluates a subquery that returns some values. --- - -`CALL` allows to execute subqueries, i.e. queries inside of other queries. -Subqueries allow you to compose queries, which is especially useful when working with `UNION` or aggregations. - -[TIP] -==== -The `CALL` clause is also used for calling procedures. -For descriptions of the `CALL` clause in this context, refer to xref::clauses/call.adoc[`CALL` procedure]. -==== - -Subqueries which end in a `RETURN` statement are called _returning subqueries_ while subqueries without such a return statement are called _unit subqueries_. - -A subquery is evaluated for each incoming input row. -Every output row of a _returning subquery_ is combined with the input row to build the result of the subquery. -That means that a returning subquery will influence the number of rows. -If the subquery does not return any rows, there will be no rows available after the subquery. - -_Unit subqueries_ on the other hand are called for their side-effects and not for their results and do therefore not influence the results of the enclosing query. - -There are restrictions on how subqueries interact with the enclosing query: - -* A subquery can only refer to variables from the enclosing query if they are explicitly imported. -* A subquery cannot return variables with the same names as variables in the enclosing query. -* All variables that are returned from a subquery are afterwards available in the enclosing query. - -The following graph is used for the examples below: - -image:graph_call_subquery_clause.svg[] - -To recreate the graph, run the following query in an empty Neo4j database: - -[source, cypher, role=test-setup] ----- -CREATE - (a:Person:Child {age: 20, name: 'Alice'}), - (b:Person {age: 27, name: 'Bob'}), - (c:Person:Parent {age: 65, name: 'Charlie'}), - (d:Person {age: 30, name: 'Dora'}) - CREATE (a)-[:FRIEND_OF]->(b) - CREATE (a)-[:CHILD_OF]->(c) -CREATE (:Counter {count: 0}) ----- - - -[[call-semantics]] -== Semantics - -A `CALL` clause is executed once for each incoming row. - - -.Execute for each incoming row -====== - -The `CALL` clause executes three times, one for each row that the `UNWIND` clause outputs. - -.Query -[source, cypher] ----- -UNWIND [0, 1, 2] AS x -CALL { - RETURN 'hello' AS innerReturn -} -RETURN innerReturn ----- - -.Result -[role="queryresult",options="header,footer",cols="m"] -|=== -| +innerReturn+ -| +'hello'+ -| +'hello'+ -| +'hello'+ -d|Rows:3 -|=== -====== - -Each execution of a `CALL` clause can observe changes from previous executions. - - -.Observe changes from previous execution -====== - -.Query -[source, cypher] ----- -UNWIND [0, 1, 2] AS x -CALL { - MATCH (n:Counter) - SET n.count = n.count + 1 - RETURN n.count AS innerCount -} -WITH innerCount -MATCH (n:Counter) -RETURN - innerCount, - n.count AS totalCount ----- - -.Result -[role="queryresult",options="header,footer",cols=""2*(next) - RETURN current AS from, next AS to -} -RETURN - from.name AS name, - from.age AS age, - to.name AS closestOlderName, - to.age AS closestOlderAge ----- - -.Result -[role="queryresult",options="header,footer",cols="4*(other:Person) - RETURN other -UNION - WITH p - OPTIONAL MATCH (p)-[:CHILD_OF]->(other:Parent) - RETURN other -} -RETURN DISTINCT p.name, count(other) ----- - -.Result -[role="queryresult",options="header,footer",cols="2*(b), - (a)-[:CHILD_OF]->(c) ----- -//// - -[[subquery-correlated-aggregation]] -== Aggregation on imported variables - -Aggregations in subqueries are scoped to the subquery evaluation, also for imported variables. -The following example counts the number of younger persons for each person in the graph: - -.Query -[source, cypher] ----- -MATCH (p:Person) -CALL { - WITH p - MATCH (other:Person) - WHERE other.age < p.age - RETURN count(other) AS youngerPersonsCount -} -RETURN p.name, youngerPersonsCount ----- - -.Result -[role="queryresult",options="header,footer",cols="2*>. -Canceling that outer transaction will cancel the inner ones. - - -.+Loading CSV data in transactions+ -====== -This example uses a CSV file and the `LOAD CSV` clause to import more data to the example graph. -It creates nodes in separate transactions using `+CALL { ... } IN TRANSACTIONS+`: - -.friends.csv -[source, csv, role="noheader" filename="friends.csv"] ----- -1,Bill,26 -2,Max,27 -3,Anna,22 -4,Gladys,29 -5,Summer,24 ----- - -.Query -[source, cypher] ----- -LOAD CSV FROM 'file:///friends.csv' AS line -CALL { - WITH line - CREATE (:Person {name: line[1], age: toInteger(line[2])}) -} IN TRANSACTIONS ----- - -.Result -[role="queryresult",options="footer",cols="1* 100 -CALL { - WITH n - DETACH DELETE n -} IN TRANSACTIONS ----- - -.Result -[role="queryresult",options="footer",cols="1*>. - -After each execution of the inner query finishes (successfully or not), a status value is created that records information about the execution and the transaction that executed it: - -* If the inner execution produces one or more rows as output, then a binding to this status value is added to each row, under the selected variable name. -* If the inner execution fails then a single row is produced containing a binding to this status value under the selected variable, and null bindings for all variables that should have been returned by the inner query (if any). - -The status value is a map value with the following fields: - -* `started`: `true` when the inner transaction was started, `false` otherwise. -* `committed`, true when the inner transaction changes were successfully committed, false otherwise. -* `transactionId`: the inner transaction id, or null if the transaction was not started. -* `errorMessage`, the inner transaction error message, or null in case of no error. - -Example of reporting status with `ON ERROR CONTINUE`: - -.Query -[source, cypher, indent=0, role=test-result-skip] ----- -UNWIND [1, 0, 2, 4] AS i -CALL { - WITH i - CREATE (n:Person {num: 100/i}) // Note, fails when i = 0 - RETURN n -} IN TRANSACTIONS - OF 1 ROW - ON ERROR CONTINUE - REPORT STATUS AS s -RETURN n.num, s; ----- - -.Result -[role="queryresult",options="header,footer",cols="2* 2 - RETURN l AS largeLists -} -RETURN largeLists ----- - -.Error message -[source, error] ----- -Importing WITH should consist only of simple references to outside variables. -WHERE is not allowed. ----- - -A solution to this restriction, necessary for any filtering or ordering of an importing `WITH` clause, is to declare a second `WITH` clause after the importing `WITH` clause. -This second `WITH` clause will act as a regular `WITH` clause. -For example, the following query will not throw an error: - -.Query -[source, cypher] ----- -UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l -CALL { - WITH l - WITH size(l) AS size, l AS l - WHERE size > 2 - RETURN l AS largeLists -} -RETURN largeLists ----- - -.Result -[role="queryresult",options="header,footer",cols="1* ---- a| -Extended xref:syntax/expressions.adoc#type-predicate-expressions[type predicate expressions]. +Extended xref:values-and-types/type-predicate.adoc[type predicate expressions]. Closed dynamic union types (`type1 \| type2 \| ...`) are now supported. For example, the following query which evaluates to true if a value is either of type `INTEGER` or `FLOAT`: [source, cypher, role="noheader"] @@ -157,7 +157,7 @@ IS [NOT] :: ---- a| -Extended xref:syntax/expressions.adoc#type-predicate-expressions[type predicate expressions]. +Extended xref:values-and-types/type-predicate.adoc[type predicate expressions]. The newly supported types are: * `NOTHING` @@ -330,7 +330,7 @@ IS [NOT] :: ---- a| -Added xref:syntax/expressions.adoc#type-predicate-expressions[type predicate expressions]. +Added xref:values-and-types/type-predicate.adoc[type predicate expressions]. The available types are: * `BOOLEAN` @@ -2170,7 +2170,7 @@ CALL { a| New clause for evaluating a subquery in separate transactions. Typically used when modifying or importing large amounts of data. -See xref:clauses/call-subquery.adoc#subquery-call-in-transactions[CALL +++{ ... }+++ IN TRANSACTIONS]. +See xref:subqueries/subqueries-in-transactions.adoc[CALL +++{ ... }+++ IN TRANSACTIONS]. a| label:syntax[] @@ -3805,7 +3805,7 @@ For versions below 5.0, use a pattern comprehension instead: ---- RETURN size([ (a)-[]->(b) \| a ]) ---- -See xref:functions/scalar.adoc#functions-size[size()] and xref:syntax/expressions.adoc#count-subqueries[Count Subqueries] for more details. +See xref:functions/scalar.adoc#functions-size[size()] and xref:subqueries/count.adoc[Count Subqueries] for more details. |=== === Updated features diff --git a/modules/ROOT/pages/functions/predicate.adoc b/modules/ROOT/pages/functions/predicate.adoc index 9c08fef4d..4f2f3fba4 100644 --- a/modules/ROOT/pages/functions/predicate.adoc +++ b/modules/ROOT/pages/functions/predicate.adoc @@ -256,7 +256,7 @@ This query returns the `name` property of every `Person` node, along with a bool ==== The *function* `exists()` looks very similar to the *expression* `+EXISTS { ... }+`, but they are not related. -See xref::syntax/expressions.adoc#existential-subqueries[Using EXISTS subqueries] for more information. +See xref::subqueries/existential.adoc[Using EXISTS subqueries] for more information. ==== diff --git a/modules/ROOT/pages/introduction/cypher_neo4j.adoc b/modules/ROOT/pages/introduction/cypher_neo4j.adoc index a7c9f3b99..24c528183 100644 --- a/modules/ROOT/pages/introduction/cypher_neo4j.adoc +++ b/modules/ROOT/pages/introduction/cypher_neo4j.adoc @@ -117,7 +117,7 @@ Transactions in Neo4j can be either explicit or implicit. | Committed automatically when a transactions finishes successfully. |=== -Queries that start separate transactions themselves, such as queries using xref::clauses/call-subquery.adoc#subquery-call-in-transactions[`+CALL { ... } IN TRANSACTIONS+`], are only allowed in _implicit_ mode. +Queries that start separate transactions themselves, such as queries using xref::subqueries/subqueries-in-transactions.adoc[`+CALL { ... } IN TRANSACTIONS+`], are only allowed in _implicit_ mode. Explicit transactions cannot be managed directly from queries, they must be managed via APIs or tools. For examples of the API, or the commands used to start and commit transactions, refer to the API or tool-specific documentation: diff --git a/modules/ROOT/pages/introduction/index.adoc b/modules/ROOT/pages/introduction/index.adoc index 8b9be6e8d..56dae9a30 100644 --- a/modules/ROOT/pages/introduction/index.adoc +++ b/modules/ROOT/pages/introduction/index.adoc @@ -21,7 +21,6 @@ For a reference of all available Cypher features, see the link:{neo4j-docs-base- This introduction will cover the following topics: * xref:introduction/cypher_overview.adoc[] -* xref:introduction/cypher_tutorial.adoc[] * xref:introduction/cypher_neo4j.adoc[] * xref:introduction/cypher_aura.adoc[] diff --git a/modules/ROOT/pages/keyword-glossary.adoc b/modules/ROOT/pages/keyword-glossary.adoc index b726cfbcd..4e744b99d 100644 --- a/modules/ROOT/pages/keyword-glossary.adoc +++ b/modules/ROOT/pages/keyword-glossary.adoc @@ -28,11 +28,11 @@ This section comprises a glossary of all the keywords -- grouped by category and | Reading/Writing | Invoke a procedure deployed in the database. -| xref::clauses/call-subquery.adoc[+CALL {...}+] +| xref::subqueries/call-subquery.adoc[+CALL {...}+] | Reading/Writing | Evaluates a subquery, typically used for post-union processing or aggregations. -| xref::clauses/call-subquery.adoc#subquery-call-in-transactions[+CALL { ... } IN TRANSACTIONS+] +| xref::subqueries/subqueries-in-transactions.adoc[+CALL { ... } IN TRANSACTIONS+] | Reading/Writing a| Evaluates a subquery in separate transactions. @@ -1023,19 +1023,19 @@ xref::functions/temporal/index.adoc#functions-temporal-truncate-overview[Truncat |=== | Name | Description -| xref::syntax/expressions.adoc#query-syntax-case[CASE] +| xref::queries/case.adoc[CASE] | A generic conditional expression, similar to if/else statements available in other languages. -| xref:syntax/expressions.adoc#collect-subqueries[COLLECT {...}] +| xref:subqueries/collect.adoc[COLLECT {...}] | Creates a list with the rows returned by a subquery. -| xref:syntax/expressions.adoc#count-subqueries[COUNT {...}] +| xref:subqueries/count.adoc[COUNT {...}] | Computes the number of results of a subquery. -| xref:syntax/expressions.adoc#existential-subqueries[EXISTS {...}] +| xref:subqueries/existential.adoc[EXISTS {...}] | Evaluates the existence of a subquery. -| xref:syntax/expressions.adoc#type-predicate-expressions[IS :: `type`] +| xref:values-and-types/type-predicate.adoc#[IS :: `type`] | Verifies that an expression is of a certain type. |=== diff --git a/modules/ROOT/pages/patterns/index.adoc b/modules/ROOT/pages/patterns/index.adoc index 0327554a5..385b2e655 100644 --- a/modules/ROOT/pages/patterns/index.adoc +++ b/modules/ROOT/pages/patterns/index.adoc @@ -5,7 +5,7 @@ Graph pattern matching sits at the very core of Cypher. It is the mechanism used to navigate, describe and extract data from a graph by applying a declarative pattern. Inside a xref:clauses/match.adoc[] clause, you can use graph patterns to define the data you are searching for and the data to return. -Graph pattern matching can also be used without a `MATCH` clause, in the subqueries xref::syntax/expressions.adoc#existential-subqueries[EXISTS], xref::syntax/expressions.adoc#count-subqueries[COUNT], and xref::syntax/expressions.adoc#collect-subqueries[COLLECT]. +Graph pattern matching can also be used without a `MATCH` clause, in the subqueries xref::subqueries/existential.adoc[EXISTS], xref::subqueries/count.adoc[COUNT], and xref::subqueries/collect.adoc[COLLECT]. A graph pattern describes data using a syntax that is similar to how the nodes and relationships of a property graph are drawn on a whiteboard. On a whiteboard, nodes are drawn as circles and relationships are drawn as arrows. diff --git a/modules/ROOT/pages/patterns/primer.adoc b/modules/ROOT/pages/patterns/primer.adoc index d414720b4..e4fc28e31 100644 --- a/modules/ROOT/pages/patterns/primer.adoc +++ b/modules/ROOT/pages/patterns/primer.adoc @@ -178,7 +178,7 @@ RETURN station.name AS callingPoint |=== `WHERE` clauses inside node patterns can themselves include path patterns. -The following query using an xref::syntax/expressions.adoc#existential-subqueries[EXISTS subquery] to anchor on the last `Stop` in a sequence of `Stops`, and returns the departure times, arrival times and final destination of all services calling at `Denmark Hill`: +The following query using an xref::subqueries/existential.adoc[EXISTS subquery] to anchor on the last `Stop` in a sequence of `Stops`, and returns the departure times, arrival times and final destination of all services calling at `Denmark Hill`: .Query [source, cypher] diff --git a/modules/ROOT/pages/patterns/reference.adoc b/modules/ROOT/pages/patterns/reference.adoc index 6dd3946fc..134a4006c 100644 --- a/modules/ROOT/pages/patterns/reference.adoc +++ b/modules/ROOT/pages/patterns/reference.adoc @@ -340,7 +340,7 @@ is equivalent to the following node pattern with a `WHERE` clause: (n WHERE n.p = valueExp1 AND n.q = valueExp2) ---- -The value expression can be any expression as listed in the chapter on xref:syntax/expressions.adoc[expressions], except for path patterns (which will throw a syntax error) and regular expressions (which will be treated as string literals). +The value expression can be any expression as listed in the section on xref:queries/expressions.adoc[expressions], except for path patterns (which will throw a syntax error) and regular expressions (which will be treated as string literals). An empty property key-value expression matches all elements. Property key-value expressions can be combined with a `WHERE` clause. diff --git a/modules/ROOT/pages/introduction/cypher_tutorial.adoc b/modules/ROOT/pages/queries/basic.adoc similarity index 94% rename from modules/ROOT/pages/introduction/cypher_tutorial.adoc rename to modules/ROOT/pages/queries/basic.adoc index b2ae94890..9890b88ce 100644 --- a/modules/ROOT/pages/introduction/cypher_tutorial.adoc +++ b/modules/ROOT/pages/queries/basic.adoc @@ -1,10 +1,12 @@ -[[cypher-tutorial]] -= Tutorial -:description: This section provides an overview of Cypher, and goes through a short Cypher tutorial based on the Neo4j movie database. += Basic queries +:description: This section provides an overview of some basic Cypher queries using the Neo4j movie database. -In this short tutorial, users will learn how to create, query, and delete a property graph database using Cypher. -The tutorial uses the xref:https://github.com/neo4j-graph-examples/movies/tree/main/documentation[Neo4j movie database]. +This page contains information about how to create, query, and delete a graph database using Cypher. +For more advanced queries, see the section on xref:subqueries/index.adoc[]. +The examples below uses the publicly available link:https://github.com/neo4j-graph-examples/movies/tree/main/documentation[Neo4j movie database]. + +[[data-model]] == Creating a data model Before creating a property graph database, it is important to develop an appropriate data model. @@ -16,17 +18,18 @@ image::introduction_schema.svg[width="800",role="middle"] It includes two types of node labels: -* `Person` nodes, which have the following properties: `name` (`STRING`) and `born` (`INTEGER`). -* `Movie` nodes, which have the following properties: `title` (`STRING`), `released` (`INTEGER`), and `tagline` (`STRING`). +* `Person` nodes, which have the following properties: `name` and `born`. +* `Movie` nodes, which have the following properties: `title`, `released`, and `tagline`. The data model also contains five different relationship types between the `Person` and `Movie` nodes: `ACTED_IN`, `DIRECTED`, `PRODUCED`, `WROTE`, and `REVIEWED`. Two of the relationship types have properties: -* The `ACTED_IN` relationship type, which has the `roles` property (`STRING`). -* The `REVIEWED` relationship type, which has a `summary` property (`STRING`) and a `rating` property (`FLOAT`). +* The `ACTED_IN` relationship type, which has the `roles` property. +* The `REVIEWED` relationship type, which has a `summary` property and a `rating` property. _To learn more about data modelling for graph databases, enroll in the free https://graphacademy.neo4j.com/courses/modeling-fundamentals/[Graph Data Modelling Fundamentals] course offered by GraphAcademy._ +[[create-graph]] == Creating a property graph database The complete Cypher query to create the Neo4j movie database, can be found https://github.com/neo4j-graph-examples/movies/blob/main/scripts/movies.cypher[here]. @@ -538,6 +541,7 @@ CREATE ---- //// +[[find-nodes]] == Finding nodes The `MATCH` clause is used to find a specific pattern in the graph, such as a specific node. @@ -557,8 +561,7 @@ RETURN keanu.name AS name, keanu.born AS born |=== | name | born -| "Keanu Reeves" -| 1964 +| "Keanu Reeves" | 1964 2+d|Rows: 1 |=== @@ -588,10 +591,11 @@ LIMIT 5 1+d|Rows: 5 |=== +[[clause-composition-note]] == Note on clause composition Similar to SQL, Cypher queries are constructed using various clauses which are chained together to feed intermediate results between each other. -Each clause has as input the state of the graph and a table of intermediate results consisting of the current variables. +Each clause has as input the state of the graph and a table of intermediate results consisting of the referenced variables. The first clause takes as input the state of the graph before the query and an empty table of intermediate results. The output of a clause is a new state of the graph and a new table of intermediate results, serving as input to the next clause. The output of the last clause is the result of the query. @@ -601,7 +605,7 @@ Note that if one of the clauses returns an empty table of intermediate results, For example, by replacing a `MATCH` clause with xref:clauses/optional-match.adoc[OPTIONAL MATCH].) In the below example, the first `MATCH` clause finds all nodes with the `Person` label. -The second clause will then filter those nodes to find all `Person` nodes who were bron in the 1980s. +The second clause will then filter those nodes to find all `Person` nodes who were born in the 1980s. The final clause returns the result in a descending chronological order. .Query @@ -609,7 +613,8 @@ The final clause returns the result in a descending chronological order. ---- MATCH (bornInEighties:Person) WHERE bornInEighties.born >= 1980 AND bornInEighties.born < 1990 -RETURN bornInEighties.name as name, bornInEighties.born as born ORDER BY born DESC +RETURN bornInEighties.name as name, bornInEighties.born as born +ORDER BY born DESC ---- .Result @@ -634,6 +639,7 @@ RETURN bornInEighties.name as name, bornInEighties.born as born ORDER BY born DE For more details, see the section on xref:clauses/clause_composition.adoc[]. +[[find-connected-nodes]] == Finding connected nodes To discover how nodes are connected to one another, relationships must be added to queries. @@ -666,17 +672,17 @@ The below query searches the graph for outgoing relationships from the `Tom Hank [source, cypher] ---- MATCH (tom:Person {name:'Tom Hanks'})-[r]->(m:Movie) -Return type(r), m.title AS movies +RETURN type(r) AS type, m.title AS movie ---- -The graph returned shows that he has 13 outgoing relationships connected to 12 different `Movie` nodes (12 have the `ACTED_IN` type and one has the `DIRECTED` type). +The result shows that he has 13 outgoing relationships connected to 12 different `Movie` nodes (12 have the `ACTED_IN` type and one has the `DIRECTED` type). image::introduction_example1.svg[width="500",role="middle"] .Result [role="queryresult",options="header,footer",cols="2*(m:Movie) -Return type(r), m.title AS movies +MATCH (:Person {name:'Tom Hanks'})-[r:!ACTED_IN]->(m:Movie) +Return type(r) AS type, m.title AS movies ---- .Result [role="queryresult",options="header,footer",cols="2*(bob), + (alice)-[:KNOWS]->(charlie), + (bob)-[:KNOWS]->(daniel), + (charlie)-[:KNOWS]->(daniel), + (bob)-[:MARRIED]->(eskil) +---- + +[[case-simple]] +== Simple `CASE` + +The simple `CASE` form is used to compare a single expression against multiple values, and is analogous to the `switch` construct of programming languages. +The expressions are evaluated by the `WHEN` operator until a match is found. +If no match is found, the expression in the `ELSE` operator is returned. +If there is no `ELSE` case and no match is found, `null` will be returned. + +[[case-simple-syntax]] +=== Syntax + +[source, syntax] +---- +CASE test + WHEN value THEN result + [WHEN ...] + [ELSE default] +END +---- + +*Arguments:* +[options="header", cols="1,2"] +|=== +| Name | Description + +| `test` +| An expression. + +| `value` +| An expression whose result will be compared to `test`. + +| `result` +| The expression returned as output if `value` matches `test` + +| `default` +| The expression to return if no value matches the test expression. +|=== + +[[case-simple-examples]] +=== Example + +[source, cypher] +---- +MATCH (n:Person) +RETURN +CASE n.eyes + WHEN 'blue' THEN 1 + WHEN 'brown' THEN 2 + ELSE 3 +END AS result, n.eyes +---- + +[role="queryresult",options="header,footer",cols="2* + WHEN null THEN -1 // <2> + ELSE n.age - 10 // <3> +END AS age_10_years_ago +---- + +<1> `n.age` is the expression being evaluated. Note that the node `Daniel` has a `null` value as age. +<2> This branch is skipped, because `null` does not equal any other value, including `null` itself. +<3> The execution takes the `ELSE` branch, which outputs `null` because `n.age - 10` equals `null`. + +[role="queryresult",options="header,footer",cols="2* + WHEN n.age IS NULL THEN -1 // <2> + ELSE n.age - 10 +END AS age_10_years_ago +---- + +<1> If no expression is provided after `CASE`, it acts in its generic form, supporting predicate expressions in each branch. +<2> This predicate expression evaluates to `true` for the node `Daniel`, so the result from this branch is returned. + +[role="queryresult",options="header,footer",cols="2*+`) indicating the direction of a relationship. + +[source, cypher] +---- +MATCH (:Person {name: 'Anna'})-[r:KNOWS WHERE r.since < 2020]->(friend:Person) +RETURN count(r) As numberOfFriends +---- + +Unlike nodes, information within a relationship pattern must be enclosed by square brackets. +The query example above matches for relationships of type `KNOWS` and with the property `since` set to less than `2020`. +The query also requires the relationships to go from a `Person` node named `Anna` to any other `Person` nodes, referred to as `friend`. +The xref:functions/aggregating.adoc#functions-count[count()] function is used in the `RETURN` clause to count all the relationships bound by the `r` variable in the preceding `MATCH` clause (i.e. how many friends Anna has known since before 2020). + +Note that while nodes can have several labels, relationships can only have one type. + +[[core-concepts-paths]] +== Paths + +Paths in a graph consist of connected nodes and relationships. +Exploring these paths sits at the very core of Cypher. + +[source, cypher] +---- +MATCH (n:Person {name: 'Anna'})-[:KNOWS]-{1,5}(friend:Person WHERE n.born < friend.born) +RETURN DISTINCT friend.name AS youngerConnections +---- + +This example uses a xref:patterns/concepts.adoc#quantified-relationships[quantified relationship] to find all paths up to `5` hops away, traversing only relationships of type `KNOWS` from the start node `Anna` to other younger `Person` nodes (as defined by the xref:clauses/where.adoc[] clause). +The xref:syntax/operators.adoc#syntax-using-the-distinct-operator[DISTINCT] operator is used to ensure that the `RETURN` clause only returns unique nodes. + +Paths can also be assigned variables. +For example, the below query binds a whole path pattern, which matches the xref:patterns/concepts.adoc#shortest-path[shortest path] from `Anna` to another `Person` node in the graph up to `10` hops away with the `nationality` property set to `Canadian`. +In this case, the `RETURN` clause returns the full path between the two nodes. + +[source, cypher] +---- +MATCH p=shortestPath((:Person {name: 'Anna'})-[:KNOWS*1..10]-(:Person {nationality: 'Canadian'})) +RETURN p +---- + +For more information about graph patterns, see the section on xref:patterns/index.adoc[]. diff --git a/modules/ROOT/pages/queries/expressions.adoc b/modules/ROOT/pages/queries/expressions.adoc new file mode 100644 index 000000000..7f73fb3cb --- /dev/null +++ b/modules/ROOT/pages/queries/expressions.adoc @@ -0,0 +1,84 @@ += Cypher expressions +:description: This page explains which expressions are allowed in Cypher. + +This page contains examples of allowed expressions in Cypher. + +[[general]] +== General + +* A variable: `n`, `x`, `rel`, `myFancyVariable`, `++`A name with special characters in it[]!`++`. +* A property: `n.prop`, `x.prop`, `rel.thisProperty`, `++myFancyVariable.`(special property name)`++`. +* A dynamic property: `n["prop"]`, `rel[n.city + n.zip]`, `map[coll[0]]`. +* A parameter: `$param`, `$0`. +* A list of expressions: `['a', 'b']`, `[1, 2, 3]`, `['a', 2, n.property, $param]`, `[]`. +* A function call: `length(p)`, `nodes(p)`. +* An aggregate function call: `avg(x.prop)`, `+count(*)+`. +* A path-pattern: `+(a)-[r]->(b)+`, `+(a)-[r]-(b)+`, `+(a)--(b)+`, `+(a)-->()<--(b)+`. +* An operator application: `1 + 2`, `3 < 4`. +* A subquery expression: `COUNT {}`, `COLLECT {}`, `EXISTS {}`, `CALL {}`. +* A regular expression: `a.name =~ 'Tim.*'`. +* A `CASE` expression. +* `null`. + +[NOTE] +==== +Expressions containing unsanitized user input may make your application vulnerable to Cypher injection. +Consider using xref:syntax/parameters.adoc[parameters] instead. +Learn more in link:https://neo4j.com/developer/kb/protecting-against-cypher-injection/[Protecting against Cypher Injection]. +==== + +[NOTE] +==== +Most expressions in Cypher evaluate to `null` if any of their inner expressions are `null`. +Notable exceptions are the operators `IS NULL`, `IS NOT NULL`, and the xref:values-and-types/type-predicate.adoc[type predicate expressions]. +==== + +[[numerical]] +== Numerical + +* A numeric (`INTEGER` or `FLOAT`) literal: `13`, `-40000`, `3.14`. +* A numeric (`INTEGER` or `FLOAT`) literal in scientific notation: `6.022E23`. +* A hexadecimal `INTEGER` literal (starting with `0x`): `0x13af`, `0xFC3A9`, `-0x66eff`. +* An octal `INTEGER` literal (starting with `0o`): `0o1372`, `-0o5671`. +* A `FLOAT` literal: `Inf`, `Infinity`, `NaN`. +* `null`. + +[NOTE] +==== +Any numeric literal may contain an underscore `_` between digits. +There may be an underscore between the `0x` or `0o` and the digits for hexadecimal and octal literals. +==== + +[[string]] +== String + +* A `STRING` literal: `'Hello'`, `"World"`. +* A case-sensitive `STRING` matching expression: `a.surname STARTS WITH 'Sven'`, `a.surname ENDS WITH 'son'` or `a.surname CONTAINS 'son'`. +* `null`. + +[[expressions-string-literals]] +=== String literal escape sequences + +String literals can contain the following escape sequences: + +[options="header", cols=">1,<2"] +|=================== +|Escape sequence|Character +|`\t`|Tab +|`\b`|Backspace +|`\n`|Newline +|`\r`|Carriage return +|`\f`|Form feed +|`\'`|Single quote +|`\"`|Double quote +|`\\`|Backslash +|`\uxxxx`|Unicode UTF-16 code point (4 hex digits must follow the `\u`) +|=================== + +[[boolean]] +== Boolean + +* A `BOOLEAN` literal: `true`, `false`. +* A predicate expression (i.e. an expression returning a `BOOLEAN` value): `a.prop = 'Hello'`, `length(p) > 10`, `a.name IS NOT NULL`. +* Label and relationship type expressions: `(n:A|B)`, `+()-[r:R1|R2]->()+`. +* `null`. diff --git a/modules/ROOT/pages/queries/index.adoc b/modules/ROOT/pages/queries/index.adoc new file mode 100644 index 000000000..b5f941fee --- /dev/null +++ b/modules/ROOT/pages/queries/index.adoc @@ -0,0 +1,10 @@ += Queries +:description: This page is an overview of the queries section in the Cypher Manual. + +This section provides a brief overview of the core concepts of a Cypher query (nodes, relationships, and paths), and examples of how to query a Neo4j graph database. +It also contains information about Cypher expressions. + +* xref:queries/concepts.adoc[] +* xref:queries/basic.adoc[] +* xref:queries/expressions.adoc[] +* xref:queries/case.adoc[] diff --git a/modules/ROOT/pages/subqueries/call-subquery.adoc b/modules/ROOT/pages/subqueries/call-subquery.adoc new file mode 100644 index 000000000..eba1a081d --- /dev/null +++ b/modules/ROOT/pages/subqueries/call-subquery.adoc @@ -0,0 +1,369 @@ += CALL subqueries +:description: This page describes how to use the CALL subquery with Cypher. + +The `CALL` clause can be used to invoke a subquery. +Unlike other subqueries in Cypher, it can be used to perform changes to the database (e.g. xref:clauses/create.adoc[] new nodes), and it requires an importing xref:clauses/with.adoc[] clause. + +[NOTE] +==== +The `CALL` clause is also used for calling procedures. +For descriptions of the `CALL` clause in this context, refer to xref::clauses/call.adoc[`CALL` procedure]. +==== + +[[call-example-graph]] +== Example graph + +The following graph is used for the examples below: + +image::call_subquery_graph.svg[] + +To recreate the graph, run the following query in an empty Neo4j database: + +[source, cypher, role=test-setup] +---- +CREATE + (a:Person:Child {name: 'Alice', age: 20}), + (b:Person {name: 'Bob', age: 27}), + (c:Person:Parent {name: 'Charlie', age: 65}), + (d:Person {name: 'Dora', age: 30}) + CREATE (a)-[:FRIEND_OF]->(b) + CREATE (a)-[:CHILD_OF]->(c) + CREATE (a)-[:OWES {dollars: 20}]->(c) + CREATE (a)-[:OWES {dollars: 25}]->(b) + CREATE (b)-[:OWES {dollars: 35}]->(d) + CREATE (d)-[:OWES {dollars: 15}]->(b) + CREATE (d)-[:OWES {dollars: 30}]->(b) +CREATE (:Counter {count: 0}) +---- + +[[call-semantics]] +== Semantics + +A `CALL` subquery is executed once for each incoming row. + +In the below example, the `CALL` subquery executes three times, one for each row that the `UNWIND` clause outputs. + +.Query +[source, cypher] +---- +UNWIND [0, 1, 2] AS x +CALL { + RETURN 'hello' AS innerReturn +} +RETURN innerReturn +---- + +.Result +[role="queryresult",options="header,footer",cols="m"] +|=== +| innerReturn +| 'hello' +| 'hello' +| 'hello' +d|Rows:3 +|=== + +Each execution of a `CALL` subquery can observe changes from previous executions. + +.Query +[source, cypher] +---- +UNWIND [0, 1, 2] AS x +CALL { + MATCH (n:Counter) + SET n.count = n.count + 1 + RETURN n.count AS innerCount +} +WITH innerCount +MATCH (n:Counter) +RETURN + innerCount, + n.count AS totalCount +---- + +.Result +[role="queryresult",options="header,footer",cols=""2*(nextPerson) + RETURN current AS from, nextPerson AS to +} +RETURN + from.name AS name, + from.age AS age, + to.name AS closestOlderName, + to.age AS closestOlderAge +---- + +.Result +[role="queryresult",options="header,footer",cols="4*(other:Person) + RETURN o.dollars * -1 AS moneyOwed +UNION ALL + WITH p + OPTIONAL MATCH (other:Person)-[o:OWES]->(p) + RETURN o.dollars AS moneyOwed +} +RETURN p.name, sum(moneyOwed) AS amountOwing +---- + +.Result +[role="queryresult",options="header,footer",cols="2*(c) + RETURN sum(o.dollars) AS owedAmount, c.name AS owedName +} +RETURN p.name, owedAmount, owedName +---- + +.Result +[role="queryresult",options="header,footer",cols="3*(:Dog {name:'Andy'}), +(timothy)-[:HAS_CAT {since: 2019}]->(:Cat {name:'Mittens'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) +---- + +[[collect-simple]] +== Simple `COLLECT` subquery + +Variables introduced by the outside scope can be used in the `COLLECT` subquery without importing them. +In this regard, `COLLECT` subqueries are different from `CALL` subqueries, xref::subqueries/call-subquery.adoc#call-importing-variables[which do require importing]. +The following query exemplifies this and outputs the owners of the dog named `Ozzy`: + +[source, cypher] +---- +MATCH (person:Person) +WHERE 'Ozzy' IN COLLECT { MATCH (person)-[:HAS_DOG]->(dog:Dog) RETURN dog.name } +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(dog:Dog) + WHERE r.since > 2017 + RETURN dog.name +} as youngDogs +---- + +[role="queryresult",options="header,footer",cols="2*(dog:Dog) + RETURN dog.name AS petName + UNION + MATCH (person)-[:HAS_CAT]->(cat:Cat) + RETURN cat.name AS petName + } AS petNames +---- + +[role="queryresult",options="header,footer",cols="2*(d:Dog {name: name}) + RETURN d.name +} as dogsOfTheYear +---- + +.Error message +[source, output, role="noheader"] +---- +The variable `name` is shadowing a variable with the same name from the outer scope and needs to be renamed (line 4, column 20 (offset: 92)) +---- + +New variables can be introduced into the subquery, as long as they use a different identifier. +In the example below, a `WITH` clause introduces a new variable. +Note that the outer scope variable `person` referenced in the main query is still available after the `WITH` clause. + +[source, cypher] +---- +MATCH (person:Person) +RETURN person.name AS name, COLLECT { + WITH 2018 AS yearOfTheDog + MATCH (person)-[r:HAS_DOG]->(d:Dog) + WHERE r.since = yearOfTheDog + RETURN d.name +} as dogsOfTheYear +---- + +[role="queryresult",options="header,footer",cols="2*(d:Dog) + MATCH (d)-[:HAS_TOY]->(t:Toy) + RETURN t.name + } as toyNames +---- + +[role="queryresult",options="header,footer",cols="2*(d:Dog) RETURN d.name } +RETURN person.dogNames as dogNames +---- + +[role="queryresult",options="header,footer",cols="1*(d:Dog) RETURN d.name } = [] THEN "No Dogs " + person.name + ELSE person.name + END AS result +---- + +[role="queryresult",options="header,footer",cols="1*(d:Dog) RETURN d.name } AS dogNames, + avg(person.age) AS averageAge + ORDER BY dogNames +---- + +[role="queryresult",options="header,footer",cols="2*(:Dog {name:'Andy'}), +(timothy)-[:HAS_CAT {since: 2019}]->(:Cat {name:'Mittens'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) +---- + +[[count-simple]] +== Simple `COUNT` subquery + +Variables introduced by the outside scope can be used in the `COUNT` subquery without importing them. +In this regard, `COUNT` subqueries are different from `CALL` subqueries, xref::subqueries/call-subquery.adoc#call-importing-variables[which do require importing]. +The following query exemplifies this and outputs the owners of more than one dog: + + +[source, cypher] +---- +MATCH (person:Person) +WHERE COUNT { (person)-[:HAS_DOG]->(:Dog) } > 1 +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(dog:Dog) + WHERE person.name = dog.name +} = 1 +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(dog:Dog) + RETURN dog.name AS petName + UNION + MATCH (person)-[:HAS_CAT]->(cat:Cat) + RETURN cat.name AS petName + } AS numPets +---- + +[role="queryresult",options="header,footer",cols="2*(d:Dog) + WHERE d.name = name +} = 1 +RETURN person.name AS name +---- + +.Error message +[source, output, role="noheader"] +---- +The variable `name` is shadowing a variable with the same name from the outer scope and needs to be renamed (line 4, column 20 (offset: 90)) +---- + +New variables can be introduced into the subquery, as long as they use a different identifier. +In the example below, a `WITH` clause introduces a new variable. +Note that the outer scope variable `person` referenced in the main query is still available after the `WITH` clause. + +[source, cypher] +---- +MATCH (person:Person) +WHERE COUNT { + WITH "Ozzy" AS dogName + MATCH (person)-[:HAS_DOG]->(d:Dog) + WHERE d.name = dogName +} = 1 +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(:Dog) } as howManyDogs + +---- + +[role="queryresult",options="header,footer",cols="2*(:Dog) } +RETURN person.howManyDogs as howManyDogs + +---- + +[role="queryresult",options="header,footer",cols="1*(:Dog) } > 1 THEN "Doglover " + person.name + ELSE person.name + END AS result + +---- + +[role="queryresult",options="header,footer",cols="1*(:Dog) } AS numDogs, + avg(person.age) AS averageAge + ORDER BY numDogs + +---- + +[role="queryresult",options="header,footer",cols="2*(:Dog) + RETURN person.name +} = 1 +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(:Dog {name:'Andy'}), +(timothy)-[:HAS_CAT {since: 2019}]->(:Cat {name:'Mittens'}), +(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), +(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) +---- + +[[existential-simple]] +== Simple `EXISTS` subquery + +Variables introduced by the outside scope can be used in the `EXISTS` subquery without importing them. +In this regard, `EXISTS` subqueries are different from `CALL` subqueries, xref::subqueries/call-subquery.adoc#call-importing-variables[which do require importing]. +The following example shows this: + + +[source, cypher] +---- +MATCH (person:Person) +WHERE EXISTS { + (person)-[:HAS_DOG]->(:Dog) +} +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(dog:Dog) + WHERE person.name = dog.name +} +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(dog:Dog) + WHERE EXISTS { + MATCH (dog)-[:HAS_TOY]->(toy:Toy) + WHERE toy.name = 'Banana' + } +} +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(:Dog) +} AS hasDog +---- + +[role="queryresult",options="header,footer",cols="2*(:Dog) + UNION + MATCH (person)-[:HAS_CAT]->(:Cat) + } AS hasPet +---- + +[role="queryresult",options="header,footer",cols="2*(d:Dog) + WHERE d.name = name +} +RETURN person.name AS name +---- + +.Error message +[source, output, role="noheader"] +---- +The variable `name` is shadowing a variable with the same name from the outer scope and needs to be renamed (line 4, column 20 (offset: 90)) +---- + +New variables can be introduced into the subquery, as long as they use a different identifier. +In the example below, a `WITH` clause introduces a new variable. +Note that the outer scope variable `person` referenced in the main query is still available after the `WITH` clause. + +[source, cypher] +---- +MATCH (person:Person) +WHERE EXISTS { + WITH "Ozzy" AS dogName + MATCH (person)-[:HAS_DOG]->(d:Dog) + WHERE d.name = dogName +} +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1*(:Dog) + RETURN person.name +} +RETURN person.name AS name +---- + +[role="queryresult",options="header,footer",cols="1* 100 +CALL { + WITH n + DETACH DELETE n +} IN TRANSACTIONS +---- + +.Result +[role="queryresult",options="footer",cols="1* 2 + RETURN l AS largeLists +} +RETURN largeLists +---- + +.Error message +[source, error] +---- +Importing WITH should consist only of simple references to outside variables. +WHERE is not allowed. +---- + +A solution to this restriction, necessary for any filtering or ordering of an importing `WITH` clause, is to declare a second `WITH` clause after the importing `WITH` clause. +This second `WITH` clause will act as a regular `WITH` clause. +For example, the following query will not throw an error: + +.Query +[source, cypher] +---- +UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l +CALL { + WITH l + WITH size(l) AS size, l AS l + WHERE size > 2 + RETURN l AS largeLists +} +RETURN largeLists +---- + +.Result +[role="queryresult",options="header,footer",cols="1*(b)+`, `+(a)-[r]-(b)+`, `+(a)--(b)+`, `+(a)-->()<--(b)+`. -* An operator application: `1 + 2`, `3 < 4`. -* A predicate expression is an expression that returns `true` or `false`: `a.prop = 'Hello'`, `length(p) > 10`, `a.name IS NOT NULL`. -* A special case of predicates are label and relationship type expressions: `(n:A|B)`, `()-[r:R1|R2]->()`. -* A subquery expression. For example: -`EXISTS { - MATCH (n)-[r]->(p) - WHERE p.name = 'Sven' -}`. -* A regular expression: `a.name =~ 'Tim.*'`. -* A case-sensitive string matching expression: `a.surname STARTS WITH 'Sven'`, `a.surname ENDS WITH 'son'` or `a.surname CONTAINS 'son'`. -* A `CASE` expression. - - -[[cypher-expressions-string-literals]] -== Note on string literals - -String literals can contain the following escape sequences: - -[options="header", cols=">1,<2"] -|=================== -|Escape sequence|Character -|`\t`|Tab -|`\b`|Backspace -|`\n`|Newline -|`\r`|Carriage return -|`\f`|Form feed -|`\'`|Single quote -|`\"`|Double quote -|`\\`|Backslash -|`\uxxxx`|Unicode UTF-16 code point (4 hex digits must follow the `\u`) -|=================== - -[NOTE] -==== -Using regular expressions with unsanitized user input makes you vulnerable to Cypher injection. -Consider using xref:syntax/parameters.adoc[parameters] instead. -==== - -[[cypher-expressions-number-literals]] -== Note on number literals - -Any number literal may contain an underscore `_` between digits. -There may be an underscore between the `0x` or `0o` and the digits for hexadecimal and octal literals. - -[[query-syntax-case]] -== `CASE` expressions - -Generic conditional expressions may be expressed using the `CASE` construct. -Two variants of `CASE` exist within Cypher: the simple form, which allows an expression to be compared against multiple values, and the generic form, which allows multiple conditional statements to be expressed. - -[NOTE] -==== -CASE can only be used as part of RETURN or WITH if you want to use the result in the succeeding clause or statement. -==== - -The following graph is used for the examples below: - -//// -[source, cypher, role=test-setup] ----- -CREATE - (alice:A {name:'Alice', age: 38, eyes: 'brown'}), - (bob:B {name: 'Bob', age: 25, eyes: 'blue'}), - (charlie:C {name: 'Charlie', age: 53, eyes: 'green'}), - (daniel:D {name: 'Daniel', eyes: 'brown'}), - (eskil:E {name: 'Eskil', age: 41, eyes: 'blue', array: ['one', 'two', 'three']}), - (alice)-[:KNOWS]->(bob), - (alice)-[:KNOWS]->(charlie), - (bob)-[:KNOWS]->(daniel), - (charlie)-[:KNOWS]->(daniel), - (bob)-[:MARRIED]->(eskil) ----- -//// - -image:graph3.svg[] - -[[syntax-simple-case]] -=== Simple `CASE` form: comparing an expression against multiple values - -The expression is calculated, and compared in order with the `WHEN` clauses until a match is found. -If no match is found, the expression in the `ELSE` clause is returned. -However, if there is no `ELSE` case and no match is found, `null` will be returned. - - -[source, syntax] ----- -CASE test - WHEN value THEN result - [WHEN ...] - [ELSE default] -END ----- - - -*Arguments:* -[options="header"] -|=== -| Name | Description - -| `test` -| A valid expression. - -| `value` -| An expression whose result will be compared to `test`. - -| `result` -| This is the expression returned as output if `value` matches `test`. - -| `default` -| If no match is found, `default` is returned. -|=== - - -[source, cypher] ----- -MATCH (n) -RETURN -CASE n.eyes - WHEN 'blue' THEN 1 - WHEN 'brown' THEN 2 - ELSE 3 -END AS result ----- - -[role="queryresult",options="header,footer",cols="1*+ -| +"Eskil"+ | +31+ -2+d|Rows: 5 -|=== - -The corrected query, behaving as expected, is given by the following generic `CASE` form: - -[source, cypher] ----- -MATCH (n) -RETURN n.name, -CASE - WHEN n.age IS NULL THEN -1 - ELSE n.age - 10 -END AS age_10_years_ago ----- - -We now see that the `age_10_years_ago` correctly returns `-1` for the node named `Daniel`. - -[role="queryresult",options="header,footer",cols="2*+ -| +"Eskil"+ | +31+ -2+d|Rows: 5 -|=== - -====== - - -[[cypher-subquery-expressions]] -== Subquery expressions - -Subquery expressions can appear anywhere that an expression is valid. -A subquery has a scope, as indicated by the opening and closing braces, `{` and `}`. -Any variable that is defined in the outside scope can be referenced inside the subquery's own scope. -Variables introduced inside the subquery are not part of the outside scope and therefore can't be accessed on the outside. - - -The following graph is used for the examples below: - -//// -[source, cypher, role=test-setup] ----- -MATCH (n:A|B|C|D|E) DETACH DELETE n; -CREATE -(andy:Swedish:Person {name: 'Andy', age: 36}), -(timothy:Person {name: 'Timothy', nickname: 'Tim', age: 25}), -(peter:Person {name: 'Peter', nickname: 'Pete', age: 35}), -(andy)-[:HAS_DOG {since: 2016}]->(:Dog {name:'Andy'}), -(timothy)-[:HAS_CAT {since: 2019}]->(:Cat {name:'Mittens'}), -(fido:Dog {name:'Fido'})<-[:HAS_DOG {since: 2010}]-(peter)-[:HAS_DOG {since: 2018}]->(:Dog {name:'Ozzy'}), -(fido)-[:HAS_TOY]->(:Toy{name:'Banana'}) ----- -//// - -image:graph_expression_subqueries.svg[] - -[[existential-subqueries]] -=== `EXISTS` subqueries - -An `EXISTS` subquery can be used to find out if a specified pattern exists at least once in the data. -It serves the same purpose as a xref::clauses/where.adoc#filter-on-patterns[path pattern] but is more powerful because it allows you to use `MATCH` and `WHERE` clauses internally. -Moreover, it can appear in any expression position, unlike path patterns. -If the subquery evaluates to at least one row, the whole expression will become `true`. -This also means that the system only needs to evaluate if there is at least one row and can skip the rest of the work. - -Any non-writing query is allowed. `EXISTS` subqueries differ from regular queries in that the final `RETURN` clause may be omitted, -as any variable defined within the subquery will not be available outside of the expression, even if a final `RETURN` clause is used. - -It is worth noting that the `MATCH` keyword can be omitted in subqueries in cases where the `EXISTS` consists of only -a pattern and an optional `WHERE` clause. - -[[existential-subquery-simple-case]] -==== Simple `EXISTS` subquery - -Variables introduced by the outside scope can be used in the `EXISTS` subquery without importing them. -In this regard, `EXISTS` subqueries are different from `CALL` subqueries, xref::clauses/call-subquery.adoc#subquery-correlated-importing[which do require importing]. -The following example shows this: - - -[source, cypher] ----- -MATCH (person:Person) -WHERE EXISTS { - (person)-[:HAS_DOG]->(:Dog) -} -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(dog:Dog) - WHERE person.name = dog.name -} -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(dog:Dog) - WHERE EXISTS { - MATCH (dog)-[:HAS_TOY]->(toy:Toy) - WHERE toy.name = 'Banana' - } -} -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(:Dog) -} AS hasDog ----- - -[role="queryresult",options="header,footer",cols="2*(:Dog) - UNION - MATCH (person)-[:HAS_CAT]->(:Cat) - } AS hasPet ----- - -[role="queryresult",options="header,footer",cols="2*(d:Dog) - WHERE d.name = name -} -RETURN person.name AS name ----- - -.Error message -[source, output, role="noheader"] ----- -The variable `name` is shadowing a variable with the same name from the outer scope and needs to be renamed (line 4, column 20 (offset: 90)) ----- - -New variables can be introduced into the subquery, as long as they use a different identifier. -In the example below, a `WITH` clause introduces a new variable. -Note that the outer scope variable `person` referenced in the main query is still available after the `WITH` clause. - -[source, cypher] ----- -MATCH (person:Person) -WHERE EXISTS { - WITH "Ozzy" AS dogName - MATCH (person)-[:HAS_DOG]->(d:Dog) - WHERE d.name = dogName -} -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(:Dog) - RETURN person.name -} -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(:Dog) } > 1 -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(dog:Dog) - WHERE person.name = dog.name -} = 1 -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(dog:Dog) - RETURN dog.name AS petName - UNION - MATCH (person)-[:HAS_CAT]->(cat:Cat) - RETURN cat.name AS petName - } AS numPets ----- - -[role="queryresult",options="header,footer",cols="2*(d:Dog) - WHERE d.name = name -} = 1 -RETURN person.name AS name ----- - -.Error message -[source, output, role="noheader"] ----- -The variable `name` is shadowing a variable with the same name from the outer scope and needs to be renamed (line 4, column 20 (offset: 90)) ----- - -New variables can be introduced into the subquery, as long as they use a different identifier. -In the example below, a `WITH` clause introduces a new variable. -Note that the outer scope variable `person` referenced in the main query is still available after the `WITH` clause. - -[source, cypher] ----- -MATCH (person:Person) -WHERE COUNT { - WITH "Ozzy" AS dogName - MATCH (person)-[:HAS_DOG]->(d:Dog) - WHERE d.name = dogName -} = 1 -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(:Dog) } as howManyDogs - ----- - -[role="queryresult",options="header,footer",cols="2*(:Dog) } -RETURN person.howManyDogs as howManyDogs - ----- - -[role="queryresult",options="header,footer",cols="1*(:Dog) } > 1 THEN "Doglover " + person.name - ELSE person.name - END AS result - ----- - -[role="queryresult",options="header,footer",cols="1*(:Dog) } AS numDogs, - avg(person.age) AS averageAge - ORDER BY numDogs - ----- - -[role="queryresult",options="header,footer",cols="2*(:Dog) - RETURN person.name -} = 1 -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(dog:Dog) RETURN dog.name } -RETURN person.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(dog:Dog) - WHERE r.since > 2017 - RETURN dog.name -} as youngDogs ----- - -[role="queryresult",options="header,footer",cols="2*(dog:Dog) - RETURN dog.name AS petName - UNION - MATCH (person)-[:HAS_CAT]->(cat:Cat) - RETURN cat.name AS petName - } AS petNames ----- - -[role="queryresult",options="header,footer",cols="2*(d:Dog {name: name}) - RETURN d.name -} as dogsOfTheYear ----- - -.Error message -[source, output, role="noheader"] ----- -The variable `name` is shadowing a variable with the same name from the outer scope and needs to be renamed (line 4, column 20 (offset: 92)) ----- - -New variables can be introduced into the subquery, as long as they use a different identifier. -In the example below, a `WITH` clause introduces a new variable. -Note that the outer scope variable `person` referenced in the main query is still available after the `WITH` clause. - -[source, cypher] ----- -MATCH (person:Person) -RETURN person.name AS name, COLLECT { - WITH 2018 AS yearOfTheDog - MATCH (person)-[r:HAS_DOG]->(d:Dog) - WHERE r.since = yearOfTheDog - RETURN d.name -} as dogsOfTheYear ----- - -[role="queryresult",options="header,footer",cols="2*(d:Dog) - MATCH (d)-[:HAS_TOY]->(t:Toy) - RETURN t.name - } as toyNames ----- - -[role="queryresult",options="header,footer",cols="2*(d:Dog) RETURN d.name } -RETURN person.dogNames as dogNames ----- - -[role="queryresult",options="header,footer",cols="1*(d:Dog) RETURN d.name } = [] THEN "No Dogs " + person.name - ELSE person.name - END AS result ----- - -[role="queryresult",options="header,footer",cols="1*(d:Dog) RETURN d.name } AS dogNames, - avg(person.age) AS averageAge - ORDER BY dogNames ----- - -[role="queryresult",options="header,footer",cols="2* IS :: ----- - -Where `` is any Cypher expression and `` is a Cypher Type. -For all available Cypher types, see the section on xref::values-and-types/property-structural-constructed.adoc#types-synonyms[types and their synonyms]. - -[source, cypher] ----- -UNWIND [42, true, 'abc'] AS val -RETURN val, val IS :: INTEGER AS isInteger ----- - -[role="queryresult",options="header,footer",cols="2* 18 -RETURN n.name AS name, n.age AS age ----- - -[role="queryresult",options="header,footer",cols="2* IS TYPED ----- - -[source, syntax, role="noheader", indent=0] ----- - :: ----- - -For verifying that an expression is not of a certain type, the following alternative syntax is supported: - -[source, syntax, role="noheader", indent=0] ----- - IS NOT TYPED ----- - -[[type-predicate-expressions-any-and-nothing]] -=== Use of `ANY` and `NOTHING` types - -_This feature was introduced in Neo4j 5.10._ - -`ANY` is a supertype which matches values of all types. -`NOTHING` is a type containing an empty set of values. -This means that it returns `false` for all values. - -[source, cypher] ----- -RETURN 42 IS :: ANY AS isOfTypeAny, 42 IS :: NOTHING AS isOfTypeNothing ----- - -[role="queryresult",options="header,footer",cols="2* AS isIntList ----- - -[role="queryresult",options="header,footer",cols="2* AS isNothingList, - [] IS :: LIST AS isIntList, - [] IS :: LIST AS isFloatNotNullList ----- - -[role="queryresult",options="header,footer",cols="3* as isMixedList ----- - -[role="queryresult",options="header,footer",cols="1*>. - -Label expressions evaluate to `true` or `false` when applied to the set of labels for a node. - -Assuming no other filters are applied, then a label expression evaluating to `true` means the node is matched. - -The following table displays whether the label expression matches the relationship: - -.Label expression matches -[cols="^3,^2,^2,^2,^2,^2,^2,^2,^2"] -|=== -| -8+^|*Node* - -|*Label expression* | `()` | `(:A)` | `(:B)` | `(:C)` | `(:A:B)` | `(:A:C)` | `(:B:C)` | `(:A:B:C)` -| `()` -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `(:A)` -| -| {check-mark} -| -| -| {check-mark} -| {check-mark} -| -| {check-mark} - -| `(:A&B)` -| -| -| -| -| {check-mark} -| -| -| {check-mark} - -| `(:A\|B)` -| -| {check-mark} -| {check-mark} -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `(:!A)` -| {check-mark} -| -| {check-mark} -| {check-mark} -| -| -| {check-mark} -| - -| `(:!!A)` -| -| {check-mark} -| -| -| {check-mark} -| {check-mark} -| -| {check-mark} - -| `(:A&!A)` -| -| -| -| -| -| -| -| - -| `(:A\|!A)` -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `(:%)` -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `(:!%)` -| {check-mark} -| -| -| -| -| -| -| - -| `(:%\|!%)` -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `(:%&!%)` -| -| -| -| -| -| -| -| - -| `(:A&%)` -| -| {check-mark} -| -| -| {check-mark} -| {check-mark} -| -| {check-mark} - -| `(:A\|%)` -| -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} -| {check-mark} - -| `(:(A&B)&!(B&C))` -| -| -| -| -| {check-mark} -| -| -| - -| `(:!(A&%)&%)` -| -| -| {check-mark} -| {check-mark} -| -| -| {check-mark} -| - -|=== - - -[[syntax-restrictions-label]] -=== Restrictions on using the different types of label expression syntax - -Neo4j 5.0 introduced an ampersand operator, which is equivalent to the colon conjunction operator. -Mixing the colon conjunction operator with any of the new label expression operators in the same clause will raise a syntax error. - -For example, each of the following clauses will raise syntax errors: - -* `MATCH (n:A|B:C)` -* `MATCH (n:A:B)-[]-(m:(A&B)|C)` -* `MATCH (n:A:B)--(m), (n)-->(o:(A&B)|C)` -* `RETURN n:A&B, n:A:B` -* `MATCH (n:A:B)-[]-(m) WHERE m:(A&B)|C` - -In earlier versions of Neo4j (4.4 and earlier), relationship type expressions only had the pipe operator. -As the pipe operator will continue to act as an `OR` operator, it can continue to be used alongside the new operators. - -To make it easier to use the new syntax when extending existing queries, using the different syntax types in separate clauses will be supported. - -For example, the following query will not raise a syntax error: - -[source, cypher, role=noplay] ----- -MATCH (m:A:B:C)-[]->() -MATCH (n:(A&B)|C)-[]->(m) -RETURN m,n ----- - -Queries that exclusively use syntax from earlier versions of Neo4j (4.4 and earlier) will continue to be supported. - -For example, the following will not raise a syntax error: - -[source, cypher, role=noplay] ----- -MATCH (m:A:B:C)-[:S|T]->() -RETURN - CASE - WHEN m:D:E THEN m.p - ELSE null - END AS result ----- - -[[label-expressions-examples]] -==== Examples - -The following graph is used for the examples below: - -//// -[source, cypher, role=test-setup] ----- -MATCH (n:Toy|Cat|Dog|Person|Swedish) DETACH DELETE n; - -CREATE - (:A {name:'Alice'}), - (:B {name:'Bob'}), - (:C {name:'Charlie'}), - (:A:B {name:'Daniel'}), - (:A:C {name:'Eskil'}), - (:B:C {name:'Frank'}), - (:A:B:C {name:'George'}), - ({name:'Henry'}) ----- -//// - -image:graph_label_expressions.svg[] - -* xref:syntax/expressions.adoc#label-expressions-node-pattern-without-label-expressions[] -* xref:syntax/expressions.adoc#label-expressions-node-pattern-with-single-node-label[] -* xref:syntax/expressions.adoc#label-expressions-node-pattern-with-and-expression[] -* xref:syntax/expressions.adoc#label-expressions-note-pattern-with-or-expression[] -* xref:syntax/expressions.adoc#label-expressions-node-pattern-with-not-expressions[] -* xref:syntax/expressions.adoc#label-expressions-node-pattern-with-wildcard-expression[] -* xref:syntax/expressions.adoc#label-expressions-node-pattern-with-nested-label-expressions[] -* xref:syntax/expressions.adoc#label-expressions-where-clause-with-label-expression-as-predicate[] -* xref:syntax/expressions.adoc#label-expressions-with-return-clauses[] - - -[discrete] -[[label-expressions-node-pattern-without-label-expressions]] -=== Node pattern without label expressions - -A node pattern without a label expression returns all nodes in the graph, including nodes without labels. - -.+Label expression+ -====== - -[source, cypher] ----- -MATCH (n) -RETURN n.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(:B), - (:C)-[:R2 {name:'Studies'}]->(:D), - (:E)-[:R3 {name:'Parents'}]->(:F) ----- -//// - -[discrete] -[[relationship-type-expressions-pattern-without-relationship-type-expression]] -=== Relationship pattern without relationship type expression - -A relationship pattern without a relationship type expression returns all relationships in the graph. - - -.Relationship type expressions -====== - -[source, cypher] ----- -MATCH ()-[r]->() -RETURN r.name as name ----- - -[role="queryresult",options="header,footer",cols="1*() -RETURN r.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*() -RETURN r.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*() -RETURN r.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*() -RETURN r.name as name ----- - -[role="queryresult",options="header,footer",cols="1*(m) -WHERE r:R1|R2 -RETURN r.name AS name ----- - -[role="queryresult",options="header,footer",cols="1*(m) -RETURN r:R1|R2 AS result ----- - -[role="queryresult",options="header,footer",cols="1*(m) -RETURN -CASE - WHEN n:A&B THEN 1 - WHEN r:!R1&!R2 THEN 2 - ELSE -1 -END AS result ----- - -[role="queryresult",options="header,footer",cols="1* b` and `1 < b` are both false when b is NaN. *** xref::values-and-types/temporal.adoc[`ZONED TIME`] *** xref::values-and-types/temporal.adoc[`LOCAL TIME`] *** xref::values-and-types/temporal.adoc[`DURATION`] - *** xref::syntax/expressions.adoc#cypher-expressions-general[`STRING`] - *** xref::syntax/expressions.adoc#cypher-expressions-general[`BOOLEAN`] - *** Numbers: xref::syntax/expressions.adoc#cypher-expressions-general[`INTEGER`, `FLOAT`] + *** xref::queries/expressions.adoc#string[`STRING`] + *** xref::queries/expressions.adoc#boolean[`BOOLEAN`] + *** Numbers: xref::queries/expressions.adoc#numerical[`INTEGER`, `FLOAT`] ** The value `null` is ordered after all other values. * *Ordering* of constructed type values: ** For the xref::values-and-types/property-structural-constructed.adoc#constructed-types[constructed types] (e.g. maps and lists), elements of the containers are compared pairwise for ordering and thus determine the ordering of two container types. diff --git a/modules/ROOT/pages/syntax/parsing.adoc b/modules/ROOT/pages/syntax/parsing.adoc index f875406a8..492146f48 100644 --- a/modules/ROOT/pages/syntax/parsing.adoc +++ b/modules/ROOT/pages/syntax/parsing.adoc @@ -16,7 +16,7 @@ Unicodes can generally be escaped as `\uxxx`. Additional documentation on escaping rules for string literals, names and regular expressions can be found here: -* xref::syntax/expressions.adoc#cypher-expressions-string-literals[String literal escape sequences] +* xref::queries/expressions.adoc#expressions-string-literals[String literal escape sequences] * xref::syntax/naming.adoc#symbolic-names-escaping-rules[Using special characters in names] * xref::clauses/where.adoc#escaping-in-regular-expressions[Regular epxressions] diff --git a/modules/ROOT/pages/values-and-types/index.adoc b/modules/ROOT/pages/values-and-types/index.adoc index 4978093d5..8f429f49c 100644 --- a/modules/ROOT/pages/values-and-types/index.adoc +++ b/modules/ROOT/pages/values-and-types/index.adoc @@ -13,4 +13,5 @@ More information about the data values and types supported by Cypher can be foun * xref::values-and-types/working-with-null.adoc[] * xref::values-and-types/lists.adoc[] * xref::values-and-types/maps.adoc[] -* xref::values-and-types/casting-data.adoc[] \ No newline at end of file +* xref::values-and-types/casting-data.adoc[] +* xref::values-and-types/type-predicate.adoc[] \ No newline at end of file diff --git a/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc b/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc index 6104d20cf..982f78a57 100644 --- a/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc +++ b/modules/ROOT/pages/values-and-types/property-structural-constructed.adoc @@ -17,7 +17,7 @@ The following data types are included in the property types category: `BOOLEAN`, * Property types can be returned from Cypher queries. * Property types can be used as xref::syntax/parameters.adoc[parameters]. * Property types can be stored as properties. -* Property types can be constructed with xref::syntax/expressions.adoc[Cypher literals]. +* Property types can be constructed with xref::queries/expressions.adoc[Cypher literals]. Homogeneous lists of simple types can be stored as properties, although lists in general (see xref::values-and-types/property-structural-constructed.adoc#constructed-types[Constructed types]) cannot be stored as properties. Lists stored as properties cannot contain `null` values. @@ -35,7 +35,7 @@ The following data types are included in the structural types category: `NODE`, * Structural types can be returned from Cypher queries. * Structural types cannot be used as xref::syntax/parameters.adoc[parameters]. * Structural types cannot be stored as properties. -* Structural types cannot be constructed with xref::syntax/expressions.adoc[Cypher literals]. +* Structural types cannot be constructed with xref::queries/expressions.adoc[Cypher literals]. The `NODE` data type includes: id, label(s), and a map of properties. Note that labels are not values, but a form of pattern syntax. @@ -59,7 +59,7 @@ The following data types are included in the constructed types category: `LIST` * Constructed types can be returned from Cypher queries. * Constructed types can be used as xref::syntax/parameters.adoc[parameters]. * Constructed types cannot be stored as properties (with the exception of homogenous lists). -* Constructed types can be constructed with xref::syntax/expressions.adoc[Cypher literals]. +* Constructed types can be constructed with xref::queries/expressions.adoc[Cypher literals]. The `LIST` data type can be either a homogenous collection of simple values, or a heterogeneous, ordered collection of values, each of which can have any property, structural or constructed type. @@ -72,8 +72,10 @@ For more details, see xref::values-and-types/working-with-null.adoc[working with == Types and their synonyms The table below shows the types and their syntactic synonyms. + These types (and their synonyms) can be used in xref::syntax/expressions.adoc#type-predicate-expressions[type predicate expressions] and in xref::constraints/examples.adoc#constraints-examples-node-property-type[node] and xref::constraints/examples.adoc#constraints-examples-relationship-property-type[relationship] property type constraints. They are also returned as a `STRING` value when using the xref::functions/scalar.adoc#functions-valueType[valueType()] function. + However, not all types can be used in all places. [.synonyms, opts="header", cols="2a,2a"] diff --git a/modules/ROOT/pages/values-and-types/type-predicate.adoc b/modules/ROOT/pages/values-and-types/type-predicate.adoc new file mode 100644 index 000000000..f44c4f8a3 --- /dev/null +++ b/modules/ROOT/pages/values-and-types/type-predicate.adoc @@ -0,0 +1,305 @@ += Type predicate expressions +:description: This page describes how to use type predicate expressions with Cypher. + +_This feature was introduced in Neo4j 5.9._ + +A type predicate expression can be used to verify the type of a variable, literal, property or other Cypher expression. + +[[type-predicate-syntax]] +== Syntax + +[source, syntax] +---- + IS :: +---- + +Where `` is any Cypher expression and `` is a Cypher type. +For all available Cypher types, see the section on xref::values-and-types/property-structural-constructed.adoc#types-synonyms[types and their synonyms]. + +[[type-predicate-regular]] +== Verify the type of a Cypher expression + +[source, cypher] +---- +UNWIND [42, true, 'abc', null] AS val +RETURN val, val IS :: INTEGER AS isInteger +---- + +[role="queryresult",options="header,footer",cols="2* 18 +RETURN n.name AS name, n.age AS age +---- + +[role="queryresult",options="header,footer",cols="2* IS TYPED +---- + +[source, syntax, role="noheader", indent=0] +---- + :: +---- + +For verifying that an expression is not of a certain type, the following alternative syntax is supported: + +[source, syntax, role="noheader", indent=0] +---- + IS NOT TYPED +---- + +[[type-predicate-any-and-nothing]] +== Use of `ANY` and `NOTHING` types + +_This feature was introduced in Neo4j 5.10._ + +`ANY` is a supertype which matches values of all types. +`NOTHING` is a type containing an empty set of values. +This means that it returns `false` for all values. + +[source, cypher] +---- +RETURN 42 IS :: ANY AS isOfTypeAny, 42 IS :: NOTHING AS isOfTypeNothing +---- + +[role="queryresult",options="header,footer",cols="2* AS isIntList +---- + +[role="queryresult",options="header,footer",cols="2* AS isNothingList, + [] IS :: LIST AS isIntList, + [] IS :: LIST AS isFloatNotNullList +---- + +[role="queryresult",options="header,footer",cols="3* as isMixedList +---- + +[role="queryresult",options="header,footer",cols="1* Date: Wed, 20 Sep 2023 15:07:19 +0200 Subject: [PATCH 2/3] Update CypherTypes in procedure/function signatures (#676) We are updating the signatures in the output for SHOW FUNCTIONS and SHOW PROCEDURES to match the defaults of the new Cypher Types --- .../ROOT/pages/administration/databases.adoc | 6 +- modules/ROOT/pages/clauses/call.adoc | 2 +- ...ions-additions-removals-compatibility.adoc | 32 +- .../ROOT/pages/execution-plans/operators.adoc | 2 +- modules/ROOT/pages/functions/index.adoc | 306 +++++++++--------- .../pages/indexes-for-search-performance.adoc | 4 +- .../ROOT/pages/indexes-for-vector-search.adoc | 6 +- 7 files changed, 194 insertions(+), 164 deletions(-) diff --git a/modules/ROOT/pages/administration/databases.adoc b/modules/ROOT/pages/administration/databases.adoc index 9e1802114..8293f40c3 100644 --- a/modules/ROOT/pages/administration/databases.adoc +++ b/modules/ROOT/pages/administration/databases.adoc @@ -245,15 +245,15 @@ May be lower than current if the DBMS is currently reducing the number of copies | creationTime | The date and time at which the database was created. -| DATETIME +| ZONED DATETIME | lastStartTime | The date and time at which the database was last started. -| DATETIME +| ZONED DATETIME | lastStopTime | The date and time at which the database was last stopped. -| DATETIME +| ZONED DATETIME | store a| diff --git a/modules/ROOT/pages/clauses/call.adoc b/modules/ROOT/pages/clauses/call.adoc index 4e00f0628..cd205151d 100644 --- a/modules/ROOT/pages/clauses/call.adoc +++ b/modules/ROOT/pages/clauses/call.adoc @@ -138,7 +138,7 @@ The result shows that: [role="queryresult",options="header,footer",cols="1* WHERE predicate :: ANY) :: BOOLEAN` | Returns true if the predicate holds for all elements in the given `LIST`. 1.1+| xref::functions/predicate.adoc#functions-any[`any()`] -| `any(variable :: VARIABLE IN list :: LIST OF ANY? WHERE predicate :: ANY?) :: (BOOLEAN?)` +| `any(variable :: VARIABLE IN list :: LIST WHERE predicate :: ANY) :: BOOLEAN` | Returns true if the predicate holds for at least one element in the given `LIST`. 1.1+| xref::functions/predicate.adoc#functions-exists[`exists()`] -| `exists(input :: ANY?) :: (BOOLEAN?)` +| `exists(input :: ANY) :: BOOLEAN` | Returns `true` if a match for the pattern exists in the graph. 1.3+| xref::functions/predicate.adoc#functions-isempty[`isEmpty()`] -| `isEmpty(input :: LIST? OF ANY?) :: (BOOLEAN?)` +| `isEmpty(input :: LIST) :: BOOLEAN` | Checks whether a `LIST` is empty. -| `isEmpty(input :: MAP?) :: (BOOLEAN?)` +| `isEmpty(input :: MAP) :: BOOLEAN` | Checks whether a `MAP` is empty. -| `isEmpty(input :: STRING?) :: (BOOLEAN?)` +| `isEmpty(input :: STRING) :: BOOLEAN` | Checks whether a `STRING` is empty. 1.1+| xref::functions/predicate.adoc#functions-none[`none()`] -| `none(variable :: VARIABLE IN list :: LIST OF ANY? WHERE predicate :: ANY?) :: (BOOLEAN?)` +| `none(variable :: VARIABLE IN list :: LIST WHERE predicate :: ANY) :: BOOLEAN` | Returns true if the predicate holds for no element in the given `LIST`. 1.1+| xref::functions/predicate.adoc#functions-single[`single()`] -| `single(variable :: VARIABLE IN list :: LIST OF ANY? WHERE predicate :: ANY?) :: (BOOLEAN?)` +| `single(variable :: VARIABLE IN list :: LIST WHERE predicate :: ANY) :: BOOLEAN` | Returns true if the predicate holds for exactly one of the elements in the given `LIST`. |=== @@ -95,93 +95,93 @@ These functions return a single value. | Function | Signature | Description 1.1+| xref::functions/scalar.adoc#functions-coalesce[`coalesce()`] -| `coalesce(input :: ANY?) :: (ANY?)` +| `coalesce(input :: ANY) :: ANY` | Returns the first non-null value in a list of expressions. 1.1+| xref::functions/scalar.adoc#functions-endnode[`endNode()`] -| `endNode(input :: RELATIONSHIP?) :: (NODE?)` +| `endNode(input :: RELATIONSHIP) :: NODE` | Returns the end `NODE` of a `RELATIONSHIP`. 1.1+| xref::functions/scalar.adoc#functions-head[`head()`] -| `head(list :: LIST? OF ANY?) :: (ANY?)` +| `head(list :: LIST) :: ANY` | Returns the first element in a `LIST`. 1.2+| xref::functions/scalar.adoc#functions-id[`id()`] -| `id(input :: NODE?) :: (INTEGER?)` +| `id(input :: NODE) :: INTEGER` | label:deprecated[] Returns the id of a `NODE`. Replaced by `elementId()` -| `id(input :: RELATIONSHIP?) :: (INTEGER?)` +| `id(input :: RELATIONSHIP) :: INTEGER` | label:deprecated[] Returns the id of a `RELATIONSHIP`. Replaced by `elementId()`. 1.1+| xref::functions/scalar.adoc#functions-last[`last()`] -| `last(list :: LIST? OF ANY?) :: (ANY?)` +| `last(list :: LIST) :: ANY` | Returns the last element in a `LIST`. 1.1+| xref::functions/scalar.adoc#functions-length[`length()`] -| `length(input :: PATH?) :: (INTEGER?)` +| `length(input :: PATH) :: INTEGER` | Returns the length of a `PATH`. 1.3+| xref::functions/scalar.adoc#functions-properties[`properties()`] -| `properties(input :: MAP?) :: (MAP?)` +| `properties(input :: MAP) :: MAP` | Returns a `MAP` containing all the properties of a `MAP`. -| `properties(input :: NODE?) :: (MAP?)` +| `properties(input :: NODE) :: MAP` | Returns a `MAP` containing all the properties of a `NODE`. -| `properties(input :: RELATIONSHIP?) :: (MAP?)` +| `properties(input :: RELATIONSHIP) :: MAP` | Returns a `MAP` containing all the properties of a `RELATIONSHIP`. 1.1+| xref::functions/scalar.adoc#functions-randomuuid[`randomUUID()`] -| `randomUUID() :: (STRING?)` +| `randomUUID() :: STRING` | Generates a random UUID. 1.2+| xref::functions/scalar.adoc#functions-size[`size()`] -| `size(input :: LIST? OF ANY?) :: (INTEGER?)` +| `size(input :: LIST) :: INTEGER` | Returns the number of items in a `LIST`. -| `size(input :: STRING?) :: (INTEGER?)` +| `size(input :: STRING) :: INTEGER` | Returns the number of Unicode characters in a `STRING`. 1.1+| xref::functions/scalar.adoc#functions-startnode[`startNode()`] -| `startNode(input :: RELATIONSHIP?) :: (NODE?)` +| `startNode(input :: RELATIONSHIP) :: NODE` | Returns the start `NODE` of a `RELATIONSHIP`. 1.3+| xref::functions/scalar.adoc#functions-toboolean[`toBoolean()`] -| `toBoolean(input :: STRING?) :: (BOOLEAN?)` +| `toBoolean(input :: STRING) :: BOOLEAN` | Converts a `STRING` value to a `BOOLEAN` value. -| `toBoolean(input :: BOOLEAN?) :: (BOOLEAN?)` +| `toBoolean(input :: BOOLEAN) :: BOOLEAN` | Converts a `BOOLEAN` value to a `BOOLEAN` value. -| `toBoolean(input :: INTEGER?) :: (BOOLEAN?)` +| `toBoolean(input :: INTEGER) :: BOOLEAN` | Converts an `INTEGER` value to a `BOOLEAN` value. 1.1+| xref::functions/scalar.adoc#functions-tobooleanornull[`toBooleanOrNull()`] -| `toBooleanOrNull(input :: ANY?) :: (BOOLEAN?)` +| `toBooleanOrNull(input :: ANY) :: BOOLEAN` | Converts a value to a `BOOLEAN` value, or null if the value cannot be converted. 1.2+| xref::functions/scalar.adoc#functions-tofloat[`toFloat()`] -| `toFloat(input :: NUMBER?) :: (FLOAT?)` +| `toFloat(input :: INTEGER | FLOAT) :: FLOAT` | Converts an `INTEGER` value to a `FLOAT` value. -| `toFloat(input :: STRING?) :: (FLOAT?)` +| `toFloat(input :: STRING) :: FLOAT` | Converts a `STRING` value to a `FLOAT` value. 1.1+| xref::functions/scalar.adoc#functions-tofloatornull[`toFloatOrNull()`] -| `toFloatOrNull(input :: ANY?) :: (FLOAT?)` +| `toFloatOrNull(input :: ANY) :: FLOAT` | Converts a value to a `FLOAT` value, or null if the value cannot be converted. 1.3+| xref::functions/scalar.adoc#functions-tointeger[`toInteger()`] -| `toInteger(input :: NUMBER?) :: (INTEGER?)` +| `toInteger(input :: INTEGER | FLOAT) :: INTEGER` | Converts a `FLOAT` value to an `INTEGER` value. -| `toInteger(input :: BOOLEAN?) :: (INTEGER?)` +| `toInteger(input :: BOOLEAN) :: INTEGER` | Converts a `BOOLEAN` value to an `INTEGER` value. -| `toInteger(input :: STRING?) :: (INTEGER?)` +| `toInteger(input :: STRING) :: INTEGER` | Converts a `STRING` value to an `INTEGER` value. 1.1+| xref::functions/scalar.adoc#functions-tointegerornull[`toIntegerOrNull()`] -| `toIntegerOrNull(input :: ANY?) :: (INTEGER?)` +| `toIntegerOrNull(input :: ANY) :: INTEGER` | Converts a value to an `INTEGER` value, or null if the value cannot be converted. 1.1+| xref::functions/scalar.adoc#functions-type[`type()`] -| `type(input :: RELATIONSHIP?) :: (STRING?)` +| `type(input :: RELATIONSHIP) :: STRING` | Returns a `STRING` representation of the `RELATIONSHIP` type. 1.1+| xref::functions/scalar.adoc#functions-valueType[`valueType()`] -| `valueType(input :: ANY?) :: (STRING?)` +| `valueType(input :: ANY) :: STRING` | Returns a `STRING` representation of the most precise value type that the given expression evaluates to. |=== @@ -197,53 +197,53 @@ These functions take multiple values as arguments, and calculate and return an a | Function | Signature | Description 1.3+| xref::functions/aggregating.adoc#functions-avg[`avg()`] -| `avg(input :: DURATION?) :: (DURATION?)` +| `avg(input :: DURATION) :: DURATION` | Returns the average of a set of `DURATION` values. -| `avg(input :: FLOAT?) :: (FLOAT?)` +| `avg(input :: FLOAT) :: FLOAT` | Returns the average of a set of `FLOAT` values. -| `avg(input :: INTEGER?) :: (INTEGER?)` +| `avg(input :: INTEGER) :: INTEGER` | Returns the average of a set of `INTEGER` values. 1.1+| xref::functions/aggregating.adoc#functions-collect[`collect()`] -| `collect(input :: ANY?) :: (LIST? OF ANY?)` +| `collect(input :: ANY) :: LIST` | Returns a list containing the values returned by an expression. 1.1+| xref::functions/aggregating.adoc#functions-count[`count()`] -| `count(input :: ANY?) :: (INTEGER?)` +| `count(input :: ANY) :: INTEGER` | Returns the number of values or rows. 1.1+| xref::functions/aggregating.adoc#functions-max[`max()`] -| `max(input :: ANY?) :: (ANY?)` +| `max(input :: ANY) :: ANY` | Returns the maximum value in a set of values. 1.1+| xref::functions/aggregating.adoc#functions-min[`min()`] -| `min(input :: ANY?) :: (ANY?)` +| `min(input :: ANY) :: ANY` | Returns the minimum value in a set of values. 1.1+| xref::functions/aggregating.adoc#functions-percentilecont[`percentileCont()`] -| `percentileCont(input :: FLOAT?, percentile :: FLOAT?) :: (FLOAT?)` +| `percentileCont(input :: FLOAT, percentile :: FLOAT) :: FLOAT` | Returns the percentile of a value over a group using linear interpolation. 1.2+| xref::functions/aggregating.adoc#functions-percentiledisc[`percentileDisc()`] -| `percentileDisc(input :: FLOAT?, percentile :: FLOAT?) :: (FLOAT?)` +| `percentileDisc(input :: FLOAT, percentile :: FLOAT) :: FLOAT` | Returns the nearest `FLOAT` value to the given percentile over a group using a rounding method. -| `percentileDisc(input :: INTEGER?, percentile :: FLOAT?) :: (INTEGER?)` +| `percentileDisc(input :: INTEGER, percentile :: FLOAT) :: INTEGER` | Returns the nearest `INTEGER` value to the given percentile over a group using a rounding method. 1.1+| xref::functions/aggregating.adoc#functions-stdev[`stdev()`] -| `stdev(input :: FLOAT?) :: (FLOAT?)` +| `stdev(input :: FLOAT) :: FLOAT` | Returns the standard deviation for the given value over a group for a sample of a population. 1.1+| xref::functions/aggregating.adoc#functions-stdevp[`stdevp()`] -| `stdevp(input :: FLOAT?) :: (FLOAT?)` +| `stdevp(input :: FLOAT) :: FLOAT` | Returns the standard deviation for the given value over a group for an entire population. 1.3+| xref::functions/aggregating.adoc#functions-sum[`sum()`] -| `sum(input :: DURATION?) :: (DURATION?)` +| `sum(input :: DURATION) :: DURATION` | Returns the sum of a set of `DURATION` values. -| `sum(input :: FLOAT?) :: (FLOAT?)` +| `sum(input :: FLOAT) :: FLOAT` | Returns the sum of a set of `FLOAT` values. -| `sum(input :: INTEGER?) :: (INTEGER?)` +| `sum(input :: INTEGER) :: INTEGER` | Returns the sum of a set of `INTEGER` values. |=== @@ -261,63 +261,63 @@ Further details and examples of lists may be found in xref::values-and-types/lis | Function | Signature | Description 1.3+| xref::functions/list.adoc#functions-keys[`keys()`] -| `keys(input :: MAP?) :: (LIST? OF STRING?)` +| `keys(input :: MAP) :: LIST` | Returns a `LIST` containing the `STRING` representations for all the property names of a `MAP`. -| `keys(input :: NODE?) :: (LIST? OF STRING?)` +| `keys(input :: NODE) :: LIST` | Returns a `LIST` containing the `STRING` representations for all the property names of a `NODE`. -| `keys(input :: RELATIONSHIP?) :: (LIST? OF STRING?)` +| `keys(input :: RELATIONSHIP) :: LIST` | Returns a `LIST` containing the `STRING` representations for all the property names of a `RELATIONSHIP`. 1.1+| xref::functions/list.adoc#functions-labels[`labels()`] -| `labels(input :: NODE?) :: (LIST? OF STRING?)` +| `labels(input :: NODE) :: LIST` | Returns a `LIST` containing the `STRING` representations for all the labels of a `NODE`. 1.1+| xref::functions/list.adoc#functions-nodes[`nodes()`] -| `nodes(input :: PATH?) :: (LIST? OF NODE?)` +| `nodes(input :: PATH) :: LIST` | Returns a `LIST` containing all the `NODE` values in a `PATH`. 1.2+| xref::functions/list.adoc#functions-range[`range()`] -| `range(start :: INTEGER?, end :: INTEGER?) :: (LIST? OF INTEGER?)` +| `range(start :: INTEGER, end :: INTEGER) :: LIST` | Returns a `LIST` comprising all `INTEGER` values within a specified range. -| `range(start :: INTEGER?, end :: INTEGER?, step :: INTEGER?) :: (LIST? OF INTEGER?)` +| `range(start :: INTEGER, end :: INTEGER, step :: INTEGER) :: LIST` | Returns a `LIST` comprising all `INTEGER` values within a specified range created with step length. 1.1+| xref::functions/list.adoc#functions-reduce[`reduce()`] -| `reduce(accumulator :: VARIABLE = initial :: ANY?, variable :: VARIABLE IN list :: LIST OF ANY? \| expression :: ANY) :: (ANY?)` +| `reduce(accumulator :: VARIABLE = initial :: ANY, variable :: VARIABLE IN list :: LIST \| expression :: ANY) :: ANY` | Runs an expression against individual elements of a `LIST`, storing the result of the expression in an accumulator. 1.1+| xref::functions/list.adoc#functions-relationships[`relationships()`] -| `relationships(input :: PATH?) :: (LIST? OF RELATIONSHIP?)` +| `relationships(input :: PATH) :: LIST` | Returns a `LIST` containing all the `RELATIONSHIP` values in a `PATH`. 1.1+| xref::functions/string.adoc#functions-reverse[`reverse()`] -| `reverse(input :: LIST? OF ANY?) :: (LIST? OF ANY?)` +| `reverse(input :: LIST) :: LIST` | Returns a `LIST` in which the order of all elements in the given `LIST` have been reversed. 1.1+| xref::functions/list.adoc#functions-tail[`tail()`] -| `tail(input :: LIST? OF ANY?) :: (LIST? OF ANY?)` +| `tail(input :: LIST) :: LIST` | Returns all but the first element in a `LIST`. 1.1+| xref::functions/list.adoc#functions-tobooleanlist[`toBooleanList()`] -| `toBooleanList(input :: LIST? OF ANY?) :: (LIST? OF BOOLEAN?)` +| `toBooleanList(input :: LIST) :: LIST` a| Converts a `LIST` of values to a `LIST` values. If any values are not convertible to `BOOLEAN` they will be null in the `LIST` returned. 1.1+| xref::functions/list.adoc#functions-tofloatlist[`toFloatList()`] -| `toFloatList(input :: LIST? OF ANY?) :: (LIST? OF FLOAT?)` +| `toFloatList(input :: LIST) :: LIST` a| Converts a `LIST` to a `LIST` values. If any values are not convertible to `FLOAT` they will be null in the `LIST` returned. 1.1+| xref::functions/list.adoc#functions-tointegerlist[`toIntegerList()`] -| `toIntegerList(input :: LIST? OF ANY?) :: (LIST? OF INTEGER?)` +| `toIntegerList(input :: LIST) :: LIST` a| Converts a `LIST` to a `LIST` values. If any values are not convertible to `INTEGER` they will be null in the `LIST` returned. 1.1+| xref::functions/list.adoc#functions-tostringlist[`toStringList()`] -| `toStringList(input :: LIST? OF ANY?) :: (LIST? OF STRING?)` +| `toStringList(input :: LIST) :: LIST` a| Converts a `LIST` to a `LIST` values. If any values are not convertible to `STRING` they will be null in the `LIST` returned. @@ -335,41 +335,41 @@ These functions all operate on numerical expressions only, and will return an er | Function | Signature | Description 1.2+| xref::functions/mathematical-numeric.adoc#functions-abs[`abs()`] -| `abs(input :: FLOAT?) :: (FLOAT?)` +| `abs(input :: FLOAT) :: FLOAT` | Returns the absolute value of a `FLOAT`. -| `abs(input :: INTEGER?) :: (INTEGER?)` +| `abs(input :: INTEGER) :: INTEGER` | Returns the absolute value of an `INTEGER`. 1.1+| xref::functions/mathematical-numeric.adoc#functions-ceil[`ceil()`] -| `ceil(input :: FLOAT?) :: (FLOAT?)` +| `ceil(input :: FLOAT) :: FLOAT` | Returns the smallest `FLOAT` that is greater than or equal to a number and equal to an `INTEGER`. 1.1+| xref::functions/mathematical-numeric.adoc#functions-floor[`floor()`] -| `floor(input :: FLOAT?) :: (FLOAT?)` +| `floor(input :: FLOAT) :: FLOAT` | Returns the largest `FLOAT` that is less than or equal to a number and equal to an `INTEGER`. 1.2+| xref::functions/mathematical-numeric.adoc#functions-isnan[`isNaN()`] -| `isNaN(input :: FLOAT?) :: (BOOLEAN?)` +| `isNaN(input :: FLOAT) :: BOOLEAN` | Returns `true` if the floating point number is `NaN`. -| `isNaN(input :: INTEGER?) :: (BOOLEAN?)` +| `isNaN(input :: INTEGER) :: BOOLEAN` | Returns `true` if the integer number is `NaN`. 1.1+| xref::functions/mathematical-numeric.adoc#functions-rand[`rand()`] -| `rand() :: (FLOAT?)` +| `rand() :: FLOAT` | Returns a random `FLOAT` in the range from 0 (inclusive) to 1 (exclusive). 1.3+| xref::functions/mathematical-numeric.adoc#functions-round[`round()`] -| `round(input :: FLOAT?) :: (FLOAT?)` +| `round(input :: FLOAT) :: FLOAT` | Returns the value of a number rounded to the nearest `INTEGER`. -| `round(value :: FLOAT?, precision :: NUMBER?) :: (FLOAT?)` +| `round(value :: FLOAT, precision :: INTEGER | FLOAT) :: FLOAT` | Returns the value of a number rounded to the specified precision using rounding mode HALF_UP. -| `round(value :: FLOAT?, precision :: NUMBER?, mode :: STRING?) :: (FLOAT?)` +| `round(value :: FLOAT, precision :: INTEGER | FLOAT, mode :: STRING) :: FLOAT` | Returns the value of a number rounded to the specified precision with the specified rounding mode. 1.2+| xref::functions/mathematical-numeric.adoc#functions-sign[`sign()`] -| `sign(input :: FLOAT?) :: (INTEGER?)` +| `sign(input :: FLOAT) :: INTEGER` | Returns the signum of a `FLOAT`: 0 if the number is 0, -1 for any negative number, and 1 for any positive number. -| `sign(input :: INTEGER?) :: (INTEGER?)` +| `sign(input :: INTEGER) :: INTEGER` | Returns the signum of an `INTEGER`: 0 if the number is 0, -1 for any negative number, and 1 for any positive number. |=== @@ -385,23 +385,23 @@ These functions all operate on numerical expressions only, and will return an er | Function | Signature | Description 1.1+| xref::functions/mathematical-logarithmic.adoc#functions-e[`e()`] -| `e() :: (FLOAT?)` +| `e() :: FLOAT` | Returns the base of the natural logarithm, e. 1.1+| xref::functions/mathematical-logarithmic.adoc#functions-exp[`exp()`] -| `exp(input :: FLOAT?) :: (FLOAT?)` +| `exp(input :: FLOAT) :: FLOAT` | Returns e^n^, where e is the base of the natural logarithm, and n is the value of the argument expression. 1.1+| xref::functions/mathematical-logarithmic.adoc#functions-log[`log()`] -| `log(input :: FLOAT?) :: (FLOAT?)` +| `log(input :: FLOAT) :: FLOAT` | Returns the natural logarithm of a `FLOAT`. 1.1+| xref::functions/mathematical-logarithmic.adoc#functions-log10[`log10()`] -| `log10(input :: FLOAT?) :: (FLOAT?)` +| `log10(input :: FLOAT) :: FLOAT` | Returns the common logarithm (base 10) of a `FLOAT`. 1.1+| xref::functions/mathematical-logarithmic.adoc#functions-sqrt[`sqrt()`] -| `sqrt(input :: FLOAT?) :: (FLOAT?)` +| `sqrt(input :: FLOAT) :: FLOAT` | Returns the square root of a `FLOAT`. |=== @@ -419,51 +419,51 @@ All trigonometric functions operate on radians, unless otherwise specified. | Function | Signature | Description 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-acos[`acos()`] -| `acos(input :: FLOAT?) :: (FLOAT?)` +| `acos(input :: FLOAT) :: FLOAT` | Returns the arccosine of a `FLOAT` in radians. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-asin[`asin()`] -| `asin(input :: FLOAT?) :: (FLOAT?)` +| `asin(input :: FLOAT) :: FLOAT` | Returns the arcsine of a `FLOAT` in radians. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-atan[`atan()`] -| `atan(input :: FLOAT?) :: (FLOAT?)` +| `atan(input :: FLOAT) :: FLOAT` | Returns the arctangent of a `FLOAT` in radians. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-atan2[`atan2()`] -| `atan2(y :: FLOAT?, x :: FLOAT?) :: (FLOAT?)` +| `atan2(y :: FLOAT, x :: FLOAT) :: FLOAT` | Returns the arctangent2 of a set of coordinates in radians. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-cos[`cos()`] -| `cos(input :: FLOAT?) :: (FLOAT?)` +| `cos(input :: FLOAT) :: FLOAT` | Returns the cosine of a `FLOAT`. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-cot[`cot()`] -| `cot(input :: FLOAT?) :: (FLOAT?)` +| `cot(input :: FLOAT) :: FLOAT` | Returns the cotangent of a `FLOAT`. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-degrees[`degrees()`] -| `degrees(input :: FLOAT?) :: (FLOAT?)` +| `degrees(input :: FLOAT) :: FLOAT` | Converts radians to degrees. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-haversin[`haversin()`] -| `haversin(input :: FLOAT?) :: (FLOAT?)` +| `haversin(input :: FLOAT) :: FLOAT` | Returns half the versine of a number. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-pi[`pi()`] -| `pi() :: (FLOAT?)` +| `pi() :: FLOAT` | Returns the mathematical constant pi. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-radians[`radians()`] -| `radians(input :: FLOAT?) :: (FLOAT?)` +| `radians(input :: FLOAT) :: FLOAT` | Converts degrees to radians. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-sin[`sin()`] -| `sin(input :: FLOAT?) :: (FLOAT?)` +| `sin(input :: FLOAT) :: FLOAT` | Returns the sine of a `FLOAT`. 1.1+| xref::functions/mathematical-trigonometric.adoc#functions-tan[`tan()`] -| `tan(input :: FLOAT?) :: (FLOAT?)` +| `tan(input :: FLOAT) :: FLOAT` | Returns the tangent of a `FLOAT`. |=== @@ -479,59 +479,59 @@ These functions are used to manipulate strings or to create a string representat | Function | Signature | Description 1.1+| xref::functions/string.adoc#functions-left[`left()`] -| `left(original :: STRING?, length :: INTEGER?) :: (STRING?)` +| `left(original :: STRING, length :: INTEGER) :: STRING` | Returns a `STRING` containing the specified number (`INTEGER`) of leftmost characters in the given `STRING`. 1.1+| xref::functions/string.adoc#functions-ltrim[`ltrim()`] -| `ltrim(input :: STRING?) :: (STRING?)` +| `ltrim(input :: STRING) :: STRING` | Returns the given `STRING` with leading whitespace removed. 1.1+| xref::functions/string.adoc#functions-replace[`replace()`] -| `replace(original :: STRING?, search :: STRING?, replace :: STRING?) :: (STRING?)` +| `replace(original :: STRING, search :: STRING, replace :: STRING) :: STRING` | Returns a `STRING` in which all occurrences of a specified search `STRING` in the given `STRING` have been replaced by another (specified) replacement `STRING`. 1.1+| xref::functions/string.adoc#functions-reverse[`reverse()`] -| `reverse(input :: STRING?) :: (STRING?)` +| `reverse(input :: STRING) :: STRING` | Returns a `STRING` in which the order of all characters in the given `STRING` have been reversed. 1.1+| xref::functions/string.adoc#functions-right[`right()`] -| `right(original :: STRING?, length :: INTEGER?) :: (STRING?)` +| `right(original :: STRING, length :: INTEGER) :: STRING` | Returns a `STRING` containing the specified number of rightmost characters in the given `STRING`. 1.1+| xref::functions/string.adoc#functions-rtrim[`rtrim()`] -| `rtrim(input :: STRING?) :: (STRING?)` +| `rtrim(input :: STRING) :: STRING` | Returns the given `STRING` with trailing whitespace removed. 1.2+| xref::functions/string.adoc#functions-split[`split()`] -| `split(original :: STRING?, splitDelimiter :: STRING?) :: (LIST? OF STRING?)` +| `split(original :: STRING, splitDelimiter :: STRING) :: LIST` | Returns a `LIST` resulting from the splitting of the given `STRING` around matches of the given delimiter. -| `split(original :: STRING?, splitDelimiters :: LIST? OF STRING?) :: (LIST? OF STRING?)` +| `split(original :: STRING, splitDelimiters :: LIST) :: LIST` | Returns a `LIST` resulting from the splitting of the given `STRING` around matches of any of the given delimiters. 1.2+| xref::functions/string.adoc#functions-substring[`substring()`] -| `substring(original :: STRING?, start :: INTEGER?) :: (STRING?)` +| `substring(original :: STRING, start :: INTEGER) :: STRING` | Returns a substring of the given `STRING`, beginning with a 0-based index start. -| `substring(original :: STRING?, start :: INTEGER?, length :: INTEGER?) :: (STRING?)` +| `substring(original :: STRING, start :: INTEGER, length :: INTEGER) :: STRING` | Returns a substring of a given `length` from the given `STRING`, beginning with a 0-based index start. 1.1+| xref::functions/string.adoc#functions-tolower[`toLower()`] -| `toLower(input :: STRING?) :: (STRING?)` +| `toLower(input :: STRING) :: STRING` | Returns the given `STRING` in lowercase. 1.1+| xref::functions/string.adoc#functions-tostring[`toString()`] -| `toString(input :: ANY?) :: (STRING?)` +| `toString(input :: ANY) :: STRING` | Converts an `INTEGER`, `FLOAT`, `BOOLEAN`, `POINT` or temporal type (i.e. `DATE`, `ZONED TIME`, `LOCAL TIME`, `ZONED DATETIME`, `LOCAL DATETIME` or `DURATION`) value to a `STRING`. 1.1+| xref::functions/string.adoc#functions-tostringornull[`toStringOrNull()`] -| `toStringOrNull(input :: ANY?) :: (STRING?)` +| `toStringOrNull(input :: ANY) :: STRING` | Converts an `INTEGER`, `FLOAT`, `BOOLEAN`, `POINT` or temporal type (i.e. `DATE`, `ZONED TIME`, `LOCAL TIME`, `ZONED DATETIME`, `LOCAL DATETIME` or `DURATION`) value to a `STRING`, or null if the value cannot be converted. 1.1+| xref::functions/string.adoc#functions-toupper[`toUpper()`] -| `toUpper(input :: STRING?) :: (STRING?)` +| `toUpper(input :: STRING) :: STRING` | Returns the given `STRING` in uppercase. 1.1+| xref::functions/string.adoc#functions-trim[`trim()`] -| `trim(input :: STRING?) :: (STRING?)` +| `trim(input :: STRING) :: STRING` | Returns the given `STRING` with leading and trailing whitespace removed. |=== @@ -547,111 +547,111 @@ Values of the xref::values-and-types/temporal.adoc[temporal types] -- `DATE`, `Z | Function | Signature | Description 1.1+| xref::functions/temporal/index.adoc#functions-date[`date()`] -| `date(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` +| `date(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: DATE` | Creates a `DATE` instant. 1.1+| xref::functions/temporal/index.adoc#functions-date-realtime[`date.realtime()`] -| `date.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` +| `date.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: DATE` | Returns the current `DATE` instant using the realtime clock. 1.1+| xref::functions/temporal/index.adoc#functions-date-statement[`date.statement()`] -| `date.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` +| `date.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: DATE` | Returns the current `DATE` instant using the statement clock. 1.1+| xref::functions/temporal/index.adoc#functions-date-transaction[`date.transaction()`] -| `date.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATE?)` +| `date.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: DATE` | Returns the current `DATE` instant using the transaction clock. 1.1+| xref::functions/temporal/index.adoc#functions-date-truncate[`date.truncate()`] -| `date.truncate(unit :: STRING?, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?, fields = null :: MAP?) :: (DATE?)` +| `date.truncate(unit :: STRING, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY, fields = null :: MAP) :: DATE` | Truncates the given temporal value to a `DATE` instant using the specified unit. 1.1+| xref::functions/temporal/index.adoc#functions-datetime[`datetime()`] -| `datetime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATETIME?)` +| `datetime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED DATETIME` | Creates a `ZONED DATETIME` instant. 1.1+| xref::functions/temporal/index.adoc#functions-datetime-timestamp[`datetime.fromepoch()`] -| `datetime.fromepoch(seconds :: NUMBER?, nanoseconds :: NUMBER?) :: (DATETIME?)` +| `datetime.fromepoch(seconds :: INTEGER | FLOAT, nanoseconds :: INTEGER | FLOAT) :: ZONED DATETIME` | Creates a `ZONED DATETIME` given the seconds and nanoseconds since the start of the epoch. 1.1+| xref::functions/temporal/index.adoc#functions-datetime-timestamp[`datetime.fromepochmillis()`] -| `datetime.fromepochmillis(milliseconds :: NUMBER?) :: (DATETIME?)` +| `datetime.fromepochmillis(milliseconds :: INTEGER | FLOAT) :: ZONED DATETIME` | Creates a `ZONED DATETIME` given the milliseconds since the start of the epoch. 1.1+| xref::functions/temporal/index.adoc#functions-datetime-realtime[`datetime.realtime()`] -| `datetime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATETIME?)` +| `datetime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED DATETIME` | Returns the current `ZONED DATETIME` instant using the realtime clock. 1.1+| xref::functions/temporal/index.adoc#functions-datetime-statement[`datetime.statement()`] -| `datetime.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATETIME?)` +| `datetime.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED DATETIME` | Returns the current `ZONED DATETIME` instant using the statement clock. 1.1+| xref::functions/temporal/index.adoc#functions-datetime-transaction[`datetime.transaction()`] -| `datetime.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (DATETIME?)` +| `datetime.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED DATETIME` | Returns the current `ZONED DATETIME` instant using the transaction clock. 1.1+| xref::functions/temporal/index.adoc#functions-datetime-truncate[`datetime.truncate()`] -| `datetime.truncate(unit :: STRING?, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?, fields = null :: MAP?) :: (DATETIME?)` +| `datetime.truncate(unit :: STRING, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY, fields = null :: MAP) :: ZONED DATETIME` | Truncates the given temporal value to a `ZONED DATETIME` instant using the specified unit. 1.1+| xref::functions/temporal/index.adoc#functions-localdatetime[`localdatetime()`] -| `localdatetime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` +| `localdatetime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL DATETIME` | Creates a `LOCAL DATETIME` instant. 1.1+| xref::functions/temporal/index.adoc#functions-localdatetime-realtime[`localdatetime.realtime()`] -| `localdatetime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` +| `localdatetime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL DATETIME` | Returns the current `LOCAL DATETIME` instant using the realtime clock. 1.1+| xref::functions/temporal/index.adoc#functions-localdatetime-statement[`localdatetime.statement()`] -| `localdatetime.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` +| `localdatetime.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL DATETIME` | Returns the current `LOCAL DATETIME` instant using the statement clock. 1.1+| xref::functions/temporal/index.adoc#functions-localdatetime-transaction[`localdatetime.transaction()`] -| `localdatetime.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALDATETIME?)` +| `localdatetime.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL DATETIME` | Returns the current `LOCAL DATETIME` instant using the transaction clock. 1.1+| xref::functions/temporal/index.adoc#functions-localdatetime-truncate[`localdatetime.truncate()`] -| `localdatetime.truncate(unit :: STRING?, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?, fields = null :: MAP?) :: (LOCALDATETIME?)` +| `localdatetime.truncate(unit :: STRING, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY, fields = null :: MAP) :: LOCAL DATETIME` | Truncates the given temporal value to a `LOCAL DATETIME` instant using the specified unit. 1.1+| xref::functions/temporal/index.adoc#functions-localtime[`localtime()`] -| `localtime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` +| `localtime(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL TIME` | Creates a `LOCAL TIME` instant. 1.1+| xref::functions/temporal/index.adoc#functions-localtime-realtime[`localtime.realtime()`] -| `localtime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` +| `localtime.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL TIME` | Returns the current `LOCAL TIME` instant using the realtime clock. 1.1+| xref::functions/temporal/index.adoc#functions-localtime-statement[`localtime.statement()`] -| `localtime.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` +| `localtime.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL TIME` | Returns the current `LOCAL TIME` instant using the statement clock. 1.1+| xref::functions/temporal/index.adoc#functions-localtime-transaction[`localtime.transaction()`] -| `localtime.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (LOCALTIME?)` +| `localtime.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: LOCAL TIME` | Returns the current `LOCAL TIME` instant using the transaction clock. 1.1+| xref::functions/temporal/index.adoc#functions-localtime-truncate[`localtime.truncate()`] -| `localtime.truncate(unit :: STRING?, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?, fields = null :: MAP?) :: (LOCALTIME?)` +| `localtime.truncate(unit :: STRING, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY, fields = null :: MAP) :: LOCAL TIME` | Truncates the given temporal value to a `LOCAL TIME` instant using the specified unit. 1.1+| xref::functions/temporal/index.adoc#functions-time[`time()`] -| `time(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` +| `time(input = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED TIME` | Creates a `ZONED TIME` instant. 1.1+| xref::functions/temporal/index.adoc#functions-time-realtime[`time.realtime()`] -| `time.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` +| `time.realtime(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED TIME` | Returns the current `ZONED TIME` instant using the realtime clock. 1.1+| xref::functions/temporal/index.adoc#functions-time-statement[`time.statement()`] -| `time.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` +| `time.statement(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED TIME` | Returns the current `ZONED TIME` instant using the statement clock. 1.1+| xref::functions/temporal/index.adoc#functions-time-transaction[`time.transaction()`] -| `time.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY?) :: (TIME?)` +| `time.transaction(timezone = DEFAULT_TEMPORAL_ARGUMENT :: ANY) :: ZONED TIME` | Returns the current `ZONED TIME` instant using the transaction clock. 1.1+| xref::functions/temporal/index.adoc#functions-time-truncate[`time.truncate()`] -| `time.truncate(unit :: STRING?, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY?, fields = null :: MAP?) :: (TIME?)` +| `time.truncate(unit :: STRING, input = DEFAULT_TEMPORAL_ARGUMENT :: ANY, fields = null :: MAP) :: ZONED TIME` | Truncates the given temporal value to a `ZONED TIME` instant using the specified unit. |=== @@ -667,23 +667,23 @@ Values of the xref::values-and-types/temporal.adoc[temporal types] -- `DATE`, `Z | Function | Signature | Description 1.1+| xref::functions/temporal/duration.adoc#functions-duration[`duration()`] -| `duration(input :: ANY?) :: (DURATION?)` +| `duration(input :: ANY) :: DURATION` | Constructs a `DURATION` value. 1.1+| xref::functions/temporal/duration.adoc#functions-duration-between[`duration.between()`] -| `duration.between(from :: ANY?, to :: ANY?) :: (DURATION?)` +| `duration.between(from :: ANY, to :: ANY) :: DURATION` | Computes the `DURATION` between the `from` instant (inclusive) and the `to` instant (exclusive) in logical units. 1.1+| xref::functions/temporal/duration.adoc#functions-duration-indays[`duration.inDays()`] -| `duration.inDays(from :: ANY?, to :: ANY?) :: (DURATION?)` +| `duration.inDays(from :: ANY, to :: ANY) :: DURATION` | Computes the `DURATION` between the `from` instant (inclusive) and the `to` instant (exclusive) in days. 1.1+| xref::functions/temporal/duration.adoc#functions-duration-inmonths[`duration.inMonths()`] -| `duration.inMonths(from :: ANY?, to :: ANY?) :: (DURATION?)` +| `duration.inMonths(from :: ANY, to :: ANY) :: DURATION` | Computes the `DURATION` between the `from` instant (inclusive) and the `to` instant (exclusive) in months. 1.1+| xref::functions/temporal/duration.adoc#functions-duration-inseconds[`duration.inSeconds()`] -| `duration.inSeconds(from :: ANY?, to :: ANY?) :: (DURATION?)` +| `duration.inSeconds(from :: ANY, to :: ANY) :: DURATION` | Computes the `DURATION` between the `from` instant (inclusive) and the `to` instant (exclusive) in seconds. |=== @@ -699,27 +699,27 @@ These functions are used to specify 2D or 3D points in a geographic or cartesian | Function | Signature | Description 1.1+| xref::functions/spatial.adoc#functions-distance[`point.distance()`] -| `point.distance(from :: POINT?, to :: POINT?) :: (FLOAT?)` +| `point.distance(from :: POINT, to :: POINT) :: FLOAT` | Returns a `FLOAT` representing the geodesic distance between any two points in the same CRS. 1.1+| xref::functions/spatial.adoc#functions-point-cartesian-2d[`point()` - Cartesian 2D] -| `point(input :: MAP?) :: (POINT?)` +| `point(input :: MAP) :: POINT` | Returns a 2D point object, given two coordinate values in the Cartesian coordinate system. 1.1+| xref::functions/spatial.adoc#functions-point-cartesian-3d[`point()` - Cartesian 3D] -| `point(input :: MAP?) :: (POINT?)` +| `point(input :: MAP) :: POINT` | Returns a 3D point object, given three coordinate values in the Cartesian coordinate system. 1.1+| xref::functions/spatial.adoc#functions-point-wgs84-2d[`point()` - WGS 84 2D] -| `point(input :: MAP?) :: (POINT?)` +| `point(input :: MAP) :: POINT` | Returns a 2D point object, given two coordinate values in the WGS 84 geographic coordinate system. 1.1+| xref::functions/spatial.adoc#functions-point-wgs84-3d[`point()` - WGS 84 3D] -| `point(input :: MAP?) :: (POINT?)` +| `point(input :: MAP) :: POINT` | Returns a 3D point object, given three coordinate values in the WGS 84 geographic coordinate system. 1.1+| xref::functions/spatial.adoc#functions-withinBBox[`point.withinBBox()`] -| `point.withinBBox(point :: POINT?, lowerLeft :: POINT?, upperRight :: POINT?) :: (BOOLEAN?)` +| `point.withinBBox(point :: POINT, lowerLeft :: POINT, upperRight :: POINT) :: BOOLEAN` | Returns `true` if the provided point is within the bounding box defined by the two provided points, `lowerLeft` and `upperRight`. |=== @@ -735,11 +735,11 @@ LOAD CSV functions can be used to get information about the file that is process | Function | Signature | Description 1.1+| xref::functions/load-csv.adoc#functions-file[`file()`] -| `file() :: (STRING?)` +| `file() :: STRING` | Returns the absolute path of the file that LOAD CSV is using. 1.1+| xref::functions/load-csv.adoc#functions-linenumber[`linenumber()`] -| `linenumber() :: (INTEGER?)` +| `linenumber() :: INTEGER` | Returns the line number that LOAD CSV is currently using. |=== @@ -753,9 +753,9 @@ Graph functions provide information about the constituent graphs in composite da [options="header"] |=== | Function | Signature | Description -1.1+| xref:functions/graph.adoc#functions-graph-names[`graph.names()`] | `graph.names() :: (LIST? OF STRING?)` | Returns a list containing the names of all graphs in the current composite database. -1.1+| xref:functions/graph.adoc#functions-graph-names[`graph.propertiesByName()`] | `graph.propertiesByName(name :: STRING?) :: (MAP?)` | Returns a map containing the properties associated with the given graph. -1.1+| xref:functions/graph.adoc#functions-graph-byname[`graph.byName()`] | `USE graph.byName(name :: STRING?)` | Resolves a constituent graph by name. +1.1+| xref:functions/graph.adoc#functions-graph-names[`graph.names()`] | `graph.names() :: LIST` | Returns a list containing the names of all graphs in the current composite database. +1.1+| xref:functions/graph.adoc#functions-graph-names[`graph.propertiesByName()`] | `graph.propertiesByName(name :: STRING) :: MAP` | Returns a map containing the properties associated with the given graph. +1.1+| xref:functions/graph.adoc#functions-graph-byname[`graph.byName()`] | `USE graph.byName(name :: STRING)` | Resolves a constituent graph by name. |=== [[header-query-functions-database]] @@ -766,7 +766,7 @@ Database functions provide information about databases. [options="header"] |=== | Function | Signature | Description -1.1+| xref:functions/database.adoc#functions-database-nameFromElementId[`db.nameFromElementId()`] | `db.nameFromElementId(name :: STRING?) :: (STRING?)` | Resolves the database name from the given element id. +1.1+| xref:functions/database.adoc#functions-database-nameFromElementId[`db.nameFromElementId()`] | `db.nameFromElementId(name :: STRING) :: STRING` | Resolves the database name from the given element id. |=== [[header-query-functions-user-defined]] diff --git a/modules/ROOT/pages/indexes-for-search-performance.adoc b/modules/ROOT/pages/indexes-for-search-performance.adoc index 2b793e6ba..dd6ce6b57 100644 --- a/modules/ROOT/pages/indexes-for-search-performance.adoc +++ b/modules/ROOT/pages/indexes-for-search-performance.adoc @@ -669,7 +669,7 @@ This command will produce a table with the following columns: Returns `null` if the index has not been read since `trackedSince`, or if the statistics are not tracked. label:default-output[] label:new[Introduced in 5.8] -| `DATETIME` +| `ZONED DATETIME` | `readCount` | The number of read queries that have been issued to this index since `trackedSince`, or `null` if the statistics are not tracked. label:default-output[] @@ -679,7 +679,7 @@ label:new[Introduced in 5.8] | `trackedSince` | The time when usage statistics tracking started for this index, or `null` if the statistics are not tracked. label:new[Introduced in 5.8] -| `DATETIME` +| `ZONED DATETIME` | `options` | The options passed to `CREATE` command. diff --git a/modules/ROOT/pages/indexes-for-vector-search.adoc b/modules/ROOT/pages/indexes-for-vector-search.adoc index 794f6f87e..4178df4a5 100644 --- a/modules/ROOT/pages/indexes-for-vector-search.adoc +++ b/modules/ROOT/pages/indexes-for-vector-search.adoc @@ -88,7 +88,7 @@ For details, see xref:#indexes-vector-similarity[]. .Signature for `db.index.vector.createNodeIndex` to create a vector node index [source,syntax,role="noheader",indent=0] ---- -db.index.vector.createNodeIndex(indexName :: STRING?, label :: STRING?, propertyKey :: STRING?, vectorDimension :: INTEGER?, vectorSimilarityFunction :: STRING?) :: VOID +db.index.vector.createNodeIndex(indexName :: STRING, label :: STRING, propertyKey :: STRING, vectorDimension :: INTEGER, vectorSimilarityFunction :: STRING) ---- [NOTE] @@ -166,7 +166,7 @@ You can query a vector index using the procedure {link-procedures-reference}#pro .Signature for `db.index.vector.queryNodes` to query a vector index [source,syntax,role="noheader",indent=0] ---- -db.index.vector.queryNodes(indexName :: STRING?, numberOfNearestNeighbours :: INTEGER?, query :: LIST? OF FLOAT?) :: (node :: NODE?, score :: FLOAT?) +db.index.vector.queryNodes(indexName :: STRING, numberOfNearestNeighbours :: INTEGER, query :: LIST) :: (node :: NODE, score :: FLOAT) ---- * The `indexName` (a `STRING`) refers to the unique name of the vector index to query. @@ -289,7 +289,7 @@ As a result, the store's space is approximately halved. .Signature for `db.create.setVectorProperty` to set a vector property [source,syntax,role="noheader",indent=0] ---- -db.create.setVectorProperty(node :: NODE?, key :: STRING?, vector :: LIST? OF FLOAT?) :: (node :: NODE?) +db.create.setVectorProperty(node :: NODE, key :: STRING, vector :: LIST) :: (node :: NODE) ---- Thus, this is the equivalent syntax for setting a vector with a procedure: From 58f8a5c02a22252fbf3d1a2e3f801f5cc13af6b5 Mon Sep 17 00:00:00 2001 From: Therese Magnusson Date: Wed, 20 Sep 2023 15:22:21 +0200 Subject: [PATCH 3/3] Re-document the split of EXECUTE and EXECUTE BOOSTED privileges (#733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change was made in 5.0 and originally added in https://github.com/neo-technology/neo4j-manual-modeling/pull/2748/files#diff-cf0b5dddf7b98b2a8f9d407b2be18664cc51fa65d6f33ea9dc88166e4c172288R1539, back in May 2022 but seemed to have been lost in the repo moves since then. Also update to not mention removed procedures or config settings anymore, which also seems to have been reintroduced in the docs restructure at some point. --------- Co-authored-by: Jens Pryce-Ã…klundh <112686610+JPryce-Aklundh@users.noreply.github.com> --- .../access-control/dbms-administration.adoc | 133 ++++++------------ 1 file changed, 44 insertions(+), 89 deletions(-) diff --git a/modules/ROOT/pages/administration/access-control/dbms-administration.adoc b/modules/ROOT/pages/administration/access-control/dbms-administration.adoc index d97f66d99..292fdd8a8 100644 --- a/modules/ROOT/pages/administration/access-control/dbms-administration.adoc +++ b/modules/ROOT/pages/administration/access-control/dbms-administration.adoc @@ -1447,7 +1447,7 @@ a|Rows: 1 [[access-control-dbms-administration-execute]] == The DBMS `EXECUTE` privileges -The DBMS privileges for procedure and user defined function execution can be assigned by using Cypher administrative commands. +The DBMS privileges for procedure and user-defined function execution can be assigned by using Cypher administrative commands. They can be granted, denied and revoked like other privileges. [NOTE] @@ -1471,7 +1471,7 @@ GRANT [IMMUTABLE] EXECUTE PROCEDURE[S] name-globbing[, ...] GRANT [IMMUTABLE] EXECUTE BOOSTED PROCEDURE[S] name-globbing[, ...] ON DBMS TO role[, ...] -| Enables the specified roles to execute the given procedures with elevated privileges. +| Enables the specified roles to use elevated privileges when executing the given procedures. | [source, syntax, role=noheader] GRANT [IMMUTABLE] EXECUTE ADMIN[ISTRATOR] PROCEDURES @@ -1483,20 +1483,15 @@ GRANT [IMMUTABLE] EXECUTE ADMIN[ISTRATOR] PROCEDURES GRANT [IMMUTABLE] EXECUTE [USER [DEFINED]] FUNCTION[S] name-globbing[, ...] ON DBMS TO role[, ...] -| Enables the specified roles to execute the given user defined functions. +| Enables the specified roles to execute the given user-defined functions. | [source, syntax, role=noheader] GRANT [IMMUTABLE] EXECUTE BOOSTED [USER [DEFINED]] FUNCTION[S] name-globbing[, ...] ON DBMS TO role[, ...] -| Enables the specified roles to execute the given user defined functions with elevated privileges. +| Enables the specified roles to use elevated privileges when executing the given user-defined functions. |=== -The `EXECUTE BOOSTED` privileges replace the `dbms.security.procedures.default_allowed` and `dbms.security.procedures.roles` configuration parameters for procedures and user defined functions. -The configuration parameters are still honored as a set of temporary privileges. -These cannot be revoked, but will be updated on each restart with the current configuration values. - - [[access-control-execute-procedure]] === The `EXECUTE PROCEDURE` privilege @@ -1529,7 +1524,7 @@ a|Rows: 1 |=== In order to allow the execution of all but only a few procedures, you can grant `EXECUTE PROCEDURES *` and deny the unwanted procedures. -For example, the following queries allow the execution of all procedures, except those starting with `dbms.killTransaction`: +For example, the following queries allow the execution of all procedures, except those starting with `dbms.cluster`: [source, cypher, role=noplay] ---- @@ -1538,10 +1533,10 @@ GRANT EXECUTE PROCEDURE * ON DBMS TO deniedProcedureExecutor [source, cypher, role=noplay] ---- -DENY EXECUTE PROCEDURE dbms.killTransaction* ON DBMS TO deniedProcedureExecutor +DENY EXECUTE PROCEDURE dbms.cluster* ON DBMS TO deniedProcedureExecutor ---- -The resulting role has privileges that only allow executing all procedures except those starting with `dbms.killTransaction`. +The resulting role has privileges that allow executing all procedures except those starting with `dbms.cluster`. List all privileges for the role `deniedProcedureExecutor` as commands by using the following query: [source, cypher, role=noplay] @@ -1553,23 +1548,26 @@ SHOW ROLE deniedProcedureExecutor PRIVILEGES AS COMMANDS [options="header,footer", width="100%", cols="m"] |=== |command -|"DENY EXECUTE PROCEDURE dbms.killTransaction* ON DBMS TO `deniedProcedureExecutor`" +|"DENY EXECUTE PROCEDURE dbms.cluster* ON DBMS TO `deniedProcedureExecutor`" |"GRANT EXECUTE PROCEDURE * ON DBMS TO `deniedProcedureExecutor`" a|Rows: 2 |=== -Both the `dbms.killTransaction` and the `dbms.killTransactions` procedures are blocked here, as well as any other procedures starting with `dbms.killTransaction`. - +The `dbms.cluster.checkConnectivity`, `dbms.cluster.cordonServer`, `dbms.cluster.protocols`, `dbms.cluster.readReplicaToggle`, `dbms.cluster.routing.getRoutingTable`, `dbms.cluster.secondaryReplicationDisable`, `dbms.cluster.setAutomaticallyEnableFreeServers`, and `dbms.cluster.uncordonServer` procedures are blocked, as well as any others starting with `dbms.cluster`. [[access-control-execute-boosted-procedure]] === The `EXECUTE BOOSTED PROCEDURE` privilege -The ability to execute a procedure with elevated privileges can be granted via the `EXECUTE BOOSTED PROCEDURE` privilege. -A user with this privilege is allowed to execute the procedures matched by the xref::administration/access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing] without the execution being restricted to their other privileges. +The ability to use elevated privileges when executing a procedure can be granted via the `EXECUTE BOOSTED PROCEDURE` privilege. +A user with this privilege will not be restricted to their other privileges when executing the procedures matched by the xref::administration/access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing]. +The `EXECUTE BOOSTED PROCEDURE` privilege only affects the elevation, and not the execution of the procedure. +Therefore, it is needed to grant `EXECUTE PROCEDURE` privilege for the procedures as well. -There is no need to grant an individual `EXECUTE PROCEDURE` privilege for the procedures either, as granting the `EXECUTE BOOSTED PROCEDURE` includes an implicit `EXECUTE PROCEDURE` grant for them. -A denied `EXECUTE PROCEDURE` still denies executing the procedure. -The following query shows an example of how to grant this privilege: +[NOTE] +-- +Since Neo4j 5.0, both `EXECUTE PROCEDURE` and `EXECUTE BOOSTED PROCEDURE` are needed to execute a procedure with elevated privileges. +This differs from Neo4j 4.x, when only the `EXECUTE BOOSTED PROCEDURE` was required. +-- [source, cypher, role=noplay] ---- @@ -1577,11 +1575,10 @@ GRANT EXECUTE PROCEDURE * ON DBMS TO boostedProcedureExecutor; GRANT EXECUTE BOOSTED PROCEDURE db.labels, db.relationshipTypes ON DBMS TO boostedProcedureExecutor ---- -Users with the role `boostedProcedureExecutor` can thus run the `db.labels` and the `db.relationshipTypes` procedures with full privileges. -Now they can see everything on the graph and not just the labels and types that the user has `TRAVERSE` privilege on. +Users with the role `boostedProcedureExecutor` can thus run the `db.labels` and the `db.relationshipTypes` procedures with full privileges, seeing everything in the graph and not just the labels and types that the user has `TRAVERSE` privilege on. +Without the `EXECUTE PROCEDURE`, no procedures could be executed at all. -The resulting role has privileges that only allow executing the `db.labels` and the `db.relationshipTypes` procedures, but with elevated execution. -List all privileges for the role `boostedProcedureExecutor` as commands by using the following query: +The resulting role has privileges that allow executing the procedures `db.labels` and `db.relationshipTypes` with elevated privileges, and all other procedures with the user's own privileges: [source, cypher, role=noplay] ---- @@ -1598,10 +1595,8 @@ SHOW ROLE boostedProcedureExecutor PRIVILEGES AS COMMANDS a|Rows: 3 |=== -Granting the `EXECUTE BOOSTED PROCEDURE` privilege on its own allows the procedure to be both executed (due to the implicit `EXECUTE PROCEDURE` grant) and proceed with elevated privileges. -A denied `EXECUTE BOOSTED PROCEDURE` on its own behaves slightly differently: it only denies the elevation and not the execution of the procedure. -However, a role with both a granted `EXECUTE BOOSTED PROCEDURE` and a denied `EXECUTE BOOSTED PROCEDURE` will deny the execution as well. -This is explained through the following examples: +As with grant, denying `EXECUTE BOOSTED PROCEDURE` on its own only affects the elevation and not the execution of the procedure. +This can be seen in the following examples: .Grant `EXECUTE PROCEDURE` and deny `EXECUTE BOOSTED PROCEDURE` [example] @@ -1650,7 +1645,7 @@ GRANT EXECUTE BOOSTED PROCEDURE * ON DBMS TO deniedBoostedProcedureExecutor2 DENY EXECUTE PROCEDURE db.labels ON DBMS TO deniedBoostedProcedureExecutor2 ---- -The resulting role has privileges that allow executing all procedures with elevated privileges except `db.labels`, which is not allowed to be executed at all. +The resulting role has privileges that allow elevating the privileges for all procedures, but cannot execute any due to missing or denied `EXECUTE PROCEDURE` privileges. List all privileges for the role `deniedBoostedProcedureExecutor2` as commands by using the following query: [source, cypher, role=noplay] @@ -1681,7 +1676,7 @@ GRANT EXECUTE BOOSTED PROCEDURE * ON DBMS TO deniedBoostedProcedureExecutor3 DENY EXECUTE BOOSTED PROCEDURE db.labels ON DBMS TO deniedBoostedProcedureExecutor3 ---- -The resulting role has privileges that allow executing all procedures with elevated privileges except `db.labels`, which is not allowed to be executed at all. +The resulting role has privileges that allow elevating the privileges for all procedures except `db.labels`, however no procedures can be executed due to missing `EXECUTE PROCEDURE` privilege. List all privileges for the role `deniedBoostedProcedureExecutor3` as commands by using the following query: [source, cypher, role=noplay] @@ -1699,51 +1694,14 @@ a|Rows: 2 |=== ==== -.Grant `EXECUTE PROCEDURE` and `EXECUTE BOOSTED PROCEDURE` and deny `EXECUTE BOOSTED PROCEDURE` -[example] -==== -[source, cypher, role=noplay] ----- -GRANT EXECUTE PROCEDURE db.labels ON DBMS TO deniedBoostedProcedureExecutor4 ----- - -[source, cypher, role=noplay] ----- -GRANT EXECUTE BOOSTED PROCEDURE * ON DBMS TO deniedBoostedProcedureExecutor4 ----- - -[source, cypher, role=noplay] ----- -DENY EXECUTE BOOSTED PROCEDURE db.labels ON DBMS TO deniedBoostedProcedureExecutor4 ----- - -The resulting role has privileges that allow executing all procedures with elevated privileges except the `db.labels` procedure, which is only allowed to execute using the user's own privileges. -List all privileges for the role `deniedBoostedProcedureExecutor4` as commands by using the following query: - -[source, cypher, role=noplay] ----- -SHOW ROLE deniedBoostedProcedureExecutor4 PRIVILEGES AS COMMANDS ----- - -.Result -[options="header,footer", width="100%", cols="m"] -|=== -|command -|"DENY EXECUTE BOOSTED PROCEDURE db.labels ON DBMS TO `deniedBoostedProcedureExecutor4`" -|"GRANT EXECUTE BOOSTED PROCEDURE * ON DBMS TO `deniedBoostedProcedureExecutor4`" -|"GRANT EXECUTE PROCEDURE db.labels ON DBMS TO `deniedBoostedProcedureExecutor4`" -a|Rows: 3 -|=== -==== - -.How would the privileges from examples 1 to 4 affect the output of a procedure? +.How would the privileges from examples 1 to 3 affect the output of a procedure? [example] ==== Assume there is a procedure called `myProc`. This procedure gives the result `A` and `B` for a user with `EXECUTE PROCEDURE` privilege and `A`, `B` and `C` for a user with `EXECUTE BOOSTED PROCEDURE` privilege. -Now, adapt the privileges from examples 1 to 4 to be applied to this procedure and show what is returned. +Now, adapt the privileges from examples 1 to 3 to be applied to this procedure and show what is returned. With the privileges from example 1, granted `EXECUTE PROCEDURE *` and denied `EXECUTE BOOSTED PROCEDURE myProc`, the `myProc` procedure returns the result `A` and `B`. With the privileges from example 2, granted `EXECUTE BOOSTED PROCEDURE *` and denied `EXECUTE PROCEDURE myProc`, execution of the `myProc` procedure is not allowed. @@ -1755,16 +1713,13 @@ For comparison, when granted: * `EXECUTE PROCEDURE myProc`: the `myProc` procedure returns the result `A` and `B`. * `EXECUTE BOOSTED PROCEDURE myProc`: execution of the `myProc` procedure is not allowed. * `EXECUTE PROCEDURE myProc` and `EXECUTE BOOSTED PROCEDURE myProc`: the `myProc` procedure returns the result `A`, `B`, and `C`. - -For comparison, when only `EXECUTE BOOSTED PROCEDURE myProc` is granted, the `myProc` procedure returns the result `A`, `B`, and `C`; without the need for granting of the `EXECUTE PROCEDURE myProc` privilege. ==== - [[access-control-admin-procedure]] === The `EXECUTE ADMIN PROCEDURE` privilege The ability to execute admin procedures (annotated with `@Admin`) can be granted via the `EXECUTE ADMIN PROCEDURES` privilege. -This privilege is equivalent to granting the xref::administration/access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[`EXECUTE BOOSTED PROCEDURE` privilege] on each of the admin procedures. +This privilege is equivalent with granting the xref::administration/access-control/dbms-administration.adoc#access-control-execute-procedure[`EXECUTE PROCEDURE`] and xref::administration/access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[`EXECUTE BOOSTED PROCEDURE`] privileges on each of the admin procedures. Any newly added `admin` procedure is automatically included in this privilege. The following query shows an example of how to grant this privilege: @@ -1774,8 +1729,8 @@ GRANT EXECUTE ADMIN PROCEDURES ON DBMS TO adminProcedureExecutor ---- Users with the role `adminProcedureExecutor` can then run any `admin` procedure with elevated privileges. - The resulting role has privileges that allow executing all admin procedures. + List all privileges for the role `adminProcedureExecutor` as commands by using the following query: [source, cypher, role=noplay] @@ -1793,14 +1748,13 @@ a|Rows: 1 In order to compare this with the `EXECUTE PROCEDURE` and `EXECUTE BOOSTED PROCEDURE` privileges, revisit the `myProc` procedure, but this time as an `admin` procedure, which will give the result `A`, `B` and `C` when allowed to execute. -By starting with a user only granted with the `EXECUTE PROCEDURE myProc` privilege, execution of the `myProc` procedure is not allowed. +By starting with a user only granted the `EXECUTE PROCEDURE myProc` or the `EXECUTE BOOSTED PROCEDURE myProc` privilege, execution of the `myProc` procedure is not allowed. -However, for a user granted with the `EXECUTE BOOSTED PROCEDURE myProc` or `EXECUTE ADMIN PROCEDURES` privileges, the `myProc` procedure returns the result `A`, `B` and `C`. +However, for a user granted the `EXECUTE ADMIN PROCEDURES` or both `EXECUTE PROCEDURE myProc` and `EXECUTE BOOSTED PROCEDURE myProc`, the `myProc` procedure returns the result `A`, `B` and `C`. Any denied `EXECUTE` privilege results in the procedure not being allowed to be executed. In this case, it does not matter whether `EXECUTE PROCEDURE`, `EXECUTE BOOSTED PROCEDURE` or `EXECUTE ADMIN PROCEDURES` is being denied. - [[access-control-execute-user-defined-function]] === The `EXECUTE USER DEFINED FUNCTION` privilege @@ -1901,22 +1855,22 @@ The `apoc.any.property` and `apoc.any.properties` are blocked, as well as any ot === The `EXECUTE BOOSTED USER DEFINED FUNCTION` privilege //EXECUTE BOOSTED [USER [DEFINED]] FUNCTION[S] -The ability to execute a user-defined function (UDF) with elevated privileges can be granted via the `EXECUTE BOOSTED USER DEFINED FUNCTION` privilege. -A user with this privilege is allowed to execute the UDFs matched by the xref::administration/access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing] without the execution being restricted to their other privileges. +The ability to use elevated privileges when executing a user-defined function (UDF) can be granted via the `EXECUTE BOOSTED USER DEFINED FUNCTION` privilege. +A user with this privilege will not be restricted to their other privileges when executing the UDFs matched by the xref::administration/access-control/dbms-administration.adoc#access-control-name-globbing[name-globbing]. +The `EXECUTE BOOSTED USER DEFINED FUNCTION` privilege only affects the elevation and not the execution of the function. +Therefore, it is needed to grant `EXECUTE USER DEFINED FUNCTION` privilege for the functions as well. -There is no need to grant an individual `EXECUTE USER DEFINED FUNCTION` privilege for the functions, as granting `EXECUTE BOOSTED USER DEFINED FUNCTION` includes an implicit `EXECUTE USER DEFINED FUNCTION` grant. -However, a denied `EXECUTE USER DEFINED FUNCTION` still prevents the function to be executed. +[NOTE] +-- +Since Neo4j 5.0, both `EXECUTE USER DEFINED FUNCTION` and `EXECUTE BOOSTED USER DEFINED FUNCTION` are needed to execute a function with elevated privileges. +This differs from Neo4j 4.x, when only the `EXECUTE BOOSTED USER DEFINED FUNCTION` was required. +-- [IMPORTANT] ==== The `EXECUTE BOOSTED USER DEFINED FUNCTION` privilege does not apply to built-in functions, as they have no concept of elevated privileges. ==== -Granting `EXECUTE BOOSTED USER DEFINED FUNCTION` on its own allows the UDF to be both executed (because of the implicit `EXECUTE USER DEFINED FUNCTION` grant) and gives it elevated privileges during the execution. -A denied `EXECUTE BOOSTED USER DEFINED FUNCTION` on its own behaves slightly differently: it only denies the elevation and not the execution of the UDF. -However, a role with only a granted `EXECUTE BOOSTED USER DEFINED FUNCTION` and a denied `EXECUTE BOOSTED USER DEFINED FUNCTION` prevents the execution to be performed as well. -This is the same behavior as for the xref::administration/access-control/dbms-administration.adoc#access-control-execute-boosted-procedure[`EXECUTE BOOSTED PROCEDURE` privilege]. - .Execute boosted user-defined function ====== The following query shows an example of how to grant the `EXECUTE BOOSTED USER DEFINED FUNCTION` privilege: @@ -1936,8 +1890,9 @@ GRANT EXECUTE BOOSTED FUNCTION apoc.any.properties ON DBMS TO boostedFunctionExe ---- Users with the role `boostedFunctionExecutor` can thus run `apoc.any.properties` with full privileges and see every property on the node/relationship, not just the properties that the user has `READ` privilege on. +Without the `EXECUTE USER DEFINED FUNCTION` no UDFs could be executed at all. -The resulting role has privileges that only allow executing of the UDF `apoc.any.properties`, but with elevated execution. +The resulting role has privileges that allow executing the UDF `apoc.any.properties` with elevated privileges, and all other UDFs with the users own privileges. List all privileges for the role `boostedFunctionExecutor` as commands by using the following query: [source,cypher,role=noplay] @@ -2052,7 +2007,7 @@ The right to perform the following privileges can be achieved with a single comm * Enable, alter, rename, reallocate, deallocate, and drop servers * Show, assign, and remove privileges. * Execute all procedures with elevated privileges. -* Execute all user defined functions with elevated privileges. +* Execute all user-defined functions with elevated privileges. * Show all configuration settings. [NOTE] @@ -2092,7 +2047,7 @@ a|Rows: 1 [[access-control-name-globbing]] == Name-globbing for procedures, user-defined functions, and settings -The name-globbing for procedure, user defined function, and setting names is a simplified version of globbing for filename expansions. +The name-globbing for procedure, user-defined function, and setting names is a simplified version of globbing for filename expansions. It only allows two wildcard characters: `+*+` and `?`, which are used for multiple and single character matches. In this case, `+*+` means 0 or more characters and `?` matches exactly one character.